import { useEffect, useState } from "react"; import { Gantt } from "@svar-ui/react-gantt"; import "@svar-ui/react-gantt/style.css"; import { Link } from "react-router-dom"; import type { DemandPlanningRollupDto, GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared"; import { useAuth } from "../../auth/AuthProvider"; import { ApiError, api } from "../../lib/api"; import { useTheme } from "../../theme/ThemeProvider"; function formatDate(value: string | null) { if (!value) { return "Unscheduled"; } return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", }).format(new Date(value)); } export function GanttPage() { const { token } = useAuth(); const { mode } = useTheme(); const [timeline, setTimeline] = useState(null); const [planningRollup, setPlanningRollup] = useState(null); const [status, setStatus] = useState("Loading live planning timeline..."); useEffect(() => { if (!token) { return; } Promise.all([api.getPlanningTimeline(token), api.getDemandPlanningRollup(token)]) .then(([data, rollup]) => { setTimeline(data); setPlanningRollup(rollup); setStatus("Planning timeline loaded."); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load planning timeline."; setStatus(message); }); }, [token]); const tasks = timeline?.tasks ?? []; const links = timeline?.links ?? []; const summary = timeline?.summary; const exceptions = timeline?.exceptions ?? []; const ganttCellHeight = 44; const ganttScaleHeight = 56; const ganttHeight = Math.max(420, tasks.length * ganttCellHeight + ganttScaleHeight); return (

Planning

Live Project + Manufacturing Gantt

The planning surface now reads directly from active projects and open work orders so schedule pressure, due-date risk, and standalone manufacturing load are visible in one place.

Timeline Status
{status}

Active Projects

{summary?.activeProjects ?? 0}

At Risk

{summary?.atRiskProjects ?? 0}

Overdue Projects

{summary?.overdueProjects ?? 0}

Active Work Orders

{summary?.activeWorkOrders ?? 0}

Overdue Work

{summary?.overdueWorkOrders ?? 0}

Unscheduled Work

{summary?.unscheduledWorkOrders ?? 0}

Shortage Items

{planningRollup?.summary.uncoveredItemCount ?? 0}

Build / Buy

{planningRollup ? `${planningRollup.summary.totalBuildQuantity} / ${planningRollup.summary.totalPurchaseQuantity}` : "0 / 0"}

Schedule Window

{summary ? `${formatDate(summary.horizonStart)} through ${formatDate(summary.horizonEnd)}` : "Waiting for live schedule data."}

{tasks.length} schedule rows
({ ...task, start: new Date(task.start), end: new Date(task.end), parent: task.parentId ?? undefined, }))} links={links} cellHeight={ganttCellHeight} scaleHeight={ganttScaleHeight} />
); }