diff --git a/apps/main/src/pages/tablo-details.tsx b/apps/main/src/pages/tablo-details.tsx index 40b1064..a7d5074 100644 --- a/apps/main/src/pages/tablo-details.tsx +++ b/apps/main/src/pages/tablo-details.tsx @@ -1,15 +1,18 @@ import { cn, toast } from "@xtablo/shared"; import type { UserTablo } from "@xtablo/shared/types/tablos.types"; -import type { KanbanTask } from "@xtablo/shared-types"; +import type { Etape, KanbanTask } from "@xtablo/shared-types"; import { LoadingSpinner } from "@ui/components/LoadingSpinner"; import { CalendarIcon, + ChevronDownIcon, + ChevronRightIcon, CircleCheckIcon, EllipsisVerticalIcon, FileTextIcon, FolderIcon, KanbanIcon, LayoutDashboardIcon, + ListChecksIcon, MapIcon, MessageCircleIcon, PlusIcon, @@ -26,7 +29,7 @@ import { TabloDiscussionSection } from "../components/TabloDiscussionSection"; import { TabloEventsSection } from "../components/TabloEventsSection"; import { TabloFilesSection } from "../components/TabloFilesSection"; import { TabloTasksSection } from "../components/TabloTasksSection"; -import { useAllTasks } from "../hooks/tasks"; +import { useAllTasks, useTabloEtapes } from "../hooks/tasks"; import { useTabloFileNames } from "../hooks/tablo_data"; import { useTablosList } from "../hooks/tablos"; @@ -69,7 +72,8 @@ type TabSection = | "files" | "discussion" | "events" - | "tasks"; + | "tasks" + | "etapes"; const TABS: { id: TabSection; @@ -78,6 +82,7 @@ const TABS: { disabled?: boolean; }[] = [ { id: "overview", label: "Aperçu", icon: LayoutDashboardIcon }, + { id: "etapes", label: "Étapes", icon: ListChecksIcon }, { id: "tasks", label: "Tâches", icon: KanbanIcon }, { id: "files", label: "Fichiers", icon: FolderIcon }, { id: "discussion", label: "Discussion", icon: MessageCircleIcon }, @@ -127,6 +132,9 @@ export const TabloDetailsPage = () => { (t) => t.tablo_id === tabloId, ); + // Etapes (parent tasks) for this tablo + const { data: etapes = [] } = useTabloEtapes(tabloId); + // Files for this tablo (used in overview) const { data: filesData } = useTabloFileNames(tabloId ?? ""); const fileNames = (filesData?.fileNames ?? []).filter( @@ -449,7 +457,153 @@ export const TabloDetailsPage = () => { {activeSection === "events" && ( )} + + {activeSection === "etapes" && ( + + )} ); }; + +// ─── Etapes (Steps) section ───────────────────────────────────────────────── + +function EtapesSection({ + etapes, + tabloTasks, +}: { + etapes: Etape[]; + tabloTasks: KanbanTask[]; +}) { + const [expandedEtapes, setExpandedEtapes] = useState>( + new Set(etapes.map((e) => e.id)), + ); + + const toggleEtape = (id: string) => { + setExpandedEtapes((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const statusConfig: Record = { + todo: { label: "À faire", color: "bg-blue-100 text-blue-700 dark:bg-blue-950/30 dark:text-blue-400" }, + in_progress: { label: "En cours", color: "bg-yellow-100 text-yellow-700 dark:bg-yellow-950/30 dark:text-yellow-400" }, + in_review: { label: "Vérification", color: "bg-purple-100 text-purple-700 dark:bg-purple-950/30 dark:text-purple-400" }, + done: { label: "Terminé", color: "bg-green-100 text-green-700 dark:bg-green-950/30 dark:text-green-400" }, + }; + + if (etapes.length === 0) { + return ( +
+ +

Aucune étape

+

Les étapes permettent de structurer votre projet en grandes phases

+
+ ); + } + + return ( +
+ {etapes.map((etape, index) => { + const childTasks = tabloTasks.filter((t) => t.parent_task_id === etape.id); + const doneCount = childTasks.filter((t) => t.status === "done").length; + const totalCount = childTasks.length; + const progressPct = totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0; + const isExpanded = expandedEtapes.has(etape.id); + const status = statusConfig[etape.status] ?? statusConfig.todo; + + return ( +
+ {/* Etape header */} + + + {/* Child tasks */} + {isExpanded && childTasks.length > 0 && ( +
+ {childTasks.map((task) => ( +
+ {task.status === "done" ? ( + + ) : ( +
+ )} + + {task.title} + + {task.status && ( + + {(statusConfig[task.status] ?? statusConfig.todo).label} + + )} +
+ ))} +
+ )} + + {isExpanded && childTasks.length === 0 && ( +
+ Aucune tâche dans cette étape +
+ )} +
+ ); + })} +
+ ); +}