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 { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter';
|
import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter';
|
||||||
import { TfsQuickFilter } from '@/components/kanban/TfsQuickFilter';
|
|
||||||
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
|
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
|
||||||
import type { KanbanFilters } from '@/lib/types';
|
import type { KanbanFilters } from '@/lib/types';
|
||||||
|
|
||||||
@@ -143,14 +142,8 @@ export function DesktopControls({
|
|||||||
{/* Section droite : Raccourcis + Bouton Nouvelle tâche */}
|
{/* Section droite : Raccourcis + Bouton Nouvelle tâche */}
|
||||||
<div className="flex items-center justify-between lg:justify-start gap-4">
|
<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">
|
<div className="flex items-center gap-2 border-l border-[var(--border)] pl-4">
|
||||||
{/* Raccourcis Jira */}
|
{/* Raccourcis Sources (Jira & TFS) */}
|
||||||
<JiraQuickFilter
|
<SourceQuickFilter
|
||||||
filters={kanbanFilters}
|
|
||||||
onFiltersChange={onFiltersChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Raccourcis TFS */}
|
|
||||||
<TfsQuickFilter
|
|
||||||
filters={kanbanFilters}
|
filters={kanbanFilters}
|
||||||
onFiltersChange={onFiltersChange}
|
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 { useState } from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter';
|
import { SourceQuickFilter } from '@/components/kanban/SourceQuickFilter';
|
||||||
import { TfsQuickFilter } from '@/components/kanban/TfsQuickFilter';
|
|
||||||
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
|
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
|
||||||
import type { KanbanFilters } from '@/lib/types';
|
import type { KanbanFilters } from '@/lib/types';
|
||||||
|
|
||||||
@@ -148,17 +147,13 @@ export function MobileControls({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section Jira & TFS */}
|
{/* Section Sources */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xs font-mono text-[var(--muted-foreground)] uppercase tracking-wide mb-2">
|
<h3 className="text-xs font-mono text-[var(--muted-foreground)] uppercase tracking-wide mb-2">
|
||||||
Raccourcis
|
Sources
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<JiraQuickFilter
|
<SourceQuickFilter
|
||||||
filters={kanbanFilters}
|
|
||||||
onFiltersChange={onFiltersChange}
|
|
||||||
/>
|
|
||||||
<TfsQuickFilter
|
|
||||||
filters={kanbanFilters}
|
filters={kanbanFilters}
|
||||||
onFiltersChange={onFiltersChange}
|
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