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:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
206
src/components/kanban/SourceQuickFilter.tsx
Normal file
206
src/components/kanban/SourceQuickFilter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user