feat: add TFS filters and integration

- Introduced TFS filtering capabilities in `KanbanFilters` with options to show/hide TFS tasks and filter by TFS projects.
- Integrated `TfsQuickFilter` component into `KanbanPageClient` and `MobileControls` for enhanced task management.
- Updated `TasksContext` to support new TFS filter states and ensure proper task filtering based on TFS criteria.
- Enhanced type definitions in `types.ts` to accommodate new TFS filter properties.
This commit is contained in:
Julien Froidefond
2025-09-23 11:07:24 +02:00
parent f9c92f9efd
commit db8ff88a4c
6 changed files with 282 additions and 9 deletions

View File

@@ -10,6 +10,7 @@ import { CreateTaskData } from '@/clients/tasks-client';
import { CreateTaskForm } from '@/components/forms/CreateTaskForm';
import { Button } from '@/components/ui/Button';
import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter';
import { TfsQuickFilter } from '@/components/kanban/TfsQuickFilter';
import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
import { MobileControls } from '@/components/kanban/MobileControls';
import { useIsMobile } from '@/hooks/useIsMobile';
@@ -119,6 +120,12 @@ function KanbanPageContent() {
onFiltersChange={setKanbanFilters}
/>
{/* Raccourcis TFS */}
<TfsQuickFilter
filters={kanbanFilters}
onFiltersChange={setKanbanFilters}
/>
<button
onClick={handleToggleCompactView}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-mono transition-all ${

View File

@@ -27,6 +27,10 @@ export interface KanbanFilters {
hideJiraTasks?: boolean; // Masquer toutes les tâches Jira
jiraProjects?: string[]; // Filtrer par projet Jira
jiraTypes?: string[]; // Filtrer par type Jira (Story, Task, Bug, etc.)
// Filtres spécifiques TFS
showTfsOnly?: boolean; // Afficher seulement les tâches TFS
hideTfsTasks?: boolean; // Masquer toutes les tâches TFS
tfsProjects?: string[]; // Filtrer par projet TFS
}
interface KanbanFiltersProps {
@@ -162,6 +166,27 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
onFiltersChange({ ...filters, ...updates });
};
const handleTfsToggle = (mode: 'show' | 'hide' | 'all') => {
const updates: Partial<KanbanFilters> = {};
switch (mode) {
case 'show':
updates.showTfsOnly = true;
updates.hideTfsTasks = false;
break;
case 'hide':
updates.showTfsOnly = false;
updates.hideTfsTasks = true;
break;
case 'all':
updates.showTfsOnly = false;
updates.hideTfsTasks = false;
break;
}
onFiltersChange({ ...filters, ...updates });
};
const handleJiraProjectToggle = (project: string) => {
const currentProjects = filters.jiraProjects || [];
const newProjects = currentProjects.includes(project)
@@ -186,6 +211,18 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
});
};
const handleTfsProjectToggle = (project: string) => {
const currentProjects = filters.tfsProjects || [];
const newProjects = currentProjects.includes(project)
? currentProjects.filter(p => p !== project)
: [...currentProjects, project];
onFiltersChange({
...filters,
tfsProjects: newProjects
});
};
const handleClearFilters = () => {
onFiltersChange({});
};
@@ -215,6 +252,20 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
// Vérifier s'il y a des tâches Jira dans le système (même masquées)
const hasJiraTasks = regularTasks.some(task => task.source === 'jira');
// Récupérer les projets TFS disponibles dans TOUTES les tâches
const availableTfsProjects = useMemo(() => {
const projects = new Set<string>();
regularTasks.forEach(task => {
if (task.source === 'tfs' && task.tfsProject) {
projects.add(task.tfsProject);
}
});
return Array.from(projects).sort();
}, [regularTasks]);
// Vérifier s'il y a des tâches TFS dans le système (même masquées)
const hasTfsTasks = regularTasks.some(task => task.source === 'tfs');
// Calculer les compteurs pour les priorités
const priorityCounts = useMemo(() => {
const counts: Record<string, number> = {};
@@ -509,6 +560,69 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
</div>
)}
{/* Filtres TFS - Ligne séparée mais intégrée */}
{hasTfsTasks && (
<div className="border-t border-[var(--border)]/30 pt-4 mt-4">
<div className="flex items-center gap-4 mb-3">
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
📦 TFS
</label>
{/* Toggle TFS Show/Hide - inline avec le titre */}
<div className="flex gap-1">
<Button
variant={filters.showTfsOnly ? "primary" : "ghost"}
onClick={() => handleTfsToggle('show')}
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🔷 Seul
</Button>
<Button
variant={filters.hideTfsTasks ? "danger" : "ghost"}
onClick={() => handleTfsToggle('hide')}
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🚫 Mask
</Button>
<Button
variant={(!filters.showTfsOnly && !filters.hideTfsTasks) ? "primary" : "ghost"}
onClick={() => handleTfsToggle('all')}
size="sm"
className="text-xs px-2 py-1 h-auto"
>
📋 All
</Button>
</div>
</div>
{/* Projets TFS */}
{availableTfsProjects.length > 0 && (
<div>
<label className="block text-xs font-medium text-[var(--muted-foreground)] mb-2">
Projets
</label>
<div className="flex flex-wrap gap-1">
{availableTfsProjects.map((project) => (
<button
key={project}
onClick={() => handleTfsProjectToggle(project)}
className={`px-2 py-1 rounded border transition-all text-xs font-medium ${
filters.tfsProjects?.includes(project)
? 'border-orange-400 bg-orange-400/10 text-orange-400'
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
}`}
>
📦 {project}
</button>
))}
</div>
</div>
)}
</div>
)}
{/* Visibilité des colonnes */}
<div className="col-span-full border-t border-[var(--border)]/50 pt-6 mt-4">
<ColumnVisibilityToggle
@@ -561,6 +675,21 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
Types Jira: <span className="text-purple-400">{filters.jiraTypes?.filter(Boolean).join(', ')}</span>
</div>
)}
{filters.showTfsOnly && (
<div className="text-[var(--muted-foreground)]">
Affichage: <span className="text-orange-400">TFS seulement</span>
</div>
)}
{filters.hideTfsTasks && (
<div className="text-[var(--muted-foreground)]">
Affichage: <span className="text-red-400">Masquer TFS</span>
</div>
)}
{(filters.tfsProjects?.filter(Boolean).length || 0) > 0 && (
<div className="text-[var(--muted-foreground)]">
Projets TFS: <span className="text-orange-400">{filters.tfsProjects?.filter(Boolean).join(', ')}</span>
</div>
)}
</div>
</div>
)}

