diff --git a/apps/main/src/components/gantt/GanttChart.tsx b/apps/main/src/components/gantt/GanttChart.tsx index f778e4a..dee1d11 100644 --- a/apps/main/src/components/gantt/GanttChart.tsx +++ b/apps/main/src/components/gantt/GanttChart.tsx @@ -135,7 +135,13 @@ function formatDateRange(start: Date, end: Date): string { return `${startStr} - ${endStr}`; } -const CARD_HEIGHT = 130; +type ViewMode = "weekly" | "biweekly"; + +const VIEW_CONFIG = { + weekly: { days: 7, label: "Semaine", cardHeight: 130, stepWeeks: 1 }, + biweekly: { days: 14, label: "2 semaines", cardHeight: 100, stepWeeks: 2 }, +} as const; + const CARD_GAP = 10; const CARD_TOP_OFFSET = 20; @@ -143,9 +149,14 @@ const CARD_TOP_OFFSET = 20; export function GanttChart({ tasks, isLoading }: GanttChartProps) { const [weekOffset, setWeekOffset] = useState(0); + const [viewMode, setViewMode] = useState("weekly"); const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(992); + const config = VIEW_CONFIG[viewMode]; + const numDays = config.days; + const cardHeight = config.cardHeight; + // Measure container width useEffect(() => { const el = containerRef.current; @@ -159,37 +170,37 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { return () => observer.disconnect(); }, []); - const colWidth = containerWidth / 7; + const colWidth = containerWidth / numDays; - // Compute week days + // Compute days const today = useMemo(() => { const d = new Date(); d.setHours(0, 0, 0, 0); return d; }, []); - const weekStart = useMemo(() => addDays(getMonday(today), weekOffset * 7), [today, weekOffset]); - const weekEnd = useMemo(() => addDays(weekStart, 6), [weekStart]); + const periodStart = useMemo(() => addDays(getMonday(today), weekOffset * 7), [today, weekOffset]); + const periodEnd = useMemo(() => addDays(periodStart, numDays - 1), [periodStart, numDays]); const days = useMemo( - () => Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)), - [weekStart] + () => Array.from({ length: numDays }, (_, i) => addDays(periodStart, i)), + [periodStart, numDays] ); - // Filter tasks with due_date in this week + // Filter tasks with due_date in this period const visibleTasks = useMemo(() => { - const start = weekStart.getTime(); - const end = addDays(weekStart, 7).getTime(); + const start = periodStart.getTime(); + const end = addDays(periodStart, numDays).getTime(); return tasks.filter((t) => { if (!t.due_date) return false; const d = new Date(t.due_date).getTime(); return d >= start && d < end; }); - }, [tasks, weekStart]); + }, [tasks, periodStart, numDays]); // Position tasks: group by day column, stack vertically const positionedTasks = useMemo(() => { - const columnSlots: number[][] = Array.from({ length: 7 }, () => []); + const columnSlots: number[][] = Array.from({ length: numDays }, () => []); return visibleTasks .map((task) => { @@ -207,9 +218,9 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { task, dayIndex, row, - left: dayIndex * colWidth + 8, - top: CARD_TOP_OFFSET + row * (CARD_HEIGHT + CARD_GAP), - width: colWidth - 16, + left: dayIndex * colWidth + 4, + top: CARD_TOP_OFFSET + row * (cardHeight + CARD_GAP), + width: colWidth - 8, }; }) .filter(Boolean) as Array<{ @@ -220,16 +231,21 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { top: number; width: number; }>; - }, [visibleTasks, days, colWidth]); + }, [visibleTasks, days, colWidth, numDays, cardHeight]); // Compute chart height const maxRow = positionedTasks.reduce((max, pt) => Math.max(max, pt.row), 0); - const chartHeight = Math.max(400, (maxRow + 1) * (CARD_HEIGHT + CARD_GAP) + CARD_TOP_OFFSET + 20); + const chartHeight = Math.max(400, (maxRow + 1) * (cardHeight + CARD_GAP) + CARD_TOP_OFFSET + 20); // Today indicator position const todayIndex = days.findIndex((d) => isSameDay(d, today)); const todayInRange = todayIndex >= 0; + const handleViewModeChange = (mode: ViewMode) => { + setViewMode(mode); + setWeekOffset(0); + }; + if (isLoading) { return (
@@ -247,20 +263,20 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { variant="outline" size="icon" className="rounded-lg" - onClick={() => setWeekOffset((w) => w - 1)} + onClick={() => setWeekOffset((w) => w - config.stepWeeks)} >
- {formatDateRange(weekStart, weekEnd)} + {formatDateRange(periodStart, periodEnd)}
@@ -270,11 +286,17 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { + handleViewModeChange("weekly")}> + Semaine + + handleViewModeChange("biweekly")}> + 2 semaines + setWeekOffset(0)}> Aller à aujourd'hui @@ -319,7 +341,7 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { {days.map((_, i) => (
))} @@ -345,11 +367,14 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { STATUS_TEXT_COLORS[pt.task.status ?? "todo"] ?? STATUS_TEXT_COLORS.todo; const TabloIcon = pt.task.tablos ? getTabloIcon(pt.task.tablos.color) : null; + const isCompact = viewMode === "biweekly"; + return (
{/* Status badge */} -
+
- - {status.label} - + {!isCompact && ( + + {status.label} + + )}
{/* Title */} -

+

{pt.task.title}

@@ -378,12 +410,12 @@ export function GanttChart({ tasks, isLoading }: GanttChartProps) { {new Date(pt.task.due_date!).toLocaleDateString("fr-FR", { weekday: "short", day: "numeric", - month: "short", + month: isCompact ? undefined : "short", })}

- {/* Tablo badge */} - {pt.task.tablos && TabloIcon && ( + {/* Tablo badge — hidden in compact mode */} + {!isCompact && pt.task.tablos && TabloIcon && (

- Aucune tâche avec échéance cette semaine + Aucune tâche avec échéance sur cette période

)}