feat: integrate Jira and TFS filters into KanbanFilters
- Replaced existing Jira and TFS toggle handlers with `JiraFilters` and `TfsFilters` components for improved modularity and maintainability. - Streamlined filter management by encapsulating logic within dedicated components, enhancing readability and future extensibility.
This commit is contained in:
@@ -11,6 +11,8 @@ import { SORT_OPTIONS } from '@/lib/sort-config';
|
|||||||
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
||||||
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
||||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||||
|
import { JiraFilters } from './filters/JiraFilters';
|
||||||
|
import { TfsFilters } from './filters/TfsFilters';
|
||||||
|
|
||||||
export interface KanbanFilters {
|
export interface KanbanFilters {
|
||||||
search?: string;
|
search?: string;
|
||||||
@@ -145,126 +147,11 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
|
|||||||
setIsSortExpanded(!isSortExpanded);
|
setIsSortExpanded(!isSortExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJiraToggle = (mode: 'show' | 'hide' | 'all') => {
|
|
||||||
const updates: Partial<KanbanFilters> = {};
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case 'show':
|
|
||||||
updates.showJiraOnly = true;
|
|
||||||
updates.hideJiraTasks = false;
|
|
||||||
break;
|
|
||||||
case 'hide':
|
|
||||||
updates.showJiraOnly = false;
|
|
||||||
updates.hideJiraTasks = true;
|
|
||||||
break;
|
|
||||||
case 'all':
|
|
||||||
updates.showJiraOnly = false;
|
|
||||||
updates.hideJiraTasks = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
? currentProjects.filter(p => p !== project)
|
|
||||||
: [...currentProjects, project];
|
|
||||||
|
|
||||||
onFiltersChange({
|
|
||||||
...filters,
|
|
||||||
jiraProjects: newProjects
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleJiraTypeToggle = (type: string) => {
|
|
||||||
const currentTypes = filters.jiraTypes || [];
|
|
||||||
const newTypes = currentTypes.includes(type)
|
|
||||||
? currentTypes.filter(t => t !== type)
|
|
||||||
: [...currentTypes, type];
|
|
||||||
|
|
||||||
onFiltersChange({
|
|
||||||
...filters,
|
|
||||||
jiraTypes: newTypes
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = () => {
|
const handleClearFilters = () => {
|
||||||
onFiltersChange({});
|
onFiltersChange({});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Récupérer les projets et types Jira disponibles dans TOUTES les tâches (pas seulement les filtrées)
|
|
||||||
// regularTasks est déjà disponible depuis la ligne 39
|
|
||||||
const availableJiraProjects = useMemo(() => {
|
|
||||||
const projects = new Set<string>();
|
|
||||||
regularTasks.forEach(task => {
|
|
||||||
if (task.source === 'jira' && task.jiraProject) {
|
|
||||||
projects.add(task.jiraProject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Array.from(projects).sort();
|
|
||||||
}, [regularTasks]);
|
|
||||||
|
|
||||||
const availableJiraTypes = useMemo(() => {
|
|
||||||
const types = new Set<string>();
|
|
||||||
regularTasks.forEach(task => {
|
|
||||||
if (task.source === 'jira' && task.jiraType) {
|
|
||||||
types.add(task.jiraType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Array.from(types).sort();
|
|
||||||
}, [regularTasks]);
|
|
||||||
|
|
||||||
// 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
|
// Calculer les compteurs pour les priorités
|
||||||
const priorityCounts = useMemo(() => {
|
const priorityCounts = useMemo(() => {
|
||||||
@@ -464,164 +351,17 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filtres Jira - Ligne séparée mais intégrée */}
|
{/* Filtres Jira */}
|
||||||
{hasJiraTasks && (
|
<JiraFilters
|
||||||
<div className="border-t border-[var(--border)]/30 pt-4 mt-4">
|
filters={filters}
|
||||||
<div className="flex items-center gap-4 mb-3">
|
onFiltersChange={onFiltersChange}
|
||||||
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
|
/>
|
||||||
🔌 Jira
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{/* Toggle Jira Show/Hide - inline avec le titre */}
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Button
|
|
||||||
variant={filters.showJiraOnly ? "primary" : "ghost"}
|
|
||||||
onClick={() => handleJiraToggle('show')}
|
|
||||||
size="sm"
|
|
||||||
className="text-xs px-2 py-1 h-auto"
|
|
||||||
>
|
|
||||||
🔹 Seul
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={filters.hideJiraTasks ? "danger" : "ghost"}
|
|
||||||
onClick={() => handleJiraToggle('hide')}
|
|
||||||
size="sm"
|
|
||||||
className="text-xs px-2 py-1 h-auto"
|
|
||||||
>
|
|
||||||
🚫 Mask
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={(!filters.showJiraOnly && !filters.hideJiraTasks) ? "primary" : "ghost"}
|
|
||||||
onClick={() => handleJiraToggle('all')}
|
|
||||||
size="sm"
|
|
||||||
className="text-xs px-2 py-1 h-auto"
|
|
||||||
>
|
|
||||||
📋 All
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Projets et Types en 2 colonnes */}
|
{/* Filtres TFS */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<TfsFilters
|
||||||
{/* Projets Jira */}
|
filters={filters}
|
||||||
{availableJiraProjects.length > 0 && (
|
onFiltersChange={onFiltersChange}
|
||||||
<div>
|
/>
|
||||||
<label className="block text-xs font-medium text-[var(--muted-foreground)] mb-2">
|
|
||||||
Projets
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{availableJiraProjects.map((project) => (
|
|
||||||
<button
|
|
||||||
key={project}
|
|
||||||
onClick={() => handleJiraProjectToggle(project)}
|
|
||||||
className={`px-2 py-1 rounded border transition-all text-xs font-medium ${
|
|
||||||
filters.jiraProjects?.includes(project)
|
|
||||||
? 'border-blue-400 bg-blue-400/10 text-blue-400'
|
|
||||||
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
📋 {project}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Types Jira */}
|
|
||||||
{availableJiraTypes.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-medium text-[var(--muted-foreground)] mb-2">
|
|
||||||
Types
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{availableJiraTypes.map((type) => (
|
|
||||||
<button
|
|
||||||
key={type}
|
|
||||||
onClick={() => handleJiraTypeToggle(type)}
|
|
||||||
className={`px-2 py-1 rounded border transition-all text-xs font-medium ${
|
|
||||||
filters.jiraTypes?.includes(type)
|
|
||||||
? 'border-purple-400 bg-purple-400/10 text-purple-400'
|
|
||||||
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{type === 'Feature' && '✨ '}
|
|
||||||
{type === 'Story' && '📖 '}
|
|
||||||
{type === 'Task' && '📝 '}
|
|
||||||
{type === 'Bug' && '🐛 '}
|
|
||||||
{type === 'Support' && '🛠️ '}
|
|
||||||
{type === 'Enabler' && '🔧 '}
|
|
||||||
{type}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</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 */}
|
{/* Visibilité des colonnes */}
|
||||||
<div className="col-span-full border-t border-[var(--border)]/50 pt-6 mt-4">
|
<div className="col-span-full border-t border-[var(--border)]/50 pt-6 mt-4">
|
||||||
|
|||||||
185
src/components/kanban/filters/JiraFilters.tsx
Normal file
185
src/components/kanban/filters/JiraFilters.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { useTasksContext } from '@/contexts/TasksContext';
|
||||||
|
import { KanbanFilters } from '../KanbanFilters';
|
||||||
|
|
||||||
|
interface JiraFiltersProps {
|
||||||
|
filters: KanbanFilters;
|
||||||
|
onFiltersChange: (filters: KanbanFilters) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
|
||||||
|
const { regularTasks } = useTasksContext();
|
||||||
|
|
||||||
|
// 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 et types Jira disponibles dans TOUTES les tâches
|
||||||
|
const availableJiraProjects = useMemo(() => {
|
||||||
|
const projects = new Set<string>();
|
||||||
|
regularTasks.forEach(task => {
|
||||||
|
if (task.source === 'jira' && task.jiraProject) {
|
||||||
|
projects.add(task.jiraProject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(projects).sort();
|
||||||
|
}, [regularTasks]);
|
||||||
|
|
||||||
|
const availableJiraTypes = useMemo(() => {
|
||||||
|
const types = new Set<string>();
|
||||||
|
regularTasks.forEach(task => {
|
||||||
|
if (task.source === 'jira' && task.jiraType) {
|
||||||
|
types.add(task.jiraType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(types).sort();
|
||||||
|
}, [regularTasks]);
|
||||||
|
|
||||||
|
const handleJiraToggle = (mode: 'show' | 'hide' | 'all') => {
|
||||||
|
const updates: Partial<KanbanFilters> = {};
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'show':
|
||||||
|
updates.showJiraOnly = true;
|
||||||
|
updates.hideJiraTasks = false;
|
||||||
|
break;
|
||||||
|
case 'hide':
|
||||||
|
updates.showJiraOnly = false;
|
||||||
|
updates.hideJiraTasks = true;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
updates.showJiraOnly = false;
|
||||||
|
updates.hideJiraTasks = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFiltersChange({ ...filters, ...updates });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJiraProjectToggle = (project: string) => {
|
||||||
|
const currentProjects = filters.jiraProjects || [];
|
||||||
|
const newProjects = currentProjects.includes(project)
|
||||||
|
? currentProjects.filter(p => p !== project)
|
||||||
|
: [...currentProjects, project];
|
||||||
|
|
||||||
|
onFiltersChange({
|
||||||
|
...filters,
|
||||||
|
jiraProjects: newProjects
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJiraTypeToggle = (type: string) => {
|
||||||
|
const currentTypes = filters.jiraTypes || [];
|
||||||
|
const newTypes = currentTypes.includes(type)
|
||||||
|
? currentTypes.filter(t => t !== type)
|
||||||
|
: [...currentTypes, type];
|
||||||
|
|
||||||
|
onFiltersChange({
|
||||||
|
...filters,
|
||||||
|
jiraTypes: newTypes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Si pas de tâches Jira, on n'affiche rien
|
||||||
|
if (!hasJiraTasks) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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">
|
||||||
|
🔌 Jira
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Toggle Jira Show/Hide - inline avec le titre */}
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button
|
||||||
|
variant={filters.showJiraOnly ? "primary" : "ghost"}
|
||||||
|
onClick={() => handleJiraToggle('show')}
|
||||||
|
size="sm"
|
||||||
|
className="text-xs px-2 py-1 h-auto"
|
||||||
|
>
|
||||||
|
🔹 Seul
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={filters.hideJiraTasks ? "danger" : "ghost"}
|
||||||
|
onClick={() => handleJiraToggle('hide')}
|
||||||
|
size="sm"
|
||||||
|
className="text-xs px-2 py-1 h-auto"
|
||||||
|
>
|
||||||
|
🚫 Mask
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={(!filters.showJiraOnly && !filters.hideJiraTasks) ? "primary" : "ghost"}
|
||||||
|
onClick={() => handleJiraToggle('all')}
|
||||||
|
size="sm"
|
||||||
|
className="text-xs px-2 py-1 h-auto"
|
||||||
|
>
|
||||||
|
📋 All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Projets et Types en 2 colonnes */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
{/* Projets Jira */}
|
||||||
|
{availableJiraProjects.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">
|
||||||
|
{availableJiraProjects.map((project) => (
|
||||||
|
<button
|
||||||
|
key={project}
|
||||||
|
onClick={() => handleJiraProjectToggle(project)}
|
||||||
|
className={`px-2 py-1 rounded border transition-all text-xs font-medium ${
|
||||||
|
filters.jiraProjects?.includes(project)
|
||||||
|
? 'border-blue-400 bg-blue-400/10 text-blue-400'
|
||||||
|
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
📋 {project}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Types Jira */}
|
||||||
|
{availableJiraTypes.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-[var(--muted-foreground)] mb-2">
|
||||||
|
Types
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{availableJiraTypes.map((type) => (
|
||||||
|
<button
|
||||||
|
key={type}
|
||||||
|
onClick={() => handleJiraTypeToggle(type)}
|
||||||
|
className={`px-2 py-1 rounded border transition-all text-xs font-medium ${
|
||||||
|
filters.jiraTypes?.includes(type)
|
||||||
|
? 'border-purple-400 bg-purple-400/10 text-purple-400'
|
||||||
|
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{type === 'Feature' && '✨ '}
|
||||||
|
{type === 'Story' && '📖 '}
|
||||||
|
{type === 'Task' && '📝 '}
|
||||||
|
{type === 'Bug' && '🐛 '}
|
||||||
|
{type === 'Support' && '🛠️ '}
|
||||||
|
{type === 'Enabler' && '🔧 '}
|
||||||
|
{type}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
130
src/components/kanban/filters/TfsFilters.tsx
Normal file
130
src/components/kanban/filters/TfsFilters.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { useTasksContext } from '@/contexts/TasksContext';
|
||||||
|
import { KanbanFilters } from '../KanbanFilters';
|
||||||
|
|
||||||
|
interface TfsFiltersProps {
|
||||||
|
filters: KanbanFilters;
|
||||||
|
onFiltersChange: (filters: KanbanFilters) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
|
||||||
|
const { regularTasks } = useTasksContext();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
|
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 handleTfsProjectToggle = (project: string) => {
|
||||||
|
const currentProjects = filters.tfsProjects || [];
|
||||||
|
const newProjects = currentProjects.includes(project)
|
||||||
|
? currentProjects.filter(p => p !== project)
|
||||||
|
: [...currentProjects, project];
|
||||||
|
|
||||||
|
onFiltersChange({
|
||||||
|
...filters,
|
||||||
|
tfsProjects: newProjects
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Si pas de tâches TFS, on n'affiche rien
|
||||||
|
if (!hasTfsTasks) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user