diff --git a/src/components/dashboard/IntegrationFilter.tsx b/src/components/dashboard/IntegrationFilter.tsx index 8a67fee..5e2099d 100644 --- a/src/components/dashboard/IntegrationFilter.tsx +++ b/src/components/dashboard/IntegrationFilter.tsx @@ -3,12 +3,18 @@ import { useState, useMemo } from 'react'; import { useTasksContext } from '@/contexts/TasksContext'; import { Dropdown, Button } from '@/components/ui'; +import type { KanbanFilters } from '@/lib/types'; interface IntegrationFilterProps { - selectedSources: string[]; - onSourcesChange: (sources: string[]) => void; - hiddenSources: string[]; - onHiddenSourcesChange: (sources: string[]) => void; + // Interface pour Kanban (nouvelle) + filters?: KanbanFilters; + onFiltersChange?: (filters: KanbanFilters) => void; + + // Interface pour Dashboard (ancienne) + selectedSources?: string[]; + onSourcesChange?: (sources: string[]) => void; + hiddenSources?: string[]; + onHiddenSourcesChange?: (sources: string[]) => void; } interface SourceOption { @@ -20,15 +26,24 @@ interface SourceOption { type FilterMode = 'all' | 'show' | 'hide'; -export function IntegrationFilter({ selectedSources, onSourcesChange, hiddenSources, onHiddenSourcesChange }: IntegrationFilterProps) { - const { regularTasks } = useTasksContext(); +export function IntegrationFilter({ + filters, + onFiltersChange, + selectedSources, + onSourcesChange, + hiddenSources, + onHiddenSourcesChange +}: IntegrationFilterProps) { + const { regularTasks, pinnedTasks } = useTasksContext(); const [isOpen, setIsOpen] = useState(false); - // Vérifier quelles sources ont des tâches + // Vérifier quelles sources ont des tâches (regularTasks + pinnedTasks) const sources = useMemo((): SourceOption[] => { - const hasJiraTasks = regularTasks.some(task => task.source === 'jira'); - const hasTfsTasks = regularTasks.some(task => task.source === 'tfs'); - const hasManualTasks = regularTasks.some(task => task.source === 'manual'); + const allTasks = [...regularTasks, ...pinnedTasks]; + const hasJiraTasks = allTasks.some(task => task.source === 'jira'); + const hasTfsTasks = allTasks.some(task => task.source === 'tfs'); + const hasManualTasks = allTasks.some(task => task.source === 'manual'); + return [ { @@ -50,7 +65,7 @@ export function IntegrationFilter({ selectedSources, onSourcesChange, hiddenSour hasTasks: hasManualTasks } ].filter(source => source.hasTasks); - }, [regularTasks]); + }, [regularTasks, pinnedTasks]); // Si aucune source disponible, on n'affiche rien if (sources.length === 0) { @@ -58,53 +73,107 @@ export function IntegrationFilter({ selectedSources, onSourcesChange, hiddenSour } - const handleModeChange = (sourceId: string, mode: FilterMode) => { - let newSelectedSources = [...selectedSources]; - let newHiddenSources = [...hiddenSources]; - - if (mode === 'show') { - // Ajouter à selectedSources et retirer de hiddenSources - if (!newSelectedSources.includes(sourceId)) { - newSelectedSources.push(sourceId); + // Déterminer le mode d'utilisation (Kanban ou Dashboard) + const isKanbanMode = filters && onFiltersChange; + const isDashboardMode = selectedSources && onSourcesChange && hiddenSources && onHiddenSourcesChange; + + // Déterminer l'état actuel de chaque source + const getSourceMode = (sourceId: 'jira' | 'tfs' | 'manual'): FilterMode => { + if (isKanbanMode && filters) { + if (sourceId === 'jira') { + return filters.showJiraOnly ? 'show' : filters.hideJiraTasks ? 'hide' : 'all'; + } else if (sourceId === 'tfs') { + return filters.showTfsOnly ? 'show' : filters.hideTfsTasks ? 'hide' : 'all'; + } else { // manual + return filters.showManualOnly ? 'show' : filters.hideManualTasks ? 'hide' : 'all'; } - newHiddenSources = newHiddenSources.filter(id => id !== sourceId); - } else if (mode === 'hide') { - // Ajouter à hiddenSources et retirer de selectedSources - if (!newHiddenSources.includes(sourceId)) { - newHiddenSources.push(sourceId); + } else if (isDashboardMode && selectedSources && hiddenSources) { + if (selectedSources.includes(sourceId)) { + return 'show'; + } else if (hiddenSources.includes(sourceId)) { + return 'hide'; + } else { + return 'all'; } - newSelectedSources = newSelectedSources.filter(id => id !== sourceId); - } else { // 'all' - // Retirer des deux listes - newSelectedSources = newSelectedSources.filter(id => id !== sourceId); - newHiddenSources = newHiddenSources.filter(id => id !== sourceId); } - - onHiddenSourcesChange(newHiddenSources); - onSourcesChange(newSelectedSources); + return 'all'; + }; + + const handleModeChange = (sourceId: 'jira' | 'tfs' | 'manual', mode: FilterMode) => { + if (isKanbanMode && filters && onFiltersChange) { + const updates: Partial = {}; + + if (sourceId === 'jira') { + updates.showJiraOnly = mode === 'show'; + updates.hideJiraTasks = mode === 'hide'; + } else if (sourceId === 'tfs') { + updates.showTfsOnly = mode === 'show'; + updates.hideTfsTasks = mode === 'hide'; + } else if (sourceId === 'manual') { + updates.showManualOnly = mode === 'show'; + updates.hideManualTasks = mode === 'hide'; + } + + onFiltersChange({ ...filters, ...updates }); + } else if (isDashboardMode && onSourcesChange && onHiddenSourcesChange && selectedSources && hiddenSources) { + let newSelectedSources = [...selectedSources]; + let newHiddenSources = [...hiddenSources]; + + if (mode === 'show') { + // Ajouter à selectedSources et retirer de hiddenSources + if (!newSelectedSources.includes(sourceId)) { + newSelectedSources.push(sourceId); + } + newHiddenSources = newHiddenSources.filter(id => id !== sourceId); + } else if (mode === 'hide') { + // Ajouter à hiddenSources et retirer de selectedSources + if (!newHiddenSources.includes(sourceId)) { + newHiddenSources.push(sourceId); + } + newSelectedSources = newSelectedSources.filter(id => id !== sourceId); + } else { // 'all' + // Retirer des deux listes + newSelectedSources = newSelectedSources.filter(id => id !== sourceId); + newHiddenSources = newHiddenSources.filter(id => id !== sourceId); + } + + onHiddenSourcesChange(newHiddenSources); + onSourcesChange(newSelectedSources); + } }; + // Déterminer la variante du bouton principal + const getMainButtonVariant = () => { + const activeFilters = sources.filter(source => { + const mode = getSourceMode(source.id); + return mode !== 'all'; + }); + + return activeFilters.length === 0 ? 'secondary' : 'selected'; + }; + const getMainButtonText = () => { - if (selectedSources.length === 0 && hiddenSources.length === 0) { - return 'Toutes les sources'; - } else if (selectedSources.length === 1 && hiddenSources.length === 0) { - const source = sources.find(s => s.id === selectedSources[0]); - return source ? `Seulement ${source.label}` : 'Source sélectionnée'; - } else if (hiddenSources.length === 1 && selectedSources.length === 0) { - const source = sources.find(s => s.id === hiddenSources[0]); - return source ? `Sans ${source.label}` : 'Source masquée'; + const activeFilters = sources.filter(source => { + const mode = getSourceMode(source.id); + return mode !== 'all'; + }); + + if (activeFilters.length === 0) { + return 'Sources'; + } else if (activeFilters.length === 1) { + const source = activeFilters[0]; + const mode = getSourceMode(source.id); + return mode === 'show' ? `Seulement ${source.label}` : `Sans ${source.label}`; } else { - const total = selectedSources.length + hiddenSources.length; - return `${total} filtres actifs`; + return `${activeFilters.length} filtres actifs`; } }; const dropdownContent = (
{sources.map((source) => { - const isSelected = selectedSources.includes(source.id); - const isHidden = hiddenSources.includes(source.id); + const currentMode = getSourceMode(source.id); return (
@@ -118,11 +187,13 @@ export function IntegrationFilter({ selectedSources, onSourcesChange, hiddenSour {/* Bouton Afficher */}
@@ -176,7 +261,10 @@ export function IntegrationFilter({ selectedSources, onSourcesChange, hiddenSour open={isOpen} onOpenChange={setIsOpen} trigger={`🔗 ${getMainButtonText()}`} + variant={getMainButtonVariant()} content={dropdownContent} + placement="bottom-start" + className="min-w-[240px]" /> ); } diff --git a/src/components/kanban/DesktopControls.tsx b/src/components/kanban/DesktopControls.tsx index 5c13ec3..29f4034 100644 --- a/src/components/kanban/DesktopControls.tsx +++ b/src/components/kanban/DesktopControls.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { Button, ToggleButton, SearchInput, ControlPanel, ControlSection, ControlGroup } from '@/components/ui'; -import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter'; +import { IntegrationFilter } from '@/components/dashboard/IntegrationFilter'; import { FontSizeToggle } from '@/components/ui/FontSizeToggle'; import type { KanbanFilters } from '@/lib/types'; @@ -134,7 +134,7 @@ export function DesktopControls({ {/* Raccourcis Sources (Jira & TFS) */} - diff --git a/src/components/kanban/MobileControls.tsx b/src/components/kanban/MobileControls.tsx index 72b3782..8c3cadc 100644 --- a/src/components/kanban/MobileControls.tsx +++ b/src/components/kanban/MobileControls.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Button, ToggleButton, ControlPanel } from '@/components/ui'; -import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter'; +import { IntegrationFilter } from '@/components/dashboard/IntegrationFilter'; import { FontSizeToggle } from '@/components/ui/FontSizeToggle'; import type { KanbanFilters } from '@/lib/types'; @@ -149,7 +149,7 @@ export function MobileControls({ Sources
- diff --git a/src/components/kanban/SourceQuickFilter.tsx b/src/components/kanban/SourceQuickFilter.tsx deleted file mode 100644 index 7582990..0000000 --- a/src/components/kanban/SourceQuickFilter.tsx +++ /dev/null @@ -1,179 +0,0 @@ -'use client'; - -import { useState, useMemo } from 'react'; -import { useTasksContext } from '@/contexts/TasksContext'; -import { Dropdown, Button } from '@/components/ui'; -import type { KanbanFilters } from '@/lib/types'; - -interface SourceQuickFilterProps { - filters: KanbanFilters; - onFiltersChange: (filters: KanbanFilters) => void; -} - -interface SourceOption { - id: 'jira' | 'tfs'; - label: string; - icon: string; - hasTasks: boolean; -} - -type FilterMode = 'all' | 'show' | 'hide'; - -export function SourceQuickFilter({ filters, onFiltersChange }: SourceQuickFilterProps) { - const { regularTasks } = useTasksContext(); - const [isOpen, setIsOpen] = useState(false); - - // Vérifier quelles sources ont des tâches - const sources = useMemo((): SourceOption[] => { - const hasJiraTasks = regularTasks.some(task => task.source === 'jira'); - const hasTfsTasks = regularTasks.some(task => task.source === 'tfs'); - - return [ - { - id: 'jira' as const, - label: 'Jira', - icon: '🔹', - hasTasks: hasJiraTasks - }, - { - id: 'tfs' as const, - label: 'TFS', - icon: '🔷', - hasTasks: hasTfsTasks - } - ].filter(source => source.hasTasks); - }, [regularTasks]); - - - // Si aucune source disponible, on n'affiche rien - if (sources.length === 0) { - return null; - } - - // Déterminer l'état actuel de chaque source - const getSourceMode = (sourceId: 'jira' | 'tfs'): FilterMode => { - if (sourceId === 'jira') { - return filters.showJiraOnly ? 'show' : filters.hideJiraTasks ? 'hide' : 'all'; - } else { - return filters.showTfsOnly ? 'show' : filters.hideTfsTasks ? 'hide' : 'all'; - } - }; - - const handleModeChange = (sourceId: 'jira' | 'tfs', mode: FilterMode) => { - const updates: Partial = {}; - - if (sourceId === 'jira') { - updates.showJiraOnly = mode === 'show'; - updates.hideJiraTasks = mode === 'hide'; - } else { - updates.showTfsOnly = mode === 'show'; - updates.hideTfsTasks = mode === 'hide'; - } - - onFiltersChange({ ...filters, ...updates }); - }; - - // Déterminer le texte du bouton principal - const getMainButtonVariant = () => { - const activeFilters = sources.filter(source => { - const mode = getSourceMode(source.id); - return mode !== 'all'; - }); - - return activeFilters.length === 0 ? 'secondary' : 'selected'; - }; - - const getMainButtonText = () => { - const activeFilters = sources.filter(source => { - const mode = getSourceMode(source.id); - return mode !== 'all'; - }); - - if (activeFilters.length === 0) { - return 'All sources'; - } else if (activeFilters.length === 1) { - const source = activeFilters[0]; - const mode = getSourceMode(source.id); - return mode === 'show' ? `${source.label} only` : `No ${source.label}`; - } else { - return `${activeFilters.length} filters`; - } - }; - - const dropdownContent = ( -
- {sources.map((source) => { - const currentMode = getSourceMode(source.id); - - return ( -
-
- {source.icon} - {source.label} -
- -
- {[ - { mode: 'all' as FilterMode, label: 'Afficher tout', icon: '👁️' }, - { mode: 'show' as FilterMode, label: 'Seulement cette source', icon: '✅' }, - { mode: 'hide' as FilterMode, label: 'Masquer cette source', icon: '🚫' } - ].map(({ mode, label, icon }) => ( - - ))} -
-
- ); - })} - - {/* Option pour réinitialiser tous les filtres */} -
- -
-
- ); - - return ( - - ); -} - diff --git a/src/contexts/TasksContext.tsx b/src/contexts/TasksContext.tsx index f2740c6..0250180 100644 --- a/src/contexts/TasksContext.tsx +++ b/src/contexts/TasksContext.tsx @@ -70,7 +70,10 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat // Filtres TFS showTfsOnly: preferences.kanbanFilters.showTfsOnly || false, hideTfsTasks: preferences.kanbanFilters.hideTfsTasks || false, - tfsProjects: preferences.kanbanFilters.tfsProjects || [] + tfsProjects: preferences.kanbanFilters.tfsProjects || [], + // Filtres Manuel + showManualOnly: preferences.kanbanFilters.showManualOnly || false, + hideManualTasks: preferences.kanbanFilters.hideManualTasks || false }), [preferences]); // Fonction pour mettre à jour les filtres avec persistance @@ -92,7 +95,10 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat // Filtres TFS showTfsOnly: newFilters.showTfsOnly, hideTfsTasks: newFilters.hideTfsTasks, - tfsProjects: newFilters.tfsProjects + tfsProjects: newFilters.tfsProjects, + // Filtres Manuel + showManualOnly: newFilters.showManualOnly, + hideManualTasks: newFilters.hideManualTasks }; const viewPreferenceUpdates = { @@ -151,7 +157,9 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat (kanbanFilters.hideJiraTasks ? 1 : 0) + (kanbanFilters.tfsProjects?.filter(Boolean).length || 0) + (kanbanFilters.showTfsOnly ? 1 : 0) + - (kanbanFilters.hideTfsTasks ? 1 : 0); + (kanbanFilters.hideTfsTasks ? 1 : 0) + + (kanbanFilters.showManualOnly ? 1 : 0) + + (kanbanFilters.hideManualTasks ? 1 : 0); }, [kanbanFilters]); // Filtrage et tri des tâches régulières (pas les épinglées) @@ -212,6 +220,13 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat filtered = filtered.filter(task => task.source !== 'tfs'); } + // Filtres spécifiques Manuel + if (kanbanFilters.showManualOnly) { + filtered = filtered.filter(task => task.source === 'manual'); + } else if (kanbanFilters.hideManualTasks) { + filtered = filtered.filter(task => task.source !== 'manual'); + } + // Filtre par projets TFS if (kanbanFilters.tfsProjects?.length) { filtered = filtered.filter(task => diff --git a/src/lib/types.ts b/src/lib/types.ts index a4c6e0d..d073d96 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -92,6 +92,9 @@ export interface KanbanFilters { showTfsOnly?: boolean; hideTfsTasks?: boolean; tfsProjects?: string[]; + // Filtres spécifiques Manuel + showManualOnly?: boolean; + hideManualTasks?: boolean; [key: string]: string | string[] | TaskPriority[] | boolean | undefined; }