feat: add swimlane mode selection to KanbanFilters and BoardContainer
- Introduced `swimlanesMode` in `KanbanFilters` to toggle between 'tags' and 'priority' swimlanes. - Updated `KanbanBoardContainer` to conditionally render `PrioritySwimlanesBoard` based on the selected mode. - Enhanced UI to include dropdown for swimlane mode selection, improving user experience in task organization. - Adjusted `TasksContext` to persist swimlane mode preferences, ensuring consistent behavior across sessions.
This commit is contained in:
@@ -16,6 +16,7 @@ export interface KanbanFilters {
|
||||
showCompleted?: boolean;
|
||||
compactView?: boolean;
|
||||
swimlanesByTags?: boolean;
|
||||
swimlanesMode?: 'tags' | 'priority'; // Mode des swimlanes
|
||||
pinnedTag?: string; // Tag pour les objectifs principaux
|
||||
sortBy?: string; // Clé de l'option de tri sélectionnée
|
||||
}
|
||||
@@ -29,23 +30,28 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
||||
const { tags: availableTags, regularTasks } = useTasksContext();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [isSortExpanded, setIsSortExpanded] = useState(false);
|
||||
const [isSwimlaneModeExpanded, setIsSwimlaneModeExpanded] = useState(false);
|
||||
const sortDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const swimlaneModeDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const sortButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
|
||||
|
||||
// Fermer le dropdown de tri en cliquant à l'extérieur
|
||||
// Fermer les dropdowns en cliquant à l'extérieur
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (sortDropdownRef.current && !sortDropdownRef.current.contains(event.target as Node)) {
|
||||
setIsSortExpanded(false);
|
||||
}
|
||||
if (swimlaneModeDropdownRef.current && !swimlaneModeDropdownRef.current.contains(event.target as Node)) {
|
||||
setIsSwimlaneModeExpanded(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSortExpanded) {
|
||||
if (isSortExpanded || isSwimlaneModeExpanded) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
}, [isSortExpanded]);
|
||||
}, [isSortExpanded, isSwimlaneModeExpanded]);
|
||||
|
||||
const handleSearchChange = (search: string) => {
|
||||
onFiltersChange({ ...filters, search: search || undefined });
|
||||
@@ -89,6 +95,25 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
||||
});
|
||||
};
|
||||
|
||||
const handleSwimlaneModeChange = (mode: 'tags' | 'priority') => {
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
swimlanesByTags: true,
|
||||
swimlanesMode: mode
|
||||
});
|
||||
setIsSwimlaneModeExpanded(false);
|
||||
};
|
||||
|
||||
const handleSwimlaneModeToggle = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const button = event.currentTarget;
|
||||
const rect = button.getBoundingClientRect();
|
||||
setDropdownPosition({
|
||||
top: rect.bottom + window.scrollY + 4,
|
||||
left: rect.left + window.scrollX
|
||||
});
|
||||
setIsSwimlaneModeExpanded(!isSwimlaneModeExpanded);
|
||||
};
|
||||
|
||||
const handleSortChange = (sortKey: string) => {
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
@@ -162,27 +187,52 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bouton swimlanes par tags */}
|
||||
<Button
|
||||
variant={filters.swimlanesByTags ? "primary" : "ghost"}
|
||||
onClick={handleSwimlanesToggle}
|
||||
className="flex items-center gap-2"
|
||||
title={filters.swimlanesByTags ? "Vue normale" : "Lignes par tags"}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
{/* Menu swimlanes */}
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant={filters.swimlanesByTags ? "primary" : "ghost"}
|
||||
onClick={handleSwimlanesToggle}
|
||||
className="flex items-center gap-2"
|
||||
title="Mode d'affichage"
|
||||
>
|
||||
{filters.swimlanesByTags ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||
)}
|
||||
</svg>
|
||||
{filters.swimlanesByTags ? 'Normal' : 'Swimlanes'}
|
||||
</Button>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{filters.swimlanesByTags ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||
)}
|
||||
</svg>
|
||||
{!filters.swimlanesByTags
|
||||
? 'Normal'
|
||||
: filters.swimlanesMode === 'priority'
|
||||
? 'Par priorité'
|
||||
: 'Par tags'
|
||||
}
|
||||
</Button>
|
||||
|
||||
{/* Bouton pour changer le mode des swimlanes */}
|
||||
{filters.swimlanesByTags && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleSwimlaneModeToggle}
|
||||
className="flex items-center gap-1 px-2"
|
||||
>
|
||||
<svg
|
||||
className={`w-3 h-3 transition-transform ${isSwimlaneModeExpanded ? '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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bouton vue compacte */}
|
||||
<Button
|
||||
@@ -399,6 +449,36 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{/* Dropdown des modes swimlanes rendu via portail pour éviter les problèmes de z-index */}
|
||||
{isSwimlaneModeExpanded && typeof window !== 'undefined' && createPortal(
|
||||
<div
|
||||
ref={swimlaneModeDropdownRef}
|
||||
className="fixed bg-slate-800 border border-slate-700 rounded-lg shadow-xl z-[9999] min-w-[140px]"
|
||||
style={{
|
||||
top: dropdownPosition.top,
|
||||
left: dropdownPosition.left,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => handleSwimlaneModeChange('tags')}
|
||||
className={`w-full px-3 py-2 text-left text-xs hover:bg-slate-700 transition-colors flex items-center gap-2 first:rounded-t-lg ${
|
||||
(!filters.swimlanesMode || filters.swimlanesMode === 'tags') ? 'bg-slate-700 text-cyan-400' : 'text-slate-300'
|
||||
}`}
|
||||
>
|
||||
🏷️ Par tags
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSwimlaneModeChange('priority')}
|
||||
className={`w-full px-3 py-2 text-left text-xs hover:bg-slate-700 transition-colors flex items-center gap-2 last:rounded-b-lg ${
|
||||
filters.swimlanesMode === 'priority' ? 'bg-slate-700 text-cyan-400' : 'text-slate-300'
|
||||
}`}
|
||||
>
|
||||
🎯 Par priorité
|
||||
</button>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user