no gantt
This commit is contained in:
@@ -17,7 +17,7 @@ const links = [
|
||||
{ to: "/shipping/shipments", label: "Shipments", icon: <ShipmentIcon /> },
|
||||
{ to: "/projects", label: "Projects", icon: <ProjectsIcon /> },
|
||||
{ to: "/manufacturing/work-orders", label: "Manufacturing", icon: <ManufacturingIcon /> },
|
||||
{ to: "/planning/gantt", label: "Gantt", icon: <GanttIcon /> },
|
||||
{ to: "/planning/workbench", label: "Workbench", icon: <WorkbenchIcon /> },
|
||||
];
|
||||
|
||||
function NavIcon({ children }: { children: ReactNode }) {
|
||||
@@ -146,7 +146,7 @@ function ShipmentIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function GanttIcon() {
|
||||
function WorkbenchIcon() {
|
||||
return (
|
||||
<NavIcon>
|
||||
<path d="M4 6h5" />
|
||||
|
||||
@@ -101,8 +101,8 @@ const ShipmentDetailPage = React.lazy(() =>
|
||||
const ShipmentFormPage = React.lazy(() =>
|
||||
import("./modules/shipping/ShipmentFormPage").then((module) => ({ default: module.ShipmentFormPage }))
|
||||
);
|
||||
const GanttPage = React.lazy(() =>
|
||||
import("./modules/gantt/GanttPage").then((module) => ({ default: module.GanttPage }))
|
||||
const WorkbenchPage = React.lazy(() =>
|
||||
import("./modules/workbench/WorkbenchPage").then((module) => ({ default: module.WorkbenchPage }))
|
||||
);
|
||||
const LandingPage = React.lazy(() =>
|
||||
import("./modules/landing/LandingPage").then((module) => ({ default: module.LandingPage }))
|
||||
@@ -258,7 +258,10 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
element: <ProtectedRoute requiredPermissions={[permissions.ganttRead]} />,
|
||||
children: [{ path: "/planning/gantt", element: lazyElement(<GanttPage />) }],
|
||||
children: [
|
||||
{ path: "/planning/workbench", element: lazyElement(<WorkbenchPage />) },
|
||||
{ path: "/planning/gantt", element: <Navigate to="/planning/workbench" replace /> },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -761,7 +761,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) {
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing Routing</p>
|
||||
<h4 className="mt-2 text-lg font-bold text-text">Station and time template</h4>
|
||||
<p className="mt-2 text-sm text-muted">These operations are copied automatically into work orders and drive gantt scheduling without manual planner task entry.</p>
|
||||
<p className="mt-2 text-sm text-muted">These operations are copied automatically into work orders and feed the planning workbench without manual planner task entry.</p>
|
||||
</div>
|
||||
<button type="button" onClick={addOperation} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Add operation
|
||||
|
||||
@@ -188,8 +188,8 @@ export function ProjectDetailPage() {
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Actionable Cockpit</p>
|
||||
<p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p>
|
||||
</div>
|
||||
<Link to="/planning/gantt" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Open gantt
|
||||
<Link to="/planning/workbench" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Open workbench
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-5 grid gap-3 xl:grid-cols-2">
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { Gantt } from "@svar-ui/react-gantt";
|
||||
import "@svar-ui/react-gantt/style.css";
|
||||
import type { DemandPlanningRollupDto, GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "../../auth/AuthProvider";
|
||||
import { ApiError, api } from "../../lib/api";
|
||||
import { useTheme } from "../../theme/ThemeProvider";
|
||||
|
||||
type WorkbenchMode = "overview" | "gantt" | "heatmap" | "agenda";
|
||||
type WorkbenchMode = "overview" | "heatmap" | "agenda";
|
||||
type FocusRecord = {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -98,9 +94,8 @@ function buildFocusRecords(tasks: GanttTaskDto[]) {
|
||||
}));
|
||||
}
|
||||
|
||||
export function GanttPage() {
|
||||
export function WorkbenchPage() {
|
||||
const { token } = useAuth();
|
||||
const { mode } = useTheme();
|
||||
const [timeline, setTimeline] = useState<PlanningTimelineDto | null>(null);
|
||||
const [planningRollup, setPlanningRollup] = useState<DemandPlanningRollupDto | null>(null);
|
||||
const [status, setStatus] = useState("Loading live planning timeline...");
|
||||
@@ -126,15 +121,11 @@ export function GanttPage() {
|
||||
}, [token]);
|
||||
|
||||
const tasks = timeline?.tasks ?? [];
|
||||
const links = timeline?.links ?? [];
|
||||
const summary = timeline?.summary;
|
||||
const exceptions = timeline?.exceptions ?? [];
|
||||
const focusRecords = useMemo(() => buildFocusRecords(tasks), [tasks]);
|
||||
const focusById = useMemo(() => new Map(focusRecords.map((record) => [record.id, record])), [focusRecords]);
|
||||
const selectedFocus = selectedFocusId ? focusById.get(selectedFocusId) ?? null : focusRecords[0] ?? null;
|
||||
const ganttCellHeight = 38;
|
||||
const ganttScaleHeight = 54;
|
||||
const ganttHeight = Math.max(520, tasks.length * ganttCellHeight + ganttScaleHeight);
|
||||
|
||||
const heatmap = useMemo(() => {
|
||||
const start = summary ? startOfDay(new Date(summary.horizonStart)) : startOfDay(new Date());
|
||||
@@ -181,7 +172,6 @@ export function GanttPage() {
|
||||
|
||||
const modeOptions: Array<{ value: WorkbenchMode; label: string; detail: string }> = [
|
||||
{ value: "overview", label: "Overview", detail: "Dense planner board" },
|
||||
{ value: "gantt", label: "Timeline", detail: "Classic gantt lens" },
|
||||
{ value: "heatmap", label: "Heatmap", detail: "Load by day" },
|
||||
{ value: "agenda", label: "Agenda", detail: "Upcoming due flow" },
|
||||
];
|
||||
@@ -267,30 +257,6 @@ export function GanttPage() {
|
||||
|
||||
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
|
||||
{workbenchMode === "overview" ? <OverviewBoard focusRecords={focusRecords} onSelect={setSelectedFocusId} /> : null}
|
||||
{workbenchMode === "gantt" ? (
|
||||
<div className={`gantt-theme overflow-x-auto overflow-y-visible rounded-[18px] border border-line/70 bg-page/60 p-4 ${mode === "dark" ? "wx-willow-dark-theme" : "wx-willow-theme"}`}>
|
||||
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Schedule Window</p>
|
||||
<p className="mt-2 text-sm text-muted">{summary ? `${formatDate(summary.horizonStart)} through ${formatDate(summary.horizonEnd)}` : "Waiting for live schedule data."}</p>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-line/70 bg-surface/80 px-3 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted">{tasks.length} schedule rows</div>
|
||||
</div>
|
||||
<div style={{ height: `${ganttHeight}px`, minWidth: "100%" }}>
|
||||
<Gantt
|
||||
tasks={tasks.map((task: GanttTaskDto) => ({
|
||||
...task,
|
||||
start: new Date(task.start),
|
||||
end: new Date(task.end),
|
||||
parent: task.parentId ?? undefined,
|
||||
}))}
|
||||
links={links}
|
||||
cellHeight={ganttCellHeight}
|
||||
scaleHeight={ganttScaleHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{workbenchMode === "heatmap" ? <HeatmapBoard heatmap={heatmap} selectedDate={selectedHeatmapDate} onSelectDate={setSelectedHeatmapDate} /> : null}
|
||||
{workbenchMode === "agenda" ? <AgendaBoard records={agendaItems} onSelect={setSelectedFocusId} /> : null}
|
||||
</div>
|
||||
@@ -312,7 +278,6 @@ export function GanttPage() {
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedFocus.detailHref ? <Link to={selectedFocus.detailHref} className="rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">Open record</Link> : null}
|
||||
<button type="button" onClick={() => setWorkbenchMode("gantt")} className="rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">View in timeline</button>
|
||||
<button type="button" onClick={() => setWorkbenchMode("heatmap")} className="rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">View load</button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user