@flowselections/logistiek-engine 1.0.34 → 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/dist-lib/components/FlowCanvas.d.ts +28 -0
- package/dist-lib/components/FlowCanvas.d.ts.map +1 -0
- package/dist-lib/components/FlowCanvas.js +429 -0
- package/dist-lib/components/FlowOrders.d.ts +8 -0
- package/dist-lib/components/FlowOrders.d.ts.map +1 -0
- package/dist-lib/components/FlowOrders.js +125 -0
- package/dist-lib/components/FlowPage.d.ts +2 -0
- package/dist-lib/components/FlowPage.d.ts.map +1 -0
- package/dist-lib/components/FlowPage.js +141 -0
- package/dist-lib/components/InklapbaarVenster.d.ts +9 -0
- package/dist-lib/components/InklapbaarVenster.d.ts.map +1 -0
- package/dist-lib/components/InklapbaarVenster.js +10 -0
- package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -1
- package/dist-lib/components/LogistiekEnginePage.js +9 -3
- package/dist-lib/components/LogistiekEngineQueuePage.d.ts +2 -0
- package/dist-lib/components/LogistiekEngineQueuePage.d.ts.map +1 -0
- package/dist-lib/components/LogistiekEngineQueuePage.js +11 -0
- package/dist-lib/components/NodeTracePanel.d.ts +11 -0
- package/dist-lib/components/NodeTracePanel.d.ts.map +1 -0
- package/dist-lib/components/NodeTracePanel.js +40 -0
- package/dist-lib/components/OrderJourneyBar.d.ts +9 -0
- package/dist-lib/components/OrderJourneyBar.d.ts.map +1 -0
- package/dist-lib/components/OrderJourneyBar.js +26 -0
- package/dist-lib/components/OrderLogboek.d.ts +10 -0
- package/dist-lib/components/OrderLogboek.d.ts.map +1 -0
- package/dist-lib/components/OrderLogboek.js +92 -0
- package/dist-lib/components/OrderStopBanner.d.ts +5 -0
- package/dist-lib/components/OrderStopBanner.d.ts.map +1 -0
- package/dist-lib/components/OrderStopBanner.js +22 -0
- package/dist-lib/components/OrderTraceDetails.d.ts +22 -0
- package/dist-lib/components/OrderTraceDetails.d.ts.map +1 -0
- package/dist-lib/components/OrderTraceDetails.js +87 -0
- package/dist-lib/components/RegiePage.d.ts.map +1 -1
- package/dist-lib/components/RegiePage.js +11 -69
- package/dist-lib/components/WerktijdenCard.d.ts +2 -0
- package/dist-lib/components/WerktijdenCard.d.ts.map +1 -0
- package/dist-lib/components/WerktijdenCard.js +37 -0
- package/dist-lib/components/WerkwijzeHerindelingCard.d.ts.map +1 -1
- package/dist-lib/components/WerkwijzeHerindelingCard.js +100 -6
- package/dist-lib/hooks/useLogisticsTrace.d.ts +78 -0
- package/dist-lib/hooks/useLogisticsTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useLogisticsTrace.js +152 -0
- package/dist-lib/hooks/useOrderNodeTrace.d.ts +11 -0
- package/dist-lib/hooks/useOrderNodeTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderNodeTrace.js +26 -0
- package/dist-lib/hooks/useOrderTrace.d.ts +74 -0
- package/dist-lib/hooks/useOrderTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderTrace.js +134 -0
- package/dist-lib/hooks/useOrderVersies.d.ts +47 -0
- package/dist-lib/hooks/useOrderVersies.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderVersies.js +180 -0
- package/dist-lib/hooks/useRuleEngine.d.ts +1 -0
- package/dist-lib/hooks/useRuleEngine.d.ts.map +1 -1
- package/dist-lib/hooks/useRuleEngine.js +1 -0
- package/dist-lib/hooks/useShippingNoteStatus.d.ts +29 -0
- package/dist-lib/hooks/useShippingNoteStatus.d.ts.map +1 -0
- package/dist-lib/hooks/useShippingNoteStatus.js +107 -0
- package/dist-lib/hooks/useWerktijden.d.ts +33 -0
- package/dist-lib/hooks/useWerktijden.d.ts.map +1 -0
- package/dist-lib/hooks/useWerktijden.js +88 -0
- package/dist-lib/hooks/useWerkwijzeStatus.d.ts +12 -1
- package/dist-lib/hooks/useWerkwijzeStatus.d.ts.map +1 -1
- package/dist-lib/hooks/useWerkwijzeStatus.js +60 -4
- package/dist-lib/index.d.ts +13 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +13 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1600 -54
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1600 -54
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +1600 -54
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1618 -50
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cartPlanRequests.server.d.ts +1 -1
- package/dist-lib/lib/cartPlanRequests.server.d.ts.map +1 -1
- package/dist-lib/lib/cartPlanRequests.server.js +49 -8
- package/dist-lib/lib/floridayDocuments.functions.d.ts +4644 -6
- package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayDocuments.functions.js +1 -1
- package/dist-lib/lib/floridayDocuments.server.d.ts +14 -0
- package/dist-lib/lib/floridayDocuments.server.d.ts.map +1 -1
- package/dist-lib/lib/floridayDocuments.server.js +58 -0
- package/dist-lib/lib/floridayGuard.server.d.ts +1 -1
- package/dist-lib/lib/floridayGuard.server.d.ts.map +1 -1
- package/dist-lib/lib/floridayGuard.server.js +6 -3
- package/dist-lib/lib/floridayPush.functions.d.ts +1600 -54
- package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayPush.functions.js +1 -1
- package/dist-lib/lib/flowGraph.d.ts +43 -0
- package/dist-lib/lib/flowGraph.d.ts.map +1 -0
- package/dist-lib/lib/flowGraph.js +153 -0
- package/dist-lib/lib/flowNodes.d.ts +23 -0
- package/dist-lib/lib/flowNodes.d.ts.map +1 -0
- package/dist-lib/lib/flowNodes.js +58 -0
- package/dist-lib/lib/flowRoute.d.ts +107 -0
- package/dist-lib/lib/flowRoute.d.ts.map +1 -0
- package/dist-lib/lib/flowRoute.js +280 -0
- package/dist-lib/lib/flowTree.d.ts +64 -0
- package/dist-lib/lib/flowTree.d.ts.map +1 -0
- package/dist-lib/lib/flowTree.js +519 -0
- package/dist-lib/lib/handlers/index.d.ts +1 -0
- package/dist-lib/lib/handlers/index.d.ts.map +1 -1
- package/dist-lib/lib/handlers/index.js +1 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.d.ts +3 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.d.ts.map +1 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.js +100 -0
- package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
- package/dist-lib/lib/handlers/logistics-tick.server.js +97 -2
- package/dist-lib/lib/loadCarrierPlanner.d.ts +2 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanner.js +18 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +7740 -10
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.functions.js +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +9 -0
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +44 -17
- package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
- package/dist-lib/lib/logisticsPlanner.js +14 -2
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +3096 -4
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalcJob.functions.js +1 -1
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts +1600 -54
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalculate.functions.js +1 -1
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
- package/dist-lib/lib/modulePrompts.js +3459 -2677
- package/dist-lib/lib/nodeTrace.d.ts +61 -0
- package/dist-lib/lib/nodeTrace.d.ts.map +1 -0
- package/dist-lib/lib/nodeTrace.js +191 -0
- package/dist-lib/lib/pickQueueCleanup.server.d.ts +14 -0
- package/dist-lib/lib/pickQueueCleanup.server.d.ts.map +1 -0
- package/dist-lib/lib/pickQueueCleanup.server.js +80 -0
- package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/planQueue.server.js +140 -0
- package/dist-lib/lib/ruleEngineImpact.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineImpact.js +11 -4
- package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineSchema.js +50 -6
- package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineWizard.js +3 -1
- package/dist-lib/lib/shippingNoteQueue.server.d.ts +18 -0
- package/dist-lib/lib/shippingNoteQueue.server.d.ts.map +1 -0
- package/dist-lib/lib/shippingNoteQueue.server.js +304 -0
- package/dist-lib/lib/trace.server.d.ts +75 -0
- package/dist-lib/lib/trace.server.d.ts.map +1 -0
- package/dist-lib/lib/trace.server.js +75 -0
- package/dist-lib/lib/vakUitleg.d.ts +19 -0
- package/dist-lib/lib/vakUitleg.d.ts.map +1 -0
- package/dist-lib/lib/vakUitleg.js +132 -0
- package/package.json +26 -2
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +0 -2
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +0 -1
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +0 -41
- package/dist-lib/integrations/supabase/client.d.ts +0 -20293
- package/dist-lib/integrations/supabase/client.d.ts.map +0 -1
- package/dist-lib/integrations/supabase/client.js +0 -13
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/OrderLogboek.tsx — alle binnengekomen orders met volledig log
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Eén lijst met binnengekomen orders. Per order de volledige tijdlijn van de
|
|
6
|
+
// Logistiek Engine: elke stap, beslissing, foutcode, document, databaseregel
|
|
7
|
+
// en uitwisseling met Floriday. Uitsluitend echt vastgelegde gebeurtenissen.
|
|
8
|
+
// GEEN <Header /> hier — die levert de shell.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { useMemo, useState } from "react";
|
|
11
|
+
import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Skeleton, } from "@flowselections/core";
|
|
12
|
+
import { AlertTriangle, ChevronDown, ChevronRight, Clock, Database, ExternalLink, FileText, RefreshCw, Search, } from "lucide-react";
|
|
13
|
+
import { useLogisticsTrace, useLogisticsTraceTimeline, } from "../hooks/useLogisticsTrace";
|
|
14
|
+
const STATUS_FILTERS = [
|
|
15
|
+
{ waarde: "", label: "Alles" },
|
|
16
|
+
{ waarde: "bezig", label: "Bezig" },
|
|
17
|
+
{ waarde: "wachtend", label: "Wachtend" },
|
|
18
|
+
{ waarde: "geblokkeerd", label: "Geblokkeerd" },
|
|
19
|
+
{ waarde: "fout", label: "Fout" },
|
|
20
|
+
{ waarde: "afgerond", label: "Afgerond" },
|
|
21
|
+
];
|
|
22
|
+
const FASE_LABEL = {
|
|
23
|
+
ontvangst: "Ontvangst",
|
|
24
|
+
validatie: "Controle van gegevens",
|
|
25
|
+
regels: "Regels",
|
|
26
|
+
karindeling: "Karindeling",
|
|
27
|
+
vrijgave: "Vrijgave",
|
|
28
|
+
pakbon: "Pakbon",
|
|
29
|
+
verzendbrief: "Verzendbrief",
|
|
30
|
+
printen: "Printen",
|
|
31
|
+
database: "Databasewijziging",
|
|
32
|
+
controle: "Controle",
|
|
33
|
+
afgerond: "Afronding",
|
|
34
|
+
onbekend: "Overig",
|
|
35
|
+
};
|
|
36
|
+
function tijd(waarde) {
|
|
37
|
+
if (!waarde)
|
|
38
|
+
return "—";
|
|
39
|
+
const d = new Date(waarde);
|
|
40
|
+
return Number.isNaN(d.getTime())
|
|
41
|
+
? "—"
|
|
42
|
+
: d.toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "medium" });
|
|
43
|
+
}
|
|
44
|
+
function statusKleur(status) {
|
|
45
|
+
if (status === "mislukt" || status === "fout" || status === "geblokkeerd")
|
|
46
|
+
return "destructive";
|
|
47
|
+
if (status === "wachtend" || status === "waarschuwing")
|
|
48
|
+
return "secondary";
|
|
49
|
+
if (status === "overgeslagen")
|
|
50
|
+
return "outline";
|
|
51
|
+
return "default";
|
|
52
|
+
}
|
|
53
|
+
function Blok({ titel, waarde }) {
|
|
54
|
+
if (waarde === null || waarde === undefined)
|
|
55
|
+
return null;
|
|
56
|
+
if (typeof waarde === "object" && Object.keys(waarde).length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
return (_jsxs("div", { className: "rounded-md bg-muted/50 p-3", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: titel }), _jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-words text-xs", children: typeof waarde === "string" ? waarde : JSON.stringify(waarde, null, 2) })] }));
|
|
59
|
+
}
|
|
60
|
+
export function Gebeurtenis({ event }) {
|
|
61
|
+
const [open, setOpen] = useState(false);
|
|
62
|
+
const heeftDetails = event.input ||
|
|
63
|
+
event.output ||
|
|
64
|
+
event.reads ||
|
|
65
|
+
event.values ||
|
|
66
|
+
event.dbOld ||
|
|
67
|
+
event.dbNew ||
|
|
68
|
+
event.externalResponse ||
|
|
69
|
+
event.decision ||
|
|
70
|
+
event.rule ||
|
|
71
|
+
event.slipRef ||
|
|
72
|
+
event.dbTable ||
|
|
73
|
+
event.externalSystem;
|
|
74
|
+
return (_jsxs("div", { className: "border-l-2 border-muted pl-4", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-start gap-2 py-2 text-left", children: [heeftDetails ? (open ? (_jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "mt-1 h-4 w-4 shrink-0 text-muted-foreground" }))) : (_jsx("span", { className: "mt-1 h-4 w-4 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: event.step }), _jsx(Badge, { variant: statusKleur(event.status), children: event.status }), _jsx(Badge, { variant: "outline", children: FASE_LABEL[event.phase] ?? event.phase }), event.attempts > 0 && _jsxs(Badge, { variant: "outline", children: ["poging ", event.attempts] }), event.durationMs !== null && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [event.durationMs, " ms"] }))] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [tijd(event.createdAt), event.actor ? ` · ${event.actor}` : "", event.cartRef ? ` · kar ${event.cartRef}` : "", event.slipRef ? ` · pakbon ${event.slipRef}` : ""] }), event.reason && _jsx("p", { className: "mt-1 text-sm", children: event.reason }), event.error && (_jsxs("p", { className: "mt-1 flex items-start gap-1 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: event.error })] }))] })] }), open && (_jsxs("div", { className: "mb-3 space-y-2", children: [event.decision && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Beslissing: ", event.decision] })), event.rule && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Regel: ", event.rule] }), event.slipRef && (_jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(FileText, { className: "h-3.5 w-3.5" }), " Document: ", event.slipRef] })), event.dbTable && (_jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Database, { className: "h-3.5 w-3.5" }), event.dbAction, " in ", event.dbTable, event.dbRecord ? ` (${event.dbRecord})` : ""] })), event.externalSystem && (_jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), event.externalSystem, " \u00B7 ", event.externalAction ?? "—", event.externalRef ? ` · ${event.externalRef}` : "", event.externalStatus ? ` · antwoord ${event.externalStatus}` : ""] })), event.nextAttemptAt && (_jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), " Volgende poging: ", tijd(event.nextAttemptAt)] })), _jsx(Blok, { titel: "Invoer", waarde: event.input }), _jsx(Blok, { titel: "Gelezen gegevens", waarde: event.reads }), _jsx(Blok, { titel: "Gebruikte waarden", waarde: event.values }), _jsx(Blok, { titel: "Resultaat", waarde: event.output }), _jsx(Blok, { titel: "Waarde v\u00F3\u00F3r wijziging", waarde: event.dbOld }), _jsx(Blok, { titel: "Waarde n\u00E1 wijziging", waarde: event.dbNew }), _jsx(Blok, { titel: "Antwoord extern systeem", waarde: event.externalResponse })] }))] }));
|
|
75
|
+
}
|
|
76
|
+
function Tijdlijn({ orderRef }) {
|
|
77
|
+
const { events, loading, error } = useLogisticsTraceTimeline(orderRef);
|
|
78
|
+
const veilig = Array.isArray(events) ? events : [];
|
|
79
|
+
if (loading)
|
|
80
|
+
return _jsx(Skeleton, { className: "h-24 w-full" });
|
|
81
|
+
if (error)
|
|
82
|
+
return _jsx("p", { className: "text-sm text-destructive", children: error });
|
|
83
|
+
if (veilig.length === 0)
|
|
84
|
+
return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Voor deze order zijn nog geen stappen vastgelegd." }));
|
|
85
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Volgnummer: ", veilig[0]?.traceId ?? "—"] }), veilig.map((e) => (_jsx(Gebeurtenis, { event: e }, e.id)))] }));
|
|
86
|
+
}
|
|
87
|
+
export function OrderLogboek({ titel = "Binnengekomen orders", omschrijving = "Klik een order open voor elke stap, beslissing, foutmelding en elk document dat de Logistiek Engine heeft vastgelegd.", }) {
|
|
88
|
+
const { orders, loading, error, search, setSearch, status, setStatus, hours, setHours, refresh } = useLogisticsTrace();
|
|
89
|
+
const [open, setOpen] = useState(null);
|
|
90
|
+
const veilig = useMemo(() => (Array.isArray(orders) ? orders : []), [orders]);
|
|
91
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: titel }), _jsx(CardDescription, { children: omschrijving })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[220px] flex-1", children: [_jsx(Search, { className: "absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek op order, kar, pakbon of foutmelding", className: "pl-8" })] }), _jsxs("select", { value: String(hours), onChange: (e) => setHours(Number(e.target.value)), className: "h-9 rounded-md border bg-background px-2 text-sm", children: [_jsx("option", { value: "6", children: "Laatste 6 uur" }), _jsx("option", { value: "24", children: "Laatste 24 uur" }), _jsx("option", { value: "48", children: "Laatste 2 dagen" }), _jsx("option", { value: "168", children: "Laatste week" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Verversen"] })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: STATUS_FILTERS.map((f) => (_jsx(Button, { size: "sm", variant: status === f.waarde ? "default" : "outline", onClick: () => setStatus(f.waarde), children: f.label }, f.waarde || "alles"))) }), error && _jsx("p", { className: "text-sm text-destructive", children: error }), loading && veilig.length === 0 && _jsx(Skeleton, { className: "h-32 w-full" }), !loading && veilig.length === 0 && !error && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn in deze periode nog geen stappen vastgelegd." })), _jsx("div", { className: "space-y-2", children: veilig.map((o) => (_jsxs("div", { className: "rounded-lg border", children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-3 p-3 text-left", onClick: () => setOpen((v) => (v === o.orderRef ? null : o.orderRef)), children: [open === o.orderRef ? (_jsx(ChevronDown, { className: "h-4 w-4 shrink-0" })) : (_jsx(ChevronRight, { className: "h-4 w-4 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: o.orderRef }), _jsx(Badge, { variant: statusKleur(o.state), children: o.state }), o.orderSource && _jsx(Badge, { variant: "outline", children: o.orderSource }), o.errors > 0 && _jsxs(Badge, { variant: "destructive", children: [o.errors, " fout"] }), o.waiting > 0 && _jsxs(Badge, { variant: "secondary", children: [o.waiting, " wachtend"] })] }), _jsxs("p", { className: "truncate text-xs text-muted-foreground", children: [o.lastStep ?? "—", " \u00B7 ", tijd(o.lastAt)] }), o.lastReason && (_jsx("p", { className: "truncate text-sm text-muted-foreground", children: o.lastReason }))] })] }), open === o.orderRef && (_jsx("div", { className: "border-t p-3", children: _jsx(Tijdlijn, { orderRef: o.orderRef }) }))] }, o.orderRef))) })] })] }));
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrderStopBanner.d.ts","sourceRoot":"","sources":["../../src/components/OrderStopBanner.tsx"],"names":[],"mappings":"AAaA,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,sCAqC5G"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/OrderStopBanner.tsx — waar staat deze order stil?
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Boven de flowkaart: bij welk vakje de order gestopt is of staat te wachten,
|
|
6
|
+
// waarom, sinds wanneer en wat er moet gebeuren. Alleen lezen.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
import { AlertTriangle, Clock } from "lucide-react";
|
|
9
|
+
import { useOrderTraceStand } from "../hooks/useOrderTrace";
|
|
10
|
+
import { flowNodeLabel } from "../lib/flowNodes";
|
|
11
|
+
import { FOUTSOORT_LABEL, Veld, sinds, tijd } from "./OrderTraceDetails";
|
|
12
|
+
export function OrderStopBanner({ orderRef, onNode }) {
|
|
13
|
+
const { stand } = useOrderTraceStand(orderRef);
|
|
14
|
+
if (!stand)
|
|
15
|
+
return null;
|
|
16
|
+
const gestopt = stand.stuckStatus === "mislukt";
|
|
17
|
+
const wacht = stand.stuckStatus === "wachtend";
|
|
18
|
+
if (!gestopt && !wacht)
|
|
19
|
+
return null;
|
|
20
|
+
const node = stand.stuckNode ?? null;
|
|
21
|
+
return (_jsxs("div", { className: `rounded-lg border p-3 ${gestopt ? "border-destructive/50 bg-destructive/5" : "border-amber-500/40 bg-amber-500/5"}`, children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-semibold", children: [gestopt ? _jsx(AlertTriangle, { className: "h-4 w-4" }) : _jsx(Clock, { className: "h-4 w-4" }), gestopt ? "ORDER GESTOPT BIJ" : "ORDER WACHT BIJ", ":", " ", node ? flowNodeLabel(node) : stand.stuckStep ?? "onbekende stap", node && onNode ? (_jsx("button", { type: "button", className: "text-xs font-normal underline", onClick: () => onNode(node), children: "toon dit vakje" })) : null] }), _jsx(Veld, { label: "Reden", children: stand.stuckReason ?? stand.queueError }), _jsx(Veld, { label: "Soort", children: stand.errorClass ? FOUTSOORT_LABEL[stand.errorClass] ?? stand.errorClass : null }), _jsx(Veld, { label: "Geblokkeerd sinds", children: stand.blockedSince ? `${tijd(stand.blockedSince)} (${sinds(stand.blockedSince)})` : null }), _jsx(Veld, { label: "Automatische pogingen", children: stand.tries > 0 ? String(stand.tries) : null }), _jsx(Veld, { label: "Volgende poging", children: stand.nextAttemptAt ? tijd(stand.nextAttemptAt) : null }), _jsx(Veld, { label: "Wat moet er gebeuren", children: stand.nextStep }), _jsx(Veld, { label: "Wachtrij", children: stand.queueStatus })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TraceEventRow } from "../hooks/useLogisticsTrace";
|
|
2
|
+
export declare const FOUTSOORT_LABEL: Record<string, string>;
|
|
3
|
+
export declare const FOUTSOORT_UITLEG: Record<string, string>;
|
|
4
|
+
export declare const STATUS_KLEUR: Record<string, string>;
|
|
5
|
+
export declare function tijd(v: string | null | undefined): string;
|
|
6
|
+
export declare function klok(v: string | null | undefined): string;
|
|
7
|
+
export declare function duur(ms: number | null | undefined): string;
|
|
8
|
+
export declare function sinds(v: string | null | undefined): string;
|
|
9
|
+
export declare function Json({ waarde }: {
|
|
10
|
+
waarde: unknown;
|
|
11
|
+
}): import("react").JSX.Element | null;
|
|
12
|
+
export declare function Veld({ label, children }: {
|
|
13
|
+
label: string;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}): import("react").JSX.Element | null;
|
|
16
|
+
/** Eén gebeurtenis van de Engine, uitklapbaar met alle vastgelegde details. */
|
|
17
|
+
export declare function Stap({ e, debug, open: standaardOpen }: {
|
|
18
|
+
e: TraceEventRow;
|
|
19
|
+
debug: boolean;
|
|
20
|
+
open?: boolean;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=OrderTraceDetails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrderTraceDetails.d.ts","sourceRoot":"","sources":["../../src/components/OrderTraceDetails.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMlD,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMnD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM/C,CAAC;AAEF,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIzD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKzD;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAK1D;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1D;AAED,wBAAgB,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,sCAOnD;AAED,wBAAgB,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,sCAQrF;AAED,+EAA+E;AAC/E,wBAAgB,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE;IAAE,CAAC,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,+BAmJ3G"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/OrderTraceDetails.tsx — bouwstenen om één gebeurtenis te tonen
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Gedeelde weergave van wat de Engine bij een stap heeft vastgelegd: de
|
|
6
|
+
// beslissing, de berekening, de fout, gewijzigde gegevens en extern verkeer.
|
|
7
|
+
// Alleen lezen. GEEN <Header /> hier — die levert de shell.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { Badge } from "@flowselections/core";
|
|
11
|
+
import { AlertTriangle, ChevronDown, ChevronRight, Database, ExternalLink } from "lucide-react";
|
|
12
|
+
import { flowNodeLabel } from "../lib/flowNodes";
|
|
13
|
+
export const FOUTSOORT_LABEL = {
|
|
14
|
+
technisch: "Technische fout",
|
|
15
|
+
ontbrekende_gegevens: "Ontbrekende gegevens",
|
|
16
|
+
configuratie: "Configuratiefout",
|
|
17
|
+
regel: "Bewuste regel",
|
|
18
|
+
extern: "Extern systeem",
|
|
19
|
+
};
|
|
20
|
+
export const FOUTSOORT_UITLEG = {
|
|
21
|
+
technisch: "Er ging iets stuk in de database, een koppeling of de printer.",
|
|
22
|
+
ontbrekende_gegevens: "De order of de stamgegevens missen iets dat de Engine nodig heeft.",
|
|
23
|
+
configuratie: "Een instelling of koppeling in de werkwijze staat niet goed.",
|
|
24
|
+
regel: "De Engine mag bewust niet verder vanwege een ingestelde regel.",
|
|
25
|
+
extern: "Een extern systeem heeft nog niets teruggegeven.",
|
|
26
|
+
};
|
|
27
|
+
export const STATUS_KLEUR = {
|
|
28
|
+
geslaagd: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
|
|
29
|
+
wachtend: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
|
|
30
|
+
waarschuwing: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
|
|
31
|
+
overgeslagen: "bg-muted text-muted-foreground",
|
|
32
|
+
mislukt: "bg-destructive/15 text-destructive",
|
|
33
|
+
};
|
|
34
|
+
export function tijd(v) {
|
|
35
|
+
if (!v)
|
|
36
|
+
return "—";
|
|
37
|
+
const d = new Date(v);
|
|
38
|
+
return Number.isNaN(d.getTime()) ? String(v) : d.toLocaleString("nl-NL");
|
|
39
|
+
}
|
|
40
|
+
export function klok(v) {
|
|
41
|
+
if (!v)
|
|
42
|
+
return "—";
|
|
43
|
+
const d = new Date(v);
|
|
44
|
+
if (Number.isNaN(d.getTime()))
|
|
45
|
+
return String(v);
|
|
46
|
+
return d.toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" });
|
|
47
|
+
}
|
|
48
|
+
export function duur(ms) {
|
|
49
|
+
if (ms === null || ms === undefined)
|
|
50
|
+
return "—";
|
|
51
|
+
if (ms < 1000)
|
|
52
|
+
return `${ms} ms`;
|
|
53
|
+
if (ms < 60000)
|
|
54
|
+
return `${(ms / 1000).toFixed(1)} s`;
|
|
55
|
+
return `${Math.round(ms / 60000)} min`;
|
|
56
|
+
}
|
|
57
|
+
export function sinds(v) {
|
|
58
|
+
if (!v)
|
|
59
|
+
return "—";
|
|
60
|
+
const start = new Date(v).getTime();
|
|
61
|
+
if (Number.isNaN(start))
|
|
62
|
+
return "—";
|
|
63
|
+
const min = Math.max(Math.round((Date.now() - start) / 60000), 0);
|
|
64
|
+
if (min < 60)
|
|
65
|
+
return `${min} minuten`;
|
|
66
|
+
const u = Math.floor(min / 60);
|
|
67
|
+
if (u < 24)
|
|
68
|
+
return `${u} uur en ${min % 60} minuten`;
|
|
69
|
+
return `${Math.floor(u / 24)} dagen en ${u % 24} uur`;
|
|
70
|
+
}
|
|
71
|
+
export function Json({ waarde }) {
|
|
72
|
+
if (waarde === null || waarde === undefined)
|
|
73
|
+
return null;
|
|
74
|
+
return (_jsx("pre", { className: "mt-1 max-h-56 overflow-auto rounded-md bg-muted/60 p-2 text-xs leading-relaxed", children: JSON.stringify(waarde, null, 2) }));
|
|
75
|
+
}
|
|
76
|
+
export function Veld({ label, children }) {
|
|
77
|
+
if (children === null || children === undefined || children === "")
|
|
78
|
+
return null;
|
|
79
|
+
return (_jsxs("div", { className: "text-sm", children: [_jsxs("span", { className: "font-medium text-foreground", children: [label, ": "] }), _jsx("span", { className: "text-muted-foreground", children: children })] }));
|
|
80
|
+
}
|
|
81
|
+
/** Eén gebeurtenis van de Engine, uitklapbaar met alle vastgelegde details. */
|
|
82
|
+
export function Stap({ e, debug, open: standaardOpen }) {
|
|
83
|
+
const [open, setOpen] = useState(standaardOpen ?? e.status === "mislukt");
|
|
84
|
+
const calc = (e.calc ?? null);
|
|
85
|
+
const fout = e.status === "mislukt";
|
|
86
|
+
return (_jsxs("div", { className: `rounded-lg border ${fout ? "border-destructive/50 bg-destructive/5" : "bg-card"}`, children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-start gap-3 p-3 text-left", children: [open ? _jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0" }) : _jsx(ChevronRight, { className: "mt-1 h-4 w-4 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: e.step }), _jsx(Badge, { className: STATUS_KLEUR[e.status] ?? "", children: e.status }), e.errorClass ? _jsx(Badge, { variant: "outline", children: FOUTSOORT_LABEL[e.errorClass] ?? e.errorClass }) : null, e.nodeId ? _jsx(Badge, { variant: "outline", children: flowNodeLabel(e.nodeId) }) : null] }), _jsxs("div", { className: "mt-1 flex flex-wrap gap-x-4 text-xs text-muted-foreground", children: [_jsx("span", { children: tijd(e.createdAt) }), _jsxs("span", { children: ["Duur: ", duur(e.durationMs)] }), e.actor ? _jsxs("span", { children: ["Door: ", e.actor] }) : null, e.attempts > 0 ? _jsxs("span", { children: ["Poging ", e.attempts] }) : null] }), e.reason || e.decision ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: e.reason ?? e.decision })) : null] })] }), open ? (_jsxs("div", { className: "space-y-3 border-t px-3 py-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Veld, { label: "Beslissing", children: e.decision }), _jsx(Veld, { label: "Waarom", children: e.reason }), _jsx(Veld, { label: "Regel", children: e.rule }), _jsx(Veld, { label: "Volgende stap", children: e.nextStep }), _jsx(Veld, { label: "Volgende poging", children: e.nextAttemptAt ? tijd(e.nextAttemptAt) : null })] }), calc ? (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3", children: [_jsx("p", { className: "text-sm font-medium", children: "Berekening" }), _jsx(Veld, { label: "Instelling", children: calc.setting }), _jsx(Veld, { label: "Rekenstap", children: calc.formula }), _jsx(Veld, { label: "Uitkomst", children: calc.result }), _jsx(Veld, { label: "Conclusie", children: calc.conclusion }), calc.values ? _jsx(Json, { waarde: calc.values }) : null] })) : null, fout ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-medium text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4" }), " Fout bij deze stap"] }), _jsx(Veld, { label: "Soort", children: e.errorClass ? FOUTSOORT_LABEL[e.errorClass] ?? e.errorClass : "Technische fout" }), _jsx(Veld, { label: "Uitleg", children: e.errorClass ? FOUTSOORT_UITLEG[e.errorClass] ?? null : null }), _jsx(Veld, { label: "Melding", children: e.error }), _jsx(Veld, { label: "Pogingen", children: e.attempts > 0 ? String(e.attempts) : null })] })) : null, e.dbTable ? (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(Database, { className: "h-4 w-4" }), " Gegevens gewijzigd"] }), _jsx(Veld, { label: "Tabel", children: e.dbTable }), _jsx(Veld, { label: "Actie", children: e.dbAction }), e.dbOld ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mt-2 text-xs font-medium", children: "Voor" }), _jsx(Json, { waarde: e.dbOld })] })) : null, e.dbNew ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mt-2 text-xs font-medium", children: "Na" }), _jsx(Json, { waarde: e.dbNew })] })) : null] })) : null, e.externalSystem ? (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(ExternalLink, { className: "h-4 w-4" }), " Extern systeem"] }), _jsx(Veld, { label: "Systeem", children: e.externalSystem }), _jsx(Veld, { label: "Actie", children: e.externalAction }), _jsx(Veld, { label: "Kenmerk", children: e.externalRef }), _jsx(Veld, { label: "Resultaat", children: e.externalStatus }), _jsx(Json, { waarde: e.externalResponse })] })) : null, e.reads ? (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: "Gelezen gegevens" }), _jsx(Json, { waarde: e.reads })] })) : null, e.values ? (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: "Gebruikte waarden" }), _jsx(Json, { waarde: e.values })] })) : null, debug ? (_jsxs("div", { className: "rounded-md border border-dashed p-3", children: [_jsx("p", { className: "text-sm font-medium", children: "Technische gegevens" }), _jsx(Veld, { label: "Trace ID", children: e.traceId }), _jsx(Veld, { label: "Gebeurtenis ID", children: e.id }), _jsx(Veld, { label: "Vakje", children: e.nodeId }), _jsx(Veld, { label: "Fase", children: e.phase }), _jsx(Veld, { label: "Kar", children: e.cartRef }), _jsx(Veld, { label: "Pakbon", children: e.slipRef }), _jsx(Veld, { label: "Regel in database", children: e.dbRecord }), _jsxs("div", { className: "mt-2 grid gap-2 md:grid-cols-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium", children: "Invoer" }), _jsx(Json, { waarde: e.input })] }), _jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium", children: "Uitvoer" }), _jsx(Json, { waarde: e.output })] })] })] })) : null] })) : null] }));
|
|
87
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegiePage.d.ts","sourceRoot":"","sources":["../../src/components/RegiePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RegiePage.d.ts","sourceRoot":"","sources":["../../src/components/RegiePage.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,SAAS,gCAqExB"}
|
|
@@ -1,75 +1,24 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// src/components/RegiePage.tsx — Regieoverzicht van de Logistics Engine
|
|
4
4
|
// ============================================================================
|
|
5
|
-
// Toont
|
|
6
|
-
//
|
|
5
|
+
// Toont hoeveel taken er per module openstaan en daaronder alle binnengekomen
|
|
6
|
+
// orders met het volledige logboek per order.
|
|
7
7
|
// GEEN <Header /> hier — die levert de shell.
|
|
8
8
|
// ============================================================================
|
|
9
|
-
import { useMemo
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { useMemo } from "react";
|
|
10
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@flowselections/core";
|
|
11
|
+
import { RefreshCw, Workflow } from "lucide-react";
|
|
12
12
|
import { Link } from "@tanstack/react-router";
|
|
13
13
|
import { useLogistiekTaken } from "../hooks/useLogistiekTaken";
|
|
14
|
-
import { BeladingIndelingCard } from "./BeladingIndelingCard";
|
|
15
|
-
import { VoorraadMeldingenCard } from "./VoorraadMeldingenCard";
|
|
16
|
-
import { VertragingMeldingenCard } from "./VertragingMeldingenCard";
|
|
17
|
-
import { CapaciteitMeldingenCard } from "./CapaciteitMeldingenCard";
|
|
18
|
-
import { FloridayPushCard } from "./FloridayPushCard";
|
|
19
|
-
import { PrintjobsCard } from "./PrintjobsCard";
|
|
20
14
|
import { ConsistentieCard } from "./ConsistentieCard";
|
|
21
15
|
import { KarplanStatusCard } from "./KarplanStatusCard";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { LOGISTICS_MODULES
|
|
25
|
-
function tijd(value) {
|
|
26
|
-
if (!value)
|
|
27
|
-
return "—";
|
|
28
|
-
const d = new Date(value);
|
|
29
|
-
if (Number.isNaN(d.getTime()))
|
|
30
|
-
return "—";
|
|
31
|
-
return d.toLocaleString("nl-NL", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
32
|
-
}
|
|
33
|
-
function statusVariant(status) {
|
|
34
|
-
if (status === "done")
|
|
35
|
-
return "default";
|
|
36
|
-
if (status === "failed" || status === "cancelled")
|
|
37
|
-
return "destructive";
|
|
38
|
-
if (status === "in_progress" || status === "claimed")
|
|
39
|
-
return "secondary";
|
|
40
|
-
return "outline";
|
|
41
|
-
}
|
|
42
|
-
const WAARSCHUWING_LABEL = {
|
|
43
|
-
pick_start_in_past: "Pickstart lag in het verleden",
|
|
44
|
-
pick_window_too_short: "Pickvenster te kort",
|
|
45
|
-
pickup_in_past: "Ophaalmoment verstreken",
|
|
46
|
-
buffer_close_in_past: "Buffer sloot al",
|
|
47
|
-
rule_version_not_found: "Regelversie niet gevonden",
|
|
48
|
-
no_rule_config: "Geen regelset gekoppeld",
|
|
49
|
-
};
|
|
16
|
+
import { OrderLogboek } from "./OrderLogboek";
|
|
17
|
+
import { InklapbaarVenster } from "./InklapbaarVenster";
|
|
18
|
+
import { LOGISTICS_MODULES } from "../lib/logisticsTasks";
|
|
50
19
|
export function RegiePage() {
|
|
51
|
-
const { taken,
|
|
52
|
-
const [filter, setFilter] = useState("");
|
|
20
|
+
const { taken, loading, laden } = useLogistiekTaken();
|
|
53
21
|
const safeTaken = Array.isArray(taken) ? taken : [];
|
|
54
|
-
const safeRuns = Array.isArray(planRuns) ? planRuns : [];
|
|
55
|
-
const safeMeldingen = Array.isArray(meldingen) ? meldingen : [];
|
|
56
|
-
const gefilterd = useMemo(() => {
|
|
57
|
-
const q = filter.trim().toLowerCase();
|
|
58
|
-
if (!q)
|
|
59
|
-
return safeTaken;
|
|
60
|
-
return safeTaken.filter((t) => t.order_ref.toLowerCase().includes(q) ||
|
|
61
|
-
t.task_type.toLowerCase().includes(q) ||
|
|
62
|
-
t.owner_module.toLowerCase().includes(q));
|
|
63
|
-
}, [safeTaken, filter]);
|
|
64
|
-
const perOrder = useMemo(() => {
|
|
65
|
-
const map = new Map();
|
|
66
|
-
for (const t of gefilterd) {
|
|
67
|
-
const list = map.get(t.order_ref) ?? [];
|
|
68
|
-
list.push(t);
|
|
69
|
-
map.set(t.order_ref, list);
|
|
70
|
-
}
|
|
71
|
-
return Array.from(map.entries());
|
|
72
|
-
}, [gefilterd]);
|
|
73
22
|
const perModule = useMemo(() => {
|
|
74
23
|
const counts = {};
|
|
75
24
|
for (const t of safeTaken) {
|
|
@@ -77,12 +26,5 @@ export function RegiePage() {
|
|
|
77
26
|
}
|
|
78
27
|
return counts;
|
|
79
28
|
}, [safeTaken]);
|
|
80
|
-
return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("h1", { className: "flex items-center gap-2 text-2xl font-semibold tracking-tight", children: [_jsx(Workflow, { className: "h-6 w-6 text-primary" }), "Overzicht & meldingen"] }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Wat er vandaag klaarstaat en waar aandacht voor nodig is." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/logistiek-engine", children: "Terug naar overzicht" }) }), _jsxs(Button, { variant: "outline", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Vernieuwen"] })] })] }), _jsx(
|
|
81
|
-
const spoed = orderTaken.some((t) => (t.priority ?? 0) > 0);
|
|
82
|
-
const actief = orderTaken.some((t) => t.status !== "cancelled" && t.status !== "done" && t.status !== "superseded");
|
|
83
|
-
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("h2", { className: "flex items-center gap-2 font-medium", children: ["Order ", orderRef, _jsxs("span", { className: "text-muted-foreground text-xs font-normal", children: [orderTaken.length, " taken"] }), spoed && (_jsx(Badge, { variant: "destructive", className: "text-[10px]", children: "spoed" }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void zetPrioriteit(orderRef, spoed ? 0 : 100), children: [_jsx(ArrowUpCircle, { className: "mr-1 h-4 w-4" }), spoed ? "Normaal" : "Spoed"] }), actief && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void annuleerOrder(orderRef), children: [_jsx(Ban, { className: "mr-1 h-4 w-4" }), "Annuleren"] })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void verwijderOrder(orderRef), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: "#" }), _jsx(TableHead, { children: "Taak" }), _jsx(TableHead, { children: "Module" }), _jsx(TableHead, { children: "Start" }), _jsx(TableHead, { children: "Gereed" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Actie" })] }) }), _jsx(TableBody, { children: orderTaken.map((t) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-muted-foreground", children: t.sequence + 1 }), _jsxs(TableCell, { className: "font-medium", children: [TASK_LABEL[t.task_type] ?? t.task_type, t.shadow && (_jsx(Badge, { variant: "outline", className: "ml-2 text-[10px]", children: "schaduw" }))] }), _jsx(TableCell, { className: "text-sm", children: LOGISTICS_MODULES[t.owner_module] ?? t.owner_module }), _jsxs(TableCell, { className: "text-sm", children: [tijd(t.planned_start), t.payload?.["pickWindowShifted"] === true && (_jsx(Badge, { variant: "outline", className: "ml-2 text-[10px]", title: "Het berekende moment lag in het verleden; deze taak is naar nu verschoven.", children: "te laat aangemeld" }))] }), _jsx(TableCell, { className: "text-sm", children: tijd(t.planned_end) }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: statusVariant(t.status), children: TASK_STATUS_LABEL[t.status] ?? t.status }), t.claimed_by && (t.status === "claimed" || t.status === "in_progress") && (_jsxs("span", { className: "text-muted-foreground ml-2 text-xs", children: ["door ", t.claimed_by] })), t.error && _jsx("span", { className: "text-destructive ml-2 text-xs", children: t.error })] }), _jsxs(TableCell, { className: "space-x-1 text-right", children: [t.status === "planned" && (_jsx(Button, { variant: "outline", size: "sm", onClick: () => void claimTaak(t.id), children: "Claimen" })), (t.status === "claimed" || t.status === "in_progress") && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => void rondTaakAf(t.id), children: "Afronden" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void meldTaakMislukt(t.id, "Handmatig gemeld vanuit regieoverzicht"), children: "Mislukt" })] }))] })] }, t.id))) })] })] }, orderRef));
|
|
84
|
-
})) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Planrondes" }), _jsx(CardDescription, { children: "Elke keer dat de Engine (her)plande, met welke regelversie." })] }), _jsx(CardContent, { children: safeRuns.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nog geen planrondes." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Moment" }), _jsx(TableHead, { children: "Order" }), _jsx(TableHead, { children: "Aanleiding" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { children: "Regelversie" }), _jsx(TableHead, { children: "Taken" }), _jsx(TableHead, { children: "Signalen" })] }) }), _jsx(TableBody, { children: safeRuns.map((r) => {
|
|
85
|
-
const warnings = Array.isArray(r.warnings) ? r.warnings : [];
|
|
86
|
-
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm", children: tijd(r.created_at) }), _jsx(TableCell, { className: "font-medium", children: r.order_ref }), _jsx(TableCell, { className: "text-sm", children: r.trigger }), _jsx(TableCell, { className: "text-sm", children: r.source_module ?? "—" }), _jsxs(TableCell, { className: "text-sm", children: [r.rule_version ?? "—", r.settings_hash ? (_jsx("span", { className: "text-muted-foreground ml-1 font-mono text-xs", children: r.settings_hash.slice(0, 6) })) : null] }), _jsxs(TableCell, { className: "text-sm", children: [r.tasks_created, " nieuw / ", r.tasks_revised, " herzien"] }), _jsx(TableCell, { className: "text-sm", children: warnings.length === 0 ? (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) : (_jsx("div", { className: "flex flex-wrap gap-1", children: warnings.map((w, i) => (_jsx(Badge, { variant: "outline", children: WAARSCHUWING_LABEL[String(w.code)] ?? String(w.code) }, i))) })) })] }, r.id));
|
|
87
|
-
}) })] })) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Binnengekomen orders" }), _jsx(CardDescription, { children: "Elke binnengekomen order wordt automatisch omgezet naar de bijbehorende werkzaamheden." })] }), _jsx(CardContent, { children: safeMeldingen.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nog geen meldingen ontvangen." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Moment" }), _jsx(TableHead, { children: "Onderdeel" }), _jsx(TableHead, { children: "Soort" }), _jsx(TableHead, { children: "Order" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-24" })] }) }), _jsx(TableBody, { children: safeMeldingen.map((m) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm", children: tijd(m.created_at) }), _jsx(TableCell, { className: "text-sm", children: LOGISTICS_MODULES[m.source_module] ?? m.source_module }), _jsx(TableCell, { className: "text-sm", children: m.event_type }), _jsx(TableCell, { className: "font-medium", children: m.order_ref }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: m.status === "failed" ? "destructive" : m.status === "processed" ? "default" : "outline", children: m.status === "processed" ? "Verwerkt" : m.status === "failed" ? "Mislukt" : "Wachtend" }), m.error && _jsx("p", { className: "text-destructive mt-1 text-xs", children: m.error })] }), _jsx(TableCell, { className: "text-right", children: m.status !== "processed" && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => void herverwerkMelding(m.id), children: [_jsx(RotateCcw, { className: "mr-1 h-4 w-4" }), "Opnieuw"] })) })] }, m.id))) })] })) })] }), _jsx(FloridayPushCard, {}), _jsx(PrintjobsCard, {}), _jsx(VoorraadMeldingenCard, {}), _jsx(VertragingMeldingenCard, {}), _jsx(CapaciteitMeldingenCard, {}), _jsx(BeladingIndelingCard, {})] }));
|
|
29
|
+
return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("h1", { className: "flex items-center gap-2 text-2xl font-semibold tracking-tight", children: [_jsx(Workflow, { className: "h-6 w-6 text-primary" }), "Overzicht & meldingen"] }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Wat er vandaag klaarstaat en waar aandacht voor nodig is." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/logistiek-engine", children: "Terug naar overzicht" }) }), _jsxs(Button, { variant: "outline", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Vernieuwen"] })] })] }), _jsx(InklapbaarVenster, { titel: "Binnengekomen orders", omschrijving: "Klik een order open voor elke stap, beslissing, foutmelding en elk document.", standaardOpen: true, children: _jsx(OrderLogboek, { titel: "Binnengekomen orders" }) }), _jsx(InklapbaarVenster, { titel: "Consistentie", omschrijving: "Controle of alle modules hetzelfde tonen.", children: _jsx(ConsistentieCard, {}) }), _jsx(InklapbaarVenster, { titel: "Karplanstatus", omschrijving: "Stand van de karrenindeling.", children: _jsx(KarplanStatusCard, {}) }), _jsx(InklapbaarVenster, { titel: "Taken per module", omschrijving: "Openstaande en geplande taken.", children: _jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Object.keys(LOGISTICS_MODULES).map((key) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsx(CardDescription, { children: LOGISTICS_MODULES[key] }), _jsx(CardTitle, { className: "text-2xl", children: perModule[key] ?? 0 })] }), _jsx(CardContent, { className: "text-muted-foreground text-xs", children: "openstaande en geplande taken" })] }, key))) }) })] }));
|
|
88
30
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WerktijdenCard.d.ts","sourceRoot":"","sources":["../../src/components/WerktijdenCard.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,cAAc,gCAwN7B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/WerktijdenCard.tsx — openingstijden voor het terugrekenen
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// De Engine rekent de benodigde picktijd terug in werkuren. Deze kaart toont en
|
|
6
|
+
// beheert de vensters per weekdag, afwijkende dagen en de tijdzone. Ontbreken
|
|
7
|
+
// die gegevens, dan rekent de Engine niets uit — er wordt niets geraden.
|
|
8
|
+
// GEEN <Header /> hier — die levert de shell.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Skeleton, } from "@flowselections/core";
|
|
12
|
+
import { Clock, Plus, RefreshCw, Trash2 } from "lucide-react";
|
|
13
|
+
import { useWerktijden } from "../hooks/useWerktijden";
|
|
14
|
+
const DAGEN = ["Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag"];
|
|
15
|
+
export function WerktijdenCard() {
|
|
16
|
+
const { status, loading, error, refresh, zetTijdzone, voegVensterToe, verwijderVenster, voegUitzonderingToe, verwijderUitzondering, } = useWerktijden();
|
|
17
|
+
const [tz, setTz] = useState("");
|
|
18
|
+
const [dag, setDag] = useState("1");
|
|
19
|
+
const [van, setVan] = useState("07:00");
|
|
20
|
+
const [tot, setTot] = useState("17:00");
|
|
21
|
+
const [datum, setDatum] = useState("");
|
|
22
|
+
const [melding, setMelding] = useState(null);
|
|
23
|
+
const vensters = status?.windows ?? [];
|
|
24
|
+
const uitzonderingen = status?.exceptions ?? [];
|
|
25
|
+
const doe = async (fn, ok) => {
|
|
26
|
+
try {
|
|
27
|
+
await fn();
|
|
28
|
+
setMelding(ok);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
setMelding(e?.message ? String(e.message) : "Opslaan is niet gelukt.");
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4" }), "Werktijden voor het picken"] }), _jsx(CardDescription, { children: "De starttijd van een pickopdracht wordt teruggerekend in werkuren, niet in klokuren. Alleen tijd binnen deze vensters telt mee." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] })] }), _jsx(CardContent, { className: "space-y-4", children: loading ? (_jsx(Skeleton, { className: "h-24 w-full" })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: error })) : (_jsxs(_Fragment, { children: [!status?.usable ? (_jsx("p", { className: "text-sm text-destructive", children: "Er zijn nog geen werktijden ingesteld. Zolang die ontbreken, bepaalt de Engine geen starttijd voor het picken en komt er niets vrij op het pickscherm." })) : null, _jsx("div", { className: "flex flex-wrap items-end gap-2", children: _jsxs("div", { children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Tijdzone van de vestiging" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: tz || status?.timezone || "", placeholder: "bijv. Europe/Amsterdam", onChange: (e) => setTz(e.target.value), className: "w-56" }), _jsx(Button, { size: "sm", onClick: () => void doe(() => zetTijdzone((tz || status?.timezone || "").trim()), "Tijdzone opgeslagen."), children: "Opslaan" })] })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Openingstijden per dag" }), vensters.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen openingstijden ingesteld." })) : (_jsx("ul", { className: "space-y-1", children: vensters.map((v) => (_jsxs("li", { className: "flex items-center gap-2 text-sm", children: [_jsx(Badge, { variant: "outline", children: DAGEN[v.weekday] ?? v.weekday }), _jsxs("span", { children: [String(v.opens).slice(0, 5), " \u2013 ", String(v.closes).slice(0, 5)] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void doe(() => verwijderVenster(v.id), "Venster verwijderd."), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, v.id))) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { value: dag, onChange: (e) => setDag(e.target.value), className: "h-9 rounded-md border bg-background px-2 text-sm", children: DAGEN.map((d, i) => (_jsx("option", { value: String(i), children: d }, d))) }), _jsx(Input, { type: "time", value: van, onChange: (e) => setVan(e.target.value), className: "w-32" }), _jsx(Input, { type: "time", value: tot, onChange: (e) => setTot(e.target.value), className: "w-32" }), _jsxs(Button, { size: "sm", onClick: () => void doe(() => voegVensterToe(Number(dag), van, tot), "Openingstijd toegevoegd."), children: [_jsx(Plus, { className: "h-4 w-4" }), "Toevoegen"] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Meerdere vensters op \u00E9\u00E9n dag mag; zo blijft bijvoorbeeld een pauze buiten de picktijd. Een dag zonder venster is een niet-werkdag." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Afwijkende dagen" }), uitzonderingen.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen afwijkende dagen ingesteld." })) : (_jsx("ul", { className: "space-y-1", children: uitzonderingen.map((u) => (_jsxs("li", { className: "flex items-center gap-2 text-sm", children: [_jsx(Badge, { variant: "outline", children: u.day }), _jsx("span", { children: u.closed
|
|
35
|
+
? "gesloten"
|
|
36
|
+
: `${String(u.opens ?? "").slice(0, 5)} – ${String(u.closes ?? "").slice(0, 5)}` }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void doe(() => verwijderUitzondering(u.id), "Afwijkende dag verwijderd."), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, u.id))) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { type: "date", value: datum, onChange: (e) => setDatum(e.target.value), className: "w-44" }), _jsx(Button, { variant: "outline", size: "sm", disabled: !datum, onClick: () => void doe(() => voegUitzonderingToe(datum, true, null, null, null), "Dag als gesloten gezet."), children: "Gesloten" }), _jsxs(Button, { size: "sm", disabled: !datum, onClick: () => void doe(() => voegUitzonderingToe(datum, false, van, tot, null), "Afwijkende openingstijd toegevoegd."), children: ["Afwijkend ", van, "\u2013", tot] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Een afwijkende dag gaat altijd boven de vaste weekinstelling." })] }), melding ? _jsx("p", { className: "text-sm", children: melding }) : null] })) })] }));
|
|
37
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WerkwijzeHerindelingCard.d.ts","sourceRoot":"","sources":["../../src/components/WerkwijzeHerindelingCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WerkwijzeHerindelingCard.d.ts","sourceRoot":"","sources":["../../src/components/WerkwijzeHerindelingCard.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,gCA+RvC"}
|
|
@@ -7,30 +7,124 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
// Engine-functies; hier wordt niets zelf berekend.
|
|
8
8
|
// GEEN <Header /> hier — die levert de shell.
|
|
9
9
|
// ============================================================================
|
|
10
|
-
import { useState } from "react";
|
|
11
|
-
import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
|
|
12
|
-
import { RefreshCw, RotateCcw, Ruler, Boxes } from "lucide-react";
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
11
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
|
|
12
|
+
import { RefreshCw, RotateCcw, Ruler, Boxes, AlertTriangle } from "lucide-react";
|
|
13
13
|
import { useWerkwijzeStatus } from "../hooks/useWerkwijzeStatus";
|
|
14
14
|
const METHODE_LABEL = {
|
|
15
15
|
belading: "Indelen op basis van belading",
|
|
16
16
|
afmetingen: "Indelen op basis van afmetingen",
|
|
17
17
|
};
|
|
18
18
|
export function WerkwijzeHerindelingCard() {
|
|
19
|
-
const { status, loading, busy, error, refresh, herindelen } = useWerkwijzeStatus();
|
|
19
|
+
const { status, loading, busy, error, refresh, refreshQuiet, herindelen, alsnogAllesHerindelen, pakbonnenGereedmaken, } = useWerkwijzeStatus();
|
|
20
20
|
const [melding, setMelding] = useState(null);
|
|
21
|
+
const [totaal, setTotaal] = useState(0);
|
|
22
|
+
const [bevestigOpen, setBevestigOpen] = useState(false);
|
|
23
|
+
const [pakbonnen, setPakbonnen] = useState(null);
|
|
24
|
+
const [pakbonBezig, setPakbonBezig] = useState(false);
|
|
25
|
+
const quietRef = useRef(refreshQuiet);
|
|
26
|
+
quietRef.current = refreshQuiet;
|
|
27
|
+
const pakbonRef = useRef(pakbonnenGereedmaken);
|
|
28
|
+
pakbonRef.current = pakbonnenGereedmaken;
|
|
29
|
+
const draaitRef = useRef(false);
|
|
21
30
|
const wachtrij = status?.wachtrij ?? {};
|
|
22
31
|
const pending = Number(wachtrij["pending"] ?? 0);
|
|
32
|
+
const running = Number(wachtrij["running"] ?? 0);
|
|
23
33
|
const blocked = Number(wachtrij["geblokkeerd"] ?? 0);
|
|
34
|
+
const openWerk = pending + running;
|
|
35
|
+
const bezigMetIndelen = totaal > 0 && openWerk > 0;
|
|
36
|
+
const gereed = totaal > 0 ? Math.max(0, Math.min(totaal, totaal - openWerk)) : 0;
|
|
37
|
+
const percentage = totaal > 0 ? Math.round((gereed / totaal) * 100) : 0;
|
|
38
|
+
const pakbonPercentage = pakbonnen && pakbonnen.totaal > 0
|
|
39
|
+
? Math.round((pakbonnen.gereed / pakbonnen.totaal) * 100)
|
|
40
|
+
: 0;
|
|
41
|
+
// Pakbongegevens klaarzetten; herhaald aanroepen is veilig en idempotent.
|
|
42
|
+
const pakbonRonde = useCallback(async () => {
|
|
43
|
+
if (draaitRef.current)
|
|
44
|
+
return;
|
|
45
|
+
draaitRef.current = true;
|
|
46
|
+
setPakbonBezig(true);
|
|
47
|
+
try {
|
|
48
|
+
setPakbonnen(await pakbonRef.current());
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Een mislukte ronde blokkeert de herindeling niet; volgende ronde probeert opnieuw.
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
draaitRef.current = false;
|
|
55
|
+
setPakbonBezig(false);
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
58
|
+
// Zolang er nog orders in de wachtrij staan, de stand stil bijwerken en
|
|
59
|
+
// ondertussen de pakbonnen van die orders alvast printklaar maken.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const nogPakbonnenOpen = (pakbonnen?.open ?? 0) > 0;
|
|
62
|
+
if (!bezigMetIndelen && !nogPakbonnenOpen)
|
|
63
|
+
return;
|
|
64
|
+
const t = setInterval(() => {
|
|
65
|
+
void quietRef.current();
|
|
66
|
+
void pakbonRonde();
|
|
67
|
+
}, 3000);
|
|
68
|
+
return () => clearInterval(t);
|
|
69
|
+
}, [bezigMetIndelen, pakbonnen?.open, pakbonRonde]);
|
|
70
|
+
// De balk verdwijnt vanzelf zodra alles klaar is, zodat hij nooit blijft staan.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (totaal === 0)
|
|
73
|
+
return;
|
|
74
|
+
const nogPakbonnenOpen = (pakbonnen?.open ?? 0) > 0;
|
|
75
|
+
if (bezigMetIndelen || nogPakbonnenOpen)
|
|
76
|
+
return;
|
|
77
|
+
const t = setTimeout(() => {
|
|
78
|
+
setTotaal(0);
|
|
79
|
+
setPakbonnen(null);
|
|
80
|
+
setMelding(null);
|
|
81
|
+
}, 6000);
|
|
82
|
+
return () => clearTimeout(t);
|
|
83
|
+
}, [totaal, bezigMetIndelen, pakbonnen?.open]);
|
|
84
|
+
// Harde bovengrens: duurt het te lang, dan stopt de balk met een leesbare melding.
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (totaal === 0)
|
|
87
|
+
return;
|
|
88
|
+
const t = setTimeout(() => {
|
|
89
|
+
setTotaal(0);
|
|
90
|
+
setPakbonnen(null);
|
|
91
|
+
setMelding("Het opnieuw indelen duurt langer dan verwacht. De achtergrondverwerking loopt door; klik op Verversen voor de actuele stand.");
|
|
92
|
+
}, 5 * 60 * 1000);
|
|
93
|
+
return () => clearTimeout(t);
|
|
94
|
+
}, [totaal]);
|
|
24
95
|
const opnieuw = async () => {
|
|
25
96
|
try {
|
|
26
97
|
const n = await herindelen();
|
|
98
|
+
setTotaal(n);
|
|
27
99
|
setMelding(n === 0
|
|
28
100
|
? "Er stonden geen orders open om opnieuw in te delen."
|
|
29
|
-
: `${n} order(s) opnieuw aangemeld. De indeling
|
|
101
|
+
: `${n} order(s) opnieuw aangemeld. De indeling en de pakbonnen worden op de achtergrond klaargezet.`);
|
|
102
|
+
void pakbonRonde();
|
|
30
103
|
}
|
|
31
104
|
catch (e) {
|
|
105
|
+
setTotaal(0);
|
|
32
106
|
setMelding(e?.message ? String(e.message) : "Opnieuw indelen is niet gelukt.");
|
|
33
107
|
}
|
|
34
108
|
};
|
|
35
|
-
|
|
109
|
+
// Noodknop: alles opnieuw indelen, ook orders die al gepickt of geprint zijn.
|
|
110
|
+
const allesOpnieuwInclusiefGepickt = async () => {
|
|
111
|
+
setBevestigOpen(false);
|
|
112
|
+
try {
|
|
113
|
+
const r = await alsnogAllesHerindelen();
|
|
114
|
+
setTotaal(r.aangemeld);
|
|
115
|
+
setMelding(r.aangemeld === 0
|
|
116
|
+
? "Er stonden geen orders open om opnieuw in te delen."
|
|
117
|
+
: `${r.aangemeld} order(s) lopen de flow opnieuw af, vanaf "Order komt binnen". ${r.karrenVerwijderd} kar(ren) en ${r.pakbonnenVervallen} oude pakbon(nen) zijn vervallen; overal worden nieuwe pakbonnen met nieuwe barcodes klaargezet.`);
|
|
118
|
+
void pakbonRonde();
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
setTotaal(0);
|
|
122
|
+
setMelding(e?.message ? String(e.message) : "Volledig opnieuw indelen is niet gelukt.");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [status?.methode === "afmetingen" ? (_jsx(Ruler, { className: "h-4 w-4" })) : (_jsx(Boxes, { className: "h-4 w-4" })), "Werkwijze en herindeling"] }), _jsx(CardDescription, { children: "Zo worden karren op dit moment samengesteld. Verandert de methode, dan worden alle openstaande orders automatisch opnieuw ingedeeld." })] }), _jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] }), _jsxs(Button, { size: "sm", onClick: () => void opnieuw(), disabled: busy, children: [_jsx(RotateCcw, { className: "h-4 w-4" }), "Alles opnieuw indelen"] }), _jsxs(Button, { size: "sm", variant: "destructive", onClick: () => setBevestigOpen(true), disabled: busy, children: [_jsx(AlertTriangle, { className: "h-4 w-4" }), "Alle orders opnieuw indelen, ook gepickte orders"] })] })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: error })) : status ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { children: METHODE_LABEL[status.methode] ?? status.methode }), status.actieveWerkwijze ? (_jsxs("span", { className: "text-sm text-muted-foreground", children: ["volgens werkwijze \u201C", status.actieveWerkwijze, "\u201D"] })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "Er staat geen werkwijze aan; er wordt op belading ingedeeld." }))] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren volgens deze methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenVolgensMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren van een oudere methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenOudeMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Wacht op indelen" }), _jsx("p", { className: "text-lg font-semibold", children: openWerk }), running > 0 ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["waarvan ", running, " nu bezig"] })) : null, blocked > 0 ? (_jsxs("p", { className: "text-xs text-destructive", children: [blocked, " geblokkeerd"] })) : null] })] }), status.karrenOudeMethode > 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Deze karren zijn nog met een eerdere methode berekend. Ze worden vervangen zodra de order opnieuw is ingedeeld; al geprinte of gecontroleerde karren blijven staan." })) : null, melding ? _jsx("p", { className: "text-sm", children: melding }) : null, totaal > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-muted", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percentage, "aria-label": "Voortgang opnieuw indelen", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-500", style: { width: `${percentage}%` } }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: bezigMetIndelen
|
|
126
|
+
? `${gereed} van ${totaal} order(s) ingedeeld — ${percentage}%`
|
|
127
|
+
: `Klaar: ${totaal} order(s) opnieuw ingedeeld.` })] })) : null, pakbonnen && pakbonnen.totaal > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-muted", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pakbonPercentage, "aria-label": "Voortgang pakbonnen klaarzetten", children: _jsx("div", { className: "h-full rounded-full bg-primary/70 transition-all duration-500", style: { width: `${pakbonPercentage}%` } }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: pakbonnen.open > 0 || pakbonBezig
|
|
128
|
+
? `Pakbonnen klaarzetten: ${pakbonnen.gereed} van ${pakbonnen.totaal} printklaar — ${pakbonPercentage}%`
|
|
129
|
+
: `Alle ${pakbonnen.totaal} pakbon(nen) staan printklaar.` })] })) : null] })) : null }), _jsx(AlertDialog, { open: bevestigOpen, onOpenChange: setBevestigOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Weet je het zeker?" }), _jsx(AlertDialogDescription, { children: "Alle openstaande orders worden opnieuw ingedeeld, ook orders die al gepickt of geprint zijn. Alle oude pakbonnen en barcodes vervallen en er worden overal nieuwe aangemaakt. Reeds geprinte pakbonnen kunnen daarna niet meer worden gescand. Dit kan niet ongedaan worden gemaakt." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => void allesOpnieuwInclusiefGepickt(), children: "Ja, alles opnieuw indelen" })] })] }) })] }));
|
|
36
130
|
}
|