View File

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

View File

@@ -0,0 +1,101 @@
'use client';
import { useMemo } from 'react';
import { useTasksContext } from '@/contexts/TasksContext';
import { KanbanFilters } from './KanbanFilters';
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>
);
}

View File

@@ -64,7 +64,11 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
showJiraOnly: preferences.kanbanFilters.showJiraOnly || false,
hideJiraTasks: preferences.kanbanFilters.hideJiraTasks || false,
jiraProjects: preferences.kanbanFilters.jiraProjects || [],
jiraTypes: preferences.kanbanFilters.jiraTypes || []
jiraTypes: preferences.kanbanFilters.jiraTypes || [],
// Filtres TFS
showTfsOnly: preferences.kanbanFilters.showTfsOnly || false,
hideTfsTasks: preferences.kanbanFilters.hideTfsTasks || false,
tfsProjects: preferences.kanbanFilters.tfsProjects || []
}), [preferences]);
// Fonction pour mettre à jour les filtres avec persistance
@@ -80,7 +84,11 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
showJiraOnly: newFilters.showJiraOnly,
hideJiraTasks: newFilters.hideJiraTasks,
jiraProjects: newFilters.jiraProjects,
jiraTypes: newFilters.jiraTypes
jiraTypes: newFilters.jiraTypes,
// Filtres TFS
showTfsOnly: newFilters.showTfsOnly,
hideTfsTasks: newFilters.hideTfsTasks,
tfsProjects: newFilters.tfsProjects
};
const viewPreferenceUpdates = {
@@ -133,7 +141,10 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
(kanbanFilters.jiraProjects?.filter(Boolean).length || 0) +
(kanbanFilters.jiraTypes?.filter(Boolean).length || 0) +
(kanbanFilters.showJiraOnly ? 1 : 0) +
(kanbanFilters.hideJiraTasks ? 1 : 0);
(kanbanFilters.hideJiraTasks ? 1 : 0) +
(kanbanFilters.tfsProjects?.filter(Boolean).length || 0) +
(kanbanFilters.showTfsOnly ? 1 : 0) +
(kanbanFilters.hideTfsTasks ? 1 : 0);
}, [kanbanFilters]);
// Filtrage et tri des tâches régulières (pas les épinglées)
@@ -187,6 +198,20 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
);
}
// Filtres spécifiques TFS
if (kanbanFilters.showTfsOnly) {
filtered = filtered.filter(task => task.source === 'tfs');
} else if (kanbanFilters.hideTfsTasks) {
filtered = filtered.filter(task => task.source !== 'tfs');
}
// Filtre par projets TFS
if (kanbanFilters.tfsProjects?.length) {
filtered = filtered.filter(task =>
task.source !== 'tfs' || kanbanFilters.tfsProjects!.includes(task.tfsProject || '')
);
}
// Tri des tâches
if (kanbanFilters.sortBy) {
const sortOption = getSortOption(kanbanFilters.sortBy);

View File

@@ -76,6 +76,10 @@ export interface KanbanFilters {
hideJiraTasks?: boolean;
jiraProjects?: string[];
jiraTypes?: string[];
// Filtres spécifiques TFS
showTfsOnly?: boolean;
hideTfsTasks?: boolean;
tfsProjects?: string[];
[key: string]: string | string[] | TaskPriority[] | boolean | undefined;
}