refactor: replace Jira and TFS filters with SourceQuickFilter in Desktop and Mobile controls

- Removed `JiraQuickFilter` and `TfsQuickFilter` components, consolidating functionality into `SourceQuickFilter`.
- Updated UI sections in `DesktopControls` and `MobileControls` to reflect the new filter structure, enhancing maintainability and reducing redundancy.
This commit is contained in:
Julien Froidefond
2025-09-26 15:05:39 +02:00
parent 6fccf20581
commit b9f801c110
5 changed files with 213 additions and 221 deletions

View File

@@ -3,8 +3,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter';
import { TfsQuickFilter } from '@/components/kanban/TfsQuickFilter';
import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter';
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
import type { KanbanFilters } from '@/lib/types';
@@ -143,14 +142,8 @@ export function DesktopControls({
{/* Section droite : Raccourcis + Bouton Nouvelle tâche */}
<div className="flex items-center justify-between lg:justify-start gap-4">
<div className="flex items-center gap-2 border-l border-[var(--border)] pl-4">
{/* Raccourcis Jira */}
<JiraQuickFilter
filters={kanbanFilters}
onFiltersChange={onFiltersChange}
/>
{/* Raccourcis TFS */}
<TfsQuickFilter
{/* Raccourcis Sources (Jira & TFS) */}
<SourceQuickFilter
filters={kanbanFilters}
onFiltersChange={onFiltersChange}
/>

View File

@@ -1,101 +0,0 @@
'use client';
import { useMemo } from 'react';
import { useTasksContext } from '@/contexts/TasksContext';
import type { KanbanFilters } from '@/lib/types';
interface JiraQuickFilterProps {
filters: KanbanFilters;
onFiltersChange: (filters: KanbanFilters) => void;
}
export function JiraQuickFilter({ filters, onFiltersChange }: JiraQuickFilterProps) {
const { regularTasks } = useTasksContext();
// Vérifier s'il y a des tâches Jira dans le système
const hasJiraTasks = useMemo(() => {
return regularTasks.some(task => task.source === 'jira');
}, [regularTasks]);
// Si pas de tâches Jira, on n'affiche rien
if (!hasJiraTasks) {
return null;
}
// Déterminer l'état actuel
const currentMode = filters.showJiraOnly ? 'show' : filters.hideJiraTasks ? 'hide' : 'all';
const handleJiraCycle = () => {
const updates: Partial<KanbanFilters> = {};
// Cycle : All -> Jira only -> No Jira -> All
switch (currentMode) {
case 'all':
// All -> Jira only
updates.showJiraOnly = true;
updates.hideJiraTasks = false;
break;
case 'show':
// Jira only -> No Jira
updates.showJiraOnly = false;
updates.hideJiraTasks = true;
break;
case 'hide':
// No Jira -> All
updates.showJiraOnly = false;
updates.hideJiraTasks = false;
break;
}
onFiltersChange({ ...filters, ...updates });
};
// Définir l'apparence selon l'état
const getButtonStyle = () => {
switch (currentMode) {
case 'show':
return 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30';
case 'hide':
return 'bg-red-500/20 text-red-400 border border-red-400/30';
default:
return 'bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)] hover:border-[var(--primary)]/50';
}
};
const getButtonContent = () => {
switch (currentMode) {
case 'show':
return { icon: '🔹', text: 'Jira only' };
case 'hide':
return { icon: '🚫', text: 'No Jira' };
default:
return { icon: '🔌', text: 'All tasks' };
}
};
const getTooltip = () => {
switch (currentMode) {
case 'all':
return 'Cliquer pour afficher seulement Jira';
case 'show':
return 'Cliquer pour masquer Jira';
case 'hide':
return 'Cliquer pour afficher tout';
default:
return 'Filtrer les tâches Jira';
}
};
const content = getButtonContent();
return (
<button
onClick={handleJiraCycle}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-mono transition-all ${getButtonStyle()}`}
title={getTooltip()}
>
<span>{content.icon}</span>
{content.text}
</button>
);
}

View File

@@ -2,8 +2,7 @@
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter';
import { TfsQuickFilter } from '@/components/kanban/TfsQuickFilter';
import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter';
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
import type { KanbanFilters } from '@/lib/types';
@@ -148,17 +147,13 @@ export function MobileControls({
</div>
</div>
{/* Section Jira & TFS */}
{/* Section Sources */}
<div>
<h3 className="text-xs font-mono text-[var(--muted-foreground)] uppercase tracking-wide mb-2">
Raccourcis
Sources
</h3>
<div className="space-y-2">
<JiraQuickFilter
filters={kanbanFilters}
onFiltersChange={onFiltersChange}
/>
<TfsQuickFilter
<SourceQuickFilter
filters={kanbanFilters}
onFiltersChange={onFiltersChange}
/>

View File

@@ -0,0 +1,206 @@
'use client';
import { useState, useMemo, useRef, useEffect } from 'react';
import { useTasksContext } from '@/contexts/TasksContext';
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);
const dropdownRef = useRef<HTMLDivElement>(null);
// 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]);
// Fermer le dropdown quand on clique ailleurs
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// 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<KanbanFilters> = {};
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 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 getMainButtonStyle = () => {
const activeFilters = sources.filter(source => {
const mode = getSourceMode(source.id);
return mode !== 'all';
});
if (activeFilters.length === 0) {
return 'bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)] hover:border-[var(--primary)]/50';
} else {
return 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30';
}
};
return (
<div className="relative" ref={dropdownRef}>
{/* Bouton principal */}
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-mono transition-all ${getMainButtonStyle()}`}
title="Filtrer par source"
>
<span>🔌</span>
{getMainButtonText()}
<svg
className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* Dropdown */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-[var(--card)] border border-[var(--border)] rounded-md shadow-lg z-50 min-w-[240px]">
<div className="p-3 space-y-3">
{sources.map((source) => {
const currentMode = getSourceMode(source.id);
return (
<div key={source.id} className="space-y-2">
<div className="flex items-center gap-2 text-sm font-mono text-[var(--muted-foreground)]">
<span>{source.icon}</span>
<span>{source.label}</span>
</div>
<div className="space-y-1 ml-6">
{[
{ 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 }) => (
<label
key={mode}
className="flex items-center gap-2 text-sm cursor-pointer hover:text-[var(--foreground)] transition-colors"
>
<input
type="radio"
name={`source-${source.id}`}
checked={currentMode === mode}
onChange={() => handleModeChange(source.id, mode)}
className="w-3 h-3 text-[var(--primary)] bg-[var(--background)] border-[var(--border)] focus:ring-[var(--primary)]/20"
/>
<span className="flex items-center gap-1">
<span>{icon}</span>
<span>{label}</span>
</span>
</label>
))}
</div>
</div>
);
})}
{/* Option pour réinitialiser tous les filtres */}
<div className="border-t border-[var(--border)] pt-2 mt-2">
<button
onClick={() => {
const updates: Partial<KanbanFilters> = {
showJiraOnly: false,
hideJiraTasks: false,
showTfsOnly: false,
hideTfsTasks: false
};
onFiltersChange({ ...filters, ...updates });
setIsOpen(false);
}}
className="w-full flex items-center gap-2 px-3 py-2 rounded-md text-sm font-mono transition-all text-left bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)] hover:border-[var(--primary)]/50 hover:text-[var(--foreground)]"
title="Réinitialiser tous les filtres de source"
>
<span>🔄</span>
<span className="flex-1">Réinitialiser tout</span>
</button>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,101 +0,0 @@
'use client';
import { useMemo } from 'react';
import { useTasksContext } from '@/contexts/TasksContext';
import type { KanbanFilters } from '@/lib/types';
interface TfsQuickFilterProps {
filters: KanbanFilters;
onFiltersChange: (filters: KanbanFilters) => void;
}
export function TfsQuickFilter({ filters, onFiltersChange }: TfsQuickFilterProps) {
const { regularTasks } = useTasksContext();
// Vérifier s'il y a des tâches TFS dans le système
const hasTfsTasks = useMemo(() => {
return regularTasks.some(task => task.source === 'tfs');
}, [regularTasks]);
// Si pas de tâches TFS, on n'affiche rien
if (!hasTfsTasks) {
return null;
}
// Déterminer l'état actuel
const currentMode = filters.showTfsOnly ? 'show' : filters.hideTfsTasks ? 'hide' : 'all';
const handleTfsCycle = () => {
const updates: Partial<KanbanFilters> = {};
// Cycle : All -> TFS only -> No TFS -> All
switch (currentMode) {
case 'all':
// All -> TFS only
updates.showTfsOnly = true;
updates.hideTfsTasks = false;
break;
case 'show':
// TFS only -> No TFS
updates.showTfsOnly = false;
updates.hideTfsTasks = true;
break;
case 'hide':
// No TFS -> All
updates.showTfsOnly = false;
updates.hideTfsTasks = false;
break;
}
onFiltersChange({ ...filters, ...updates });
};
// Définir l'apparence selon l'état
const getButtonStyle = () => {
switch (currentMode) {
case 'show':
return 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30';
case 'hide':
return 'bg-red-500/20 text-red-400 border border-red-400/30';
default:
return 'bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)] hover:border-[var(--primary)]/50';
}
};
const getButtonContent = () => {
switch (currentMode) {
case 'show':
return { icon: '🔷', text: 'TFS only' };
case 'hide':
return { icon: '🚫', text: 'No TFS' };
default:
return { icon: '📦', text: 'All tasks' };
}
};
const getTooltip = () => {
switch (currentMode) {
case 'all':
return 'Cliquer pour afficher seulement TFS';
case 'show':
return 'Cliquer pour masquer TFS';
case 'hide':
return 'Cliquer pour afficher tout';
default:
return 'Filtrer les tâches TFS';
}
};
const content = getButtonContent();
return (
<button
onClick={handleTfsCycle}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-mono transition-all ${getButtonStyle()}`}
title={getTooltip()}
>
<span>{content.icon}</span>
{content.text}
</button>
);
}