@flowselections/logistiek-engine 1.0.34 → 1.0.89
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/dist-lib/components/AfrondWachtrijCard.d.ts +2 -0
- package/dist-lib/components/AfrondWachtrijCard.d.ts.map +1 -0
- package/dist-lib/components/AfrondWachtrijCard.js +27 -0
- package/dist-lib/components/BeladingsafwijkingenCard.d.ts +2 -0
- package/dist-lib/components/BeladingsafwijkingenCard.d.ts.map +1 -0
- package/dist-lib/components/BeladingsafwijkingenCard.js +44 -0
- package/dist-lib/components/FlowCanvas.d.ts +34 -0
- package/dist-lib/components/FlowCanvas.d.ts.map +1 -0
- package/dist-lib/components/FlowCanvas.js +490 -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 +171 -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 +310 -0
- package/dist-lib/components/GeblokkeerdeOrdersCard.d.ts.map +1 -1
- package/dist-lib/components/GeblokkeerdeOrdersCard.js +21 -2
- 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/KardekkingCard.d.ts +2 -0
- package/dist-lib/components/KardekkingCard.d.ts.map +1 -0
- package/dist-lib/components/KardekkingCard.js +29 -0
- package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -1
- package/dist-lib/components/LogistiekEnginePage.js +15 -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 +13 -0
- package/dist-lib/components/NodeTracePanel.d.ts +14 -0
- package/dist-lib/components/NodeTracePanel.d.ts.map +1 -0
- package/dist-lib/components/NodeTracePanel.js +46 -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 +12 -0
- package/dist-lib/components/OrderStopBanner.d.ts.map +1 -0
- package/dist-lib/components/OrderStopBanner.js +36 -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 +88 -0
- package/dist-lib/components/PlanWachtrijPage.d.ts +2 -0
- package/dist-lib/components/PlanWachtrijPage.d.ts.map +1 -0
- package/dist-lib/components/PlanWachtrijPage.js +99 -0
- package/dist-lib/components/PlannerDiffCard.d.ts +2 -0
- package/dist-lib/components/PlannerDiffCard.d.ts.map +1 -0
- package/dist-lib/components/PlannerDiffCard.js +25 -0
- package/dist-lib/components/RegiePage.d.ts.map +1 -1
- package/dist-lib/components/RegiePage.js +28 -78
- package/dist-lib/components/TaakGezondheidCard.d.ts +2 -0
- package/dist-lib/components/TaakGezondheidCard.d.ts.map +1 -0
- package/dist-lib/components/TaakGezondheidCard.js +31 -0
- 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/useAfrondWachtrij.d.ts +28 -0
- package/dist-lib/hooks/useAfrondWachtrij.d.ts.map +1 -0
- package/dist-lib/hooks/useAfrondWachtrij.js +88 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.d.ts +39 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.d.ts.map +1 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.js +119 -0
- package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts +7 -0
- package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts.map +1 -1
- package/dist-lib/hooks/useGeblokkeerdeOrders.js +28 -2
- 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 +194 -0
- package/dist-lib/hooks/usePlanWachtrij.d.ts +33 -0
- package/dist-lib/hooks/usePlanWachtrij.d.ts.map +1 -0
- package/dist-lib/hooks/usePlanWachtrij.js +104 -0
- package/dist-lib/hooks/useRecalcJob.js +1 -1
- 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/useShippingNoteDiagnose.d.ts +20 -0
- package/dist-lib/hooks/useShippingNoteDiagnose.d.ts.map +1 -0
- package/dist-lib/hooks/useShippingNoteDiagnose.js +52 -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/useTaakGezondheid.d.ts +16 -0
- package/dist-lib/hooks/useTaakGezondheid.d.ts.map +1 -0
- package/dist-lib/hooks/useTaakGezondheid.js +43 -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 +61 -5
- package/dist-lib/index.d.ts +23 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +23 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +4993 -459
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +4993 -459
- 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 +4993 -459
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +8 -1
- package/dist-lib/integrations/supabase/types.d.ts +5022 -432
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/attachAuthToken.js +1 -1
- package/dist-lib/lib/bufferDecision.test.d.ts +2 -0
- package/dist-lib/lib/bufferDecision.test.d.ts.map +1 -0
- package/dist-lib/lib/bufferDecision.test.js +104 -0
- package/dist-lib/lib/cartCoverage.functions.d.ts +30820 -0
- package/dist-lib/lib/cartCoverage.functions.d.ts.map +1 -0
- package/dist-lib/lib/cartCoverage.functions.js +20 -0
- 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 +13623 -21
- package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayDocuments.functions.js +5 -5
- 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 +4993 -459
- package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayPush.functions.js +3 -3
- 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/flowJourney.d.ts +40 -0
- package/dist-lib/lib/flowJourney.d.ts.map +1 -0
- package/dist-lib/lib/flowJourney.js +156 -0
- package/dist-lib/lib/flowNodes.d.ts +26 -0
- package/dist-lib/lib/flowNodes.d.ts.map +1 -0
- package/dist-lib/lib/flowNodes.js +64 -0
- package/dist-lib/lib/flowRoute.d.ts +117 -0
- package/dist-lib/lib/flowRoute.d.ts.map +1 -0
- package/dist-lib/lib/flowRoute.js +583 -0
- package/dist-lib/lib/flowTree.d.ts +149 -0
- package/dist-lib/lib/flowTree.d.ts.map +1 -0
- package/dist-lib/lib/flowTree.js +908 -0
- package/dist-lib/lib/foutTekst.d.ts +4 -0
- package/dist-lib/lib/foutTekst.d.ts.map +1 -0
- package/dist-lib/lib/foutTekst.js +43 -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 +2 -0
- package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
- package/dist-lib/lib/handlers/logistics-tick.server.js +153 -2
- package/dist-lib/lib/handlers/logistics-tick.test.d.ts +2 -0
- package/dist-lib/lib/handlers/logistics-tick.test.d.ts.map +1 -0
- package/dist-lib/lib/handlers/logistics-tick.test.js +63 -0
- 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 +22705 -35
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.functions.js +7 -7
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +33 -12
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +534 -228
- 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 +9082 -14
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalcJob.functions.js +4 -4
- package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalcJob.server.js +17 -2
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts +4993 -459
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalculate.functions.js +2 -2
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
- package/dist-lib/lib/modulePrompts.js +893 -0
- package/dist-lib/lib/nodeTrace.d.ts +65 -0
- package/dist-lib/lib/nodeTrace.d.ts.map +1 -0
- package/dist-lib/lib/nodeTrace.js +182 -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 +164 -3
- package/dist-lib/lib/plannerDiff.d.ts +37 -0
- package/dist-lib/lib/plannerDiff.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.functions.d.ts +30821 -0
- package/dist-lib/lib/plannerDiff.functions.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.functions.js +21 -0
- package/dist-lib/lib/plannerDiff.js +74 -0
- package/dist-lib/lib/plannerDiff.server.d.ts +5 -0
- package/dist-lib/lib/plannerDiff.server.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.server.js +60 -0
- package/dist-lib/lib/plannerDiff.test.d.ts +2 -0
- package/dist-lib/lib/plannerDiff.test.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.test.js +41 -0
- package/dist-lib/lib/resolveBuffer.test.d.ts +2 -0
- package/dist-lib/lib/resolveBuffer.test.d.ts.map +1 -0
- package/dist-lib/lib/resolveBuffer.test.js +38 -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 +102 -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/stopReden.d.ts +43 -0
- package/dist-lib/lib/stopReden.d.ts.map +1 -0
- package/dist-lib/lib/stopReden.js +197 -0
- package/dist-lib/lib/taskCounts.d.ts +2 -0
- package/dist-lib/lib/taskCounts.d.ts.map +1 -0
- package/dist-lib/lib/taskCounts.js +15 -0
- package/dist-lib/lib/taskCounts.test.d.ts +2 -0
- package/dist-lib/lib/taskCounts.test.d.ts.map +1 -0
- package/dist-lib/lib/taskCounts.test.js +12 -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 +78 -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 +133 -0
- package/package.json +158 -131
- 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,171 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/FlowOrders.tsx — orders en herberekeningen onder de flowkaart
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Alle orders die het systeem kent. Klik een order: alle herberekeningen
|
|
6
|
+
// (versies). Klik een versie: de gelopen route wordt blauw op de kaart, en
|
|
7
|
+
// hieronder staat elke gebeurtenis, beslissing, foutcode en document van die
|
|
8
|
+
// versie. Alleen lezen. GEEN <Header /> hier — die levert de shell.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Skeleton, } from "@flowselections/core";
|
|
12
|
+
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Clock, RefreshCw, RotateCcw, Search, X } from "lucide-react";
|
|
13
|
+
import { toast } from "sonner";
|
|
14
|
+
import { supabase } from "../integrations/supabase/moduleClient.js";
|
|
15
|
+
import { useAlleOrders, useOrderVersies, useVersieEvents } from "../hooks/useOrderVersies";
|
|
16
|
+
import { bepaalRoute, redenTekst } from "../lib/flowRoute";
|
|
17
|
+
import { Gebeurtenis } from "./OrderLogboek";
|
|
18
|
+
function tijd(v) {
|
|
19
|
+
if (!v)
|
|
20
|
+
return "—";
|
|
21
|
+
const d = new Date(v);
|
|
22
|
+
if (Number.isNaN(d.getTime()))
|
|
23
|
+
return v;
|
|
24
|
+
return d.toLocaleString("nl-NL", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
25
|
+
}
|
|
26
|
+
function duur(a, b) {
|
|
27
|
+
if (!a || !b)
|
|
28
|
+
return null;
|
|
29
|
+
const ms = new Date(b).getTime() - new Date(a).getTime();
|
|
30
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
31
|
+
return null;
|
|
32
|
+
if (ms < 1000)
|
|
33
|
+
return `${ms} ms`;
|
|
34
|
+
if (ms < 60000)
|
|
35
|
+
return `${(ms / 1000).toFixed(1)} s`;
|
|
36
|
+
return `${Math.round(ms / 60000)} min`;
|
|
37
|
+
}
|
|
38
|
+
const VERSIE_STATUS = {
|
|
39
|
+
done: { label: "Ingedeeld", kleur: "default" },
|
|
40
|
+
running: { label: "Bezig", kleur: "secondary" },
|
|
41
|
+
pending: { label: "Wachtend", kleur: "secondary" },
|
|
42
|
+
failed: { label: "Mislukt", kleur: "destructive" },
|
|
43
|
+
geblokkeerd: { label: "Geblokkeerd", kleur: "destructive" },
|
|
44
|
+
};
|
|
45
|
+
function orderStand(r) {
|
|
46
|
+
if (r.cancelledAt)
|
|
47
|
+
return { label: "Geannuleerd", kleur: "outline" };
|
|
48
|
+
const q = (r.queueStatus ?? "").toLowerCase();
|
|
49
|
+
if (q === "failed" || q === "geblokkeerd")
|
|
50
|
+
return { label: q === "failed" ? "Mislukt" : "Geblokkeerd", kleur: "destructive" };
|
|
51
|
+
if (r.errors > 0 && r.lastStatus === "mislukt")
|
|
52
|
+
return { label: "Fout", kleur: "destructive" };
|
|
53
|
+
if (q === "running" || q === "pending")
|
|
54
|
+
return { label: q === "running" ? "Bezig" : "Wachtend", kleur: "secondary" };
|
|
55
|
+
if (r.orderStatus)
|
|
56
|
+
return { label: r.orderStatus, kleur: "default" };
|
|
57
|
+
return { label: "Onbekend", kleur: "outline" };
|
|
58
|
+
}
|
|
59
|
+
export function FlowOrders({ onRoute }) {
|
|
60
|
+
const [zoek, setZoek] = useState("");
|
|
61
|
+
const [zoekActief, setZoekActief] = useState("");
|
|
62
|
+
const [filter, setFilter] = useState("");
|
|
63
|
+
const [sortering, setSortering] = useState("nieuw");
|
|
64
|
+
const [orderRef, setOrderRef] = useState(null);
|
|
65
|
+
const [versieId, setVersieId] = useState(null);
|
|
66
|
+
const [herbezig, setHerbezig] = useState(false);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const t = setTimeout(() => setZoekActief(zoek.trim()), 300);
|
|
69
|
+
return () => clearTimeout(t);
|
|
70
|
+
}, [zoek]);
|
|
71
|
+
const { rows, loading, error, refresh } = useAlleOrders(zoekActief);
|
|
72
|
+
const veiligeRows = Array.isArray(rows) ? rows : [];
|
|
73
|
+
const gefilterd = useMemo(() => {
|
|
74
|
+
const lijst = veiligeRows.filter((r) => {
|
|
75
|
+
if (!filter)
|
|
76
|
+
return true;
|
|
77
|
+
const st = orderStand(r);
|
|
78
|
+
if (filter === "fout")
|
|
79
|
+
return st.kleur === "destructive";
|
|
80
|
+
if (filter === "bezig")
|
|
81
|
+
return st.kleur === "secondary";
|
|
82
|
+
return st.kleur === "default" || st.kleur === "outline";
|
|
83
|
+
});
|
|
84
|
+
const stempel = (r) => {
|
|
85
|
+
const t = Date.parse(String(r.createdAt ?? r.lastAt ?? ""));
|
|
86
|
+
return Number.isFinite(t) ? t : 0;
|
|
87
|
+
};
|
|
88
|
+
return [...lijst].sort((a, b) => (sortering === "nieuw" ? stempel(b) - stempel(a) : stempel(a) - stempel(b)));
|
|
89
|
+
}, [veiligeRows, filter, sortering]);
|
|
90
|
+
const versies = useOrderVersies(orderRef);
|
|
91
|
+
const veiligeVersies = Array.isArray(versies.versions) ? versies.versions : [];
|
|
92
|
+
const gekozen = veiligeVersies.find((v) => v.id === versieId) ?? null;
|
|
93
|
+
const laatsteId = veiligeVersies[0]?.id ?? null;
|
|
94
|
+
// Route naar de kaart sturen zodra order/versie verandert.
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!orderRef) {
|
|
97
|
+
onRoute(null, null, null, null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (versies.loading)
|
|
101
|
+
return;
|
|
102
|
+
const v = gekozen ?? (versieId ? null : veiligeVersies[0] ?? null);
|
|
103
|
+
const route = bepaalRoute(v, versies.order, versies.carts, !v || v.id === laatsteId);
|
|
104
|
+
const label = v ? `${orderRef} · versie ${v.nr}${v.id === laatsteId ? " (laatste)" : ""}` : orderRef;
|
|
105
|
+
onRoute(route, label, orderRef, versieId ? v : null);
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, [orderRef, versieId, versies.loading, versies.order, versies.carts, veiligeVersies.length]);
|
|
108
|
+
const kies = (nr) => {
|
|
109
|
+
setVersieId(null);
|
|
110
|
+
setOrderRef((v) => (v === nr ? null : nr));
|
|
111
|
+
};
|
|
112
|
+
// Eén order helemaal opnieuw vanaf "Order komt binnen" door de flow laten lopen.
|
|
113
|
+
const opnieuwDoorFlow = async (nr) => {
|
|
114
|
+
setHerbezig(true);
|
|
115
|
+
try {
|
|
116
|
+
const { data, error: rpcError } = await supabase.rpc("logistics_replan_order_journey", {
|
|
117
|
+
p_order_ref: nr,
|
|
118
|
+
});
|
|
119
|
+
if (rpcError)
|
|
120
|
+
throw new Error(rpcError.message);
|
|
121
|
+
const r = (data ?? {});
|
|
122
|
+
if (r?.gevonden === false) {
|
|
123
|
+
toast.error(`Order ${nr} is niet gevonden.`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
toast.success(`${nr} begint opnieuw vanaf "Order komt binnen"`, {
|
|
127
|
+
description: `Oude route gewist (${r?.oudeStappenGewist ?? 0} stappen, ${r?.oudeBerekeningenGewist ?? 0} eerdere berekeningen). ${r?.karrenVerwijderd ?? 0} karren opnieuw te berekenen.`,
|
|
128
|
+
});
|
|
129
|
+
setVersieId(null);
|
|
130
|
+
onRoute(null, null, null, null);
|
|
131
|
+
try {
|
|
132
|
+
await supabase.rpc("logistics_trace_progress_order", { p_order_ref: nr });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* meekijken mag nooit blokkeren */
|
|
136
|
+
}
|
|
137
|
+
await refresh();
|
|
138
|
+
await versies.refresh();
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
toast.error("Opnieuw berekenen is niet gelukt", { description: e?.message ? String(e.message) : undefined });
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
setHerbezig(false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { className: "text-base", children: "Orders in het systeem" }), _jsx(CardDescription, { children: "Klik een order voor alle herberekeningen; klik een versie om de gelopen route blauw op de kaart te zien en precies te lezen waar hij is vastgelopen." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: `mr-1 h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }), "Vernieuwen"] })] }), _jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative w-full max-w-xs", children: [_jsx(Search, { className: "pointer-events-none absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" }), _jsx(Input, { className: "pl-8", placeholder: "Zoek op ordernummer of klant", value: zoek, onChange: (e) => setZoek(e.target.value) })] }), [
|
|
148
|
+
["", "Alles"],
|
|
149
|
+
["fout", "Vastgelopen"],
|
|
150
|
+
["bezig", "Bezig / wachtend"],
|
|
151
|
+
["klaar", "Overig"],
|
|
152
|
+
].map(([w, l]) => (_jsx(Button, { size: "sm", variant: filter === w ? "default" : "outline", onClick: () => setFilter(w), children: l }, w))), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setSortering((s) => (s === "nieuw" ? "oud" : "nieuw")), children: sortering === "nieuw" ? "Nieuwste eerst" : "Oudste eerst" }), _jsxs("span", { className: "ml-auto text-xs text-muted-foreground", children: [gefilterd.length, " orders"] })] })] }), _jsxs(CardContent, { className: "space-y-3", children: [error && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2", children: [_jsx("p", { className: "text-sm text-destructive", children: error }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: `mr-1 h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }), "Opnieuw proberen"] })] })), loading && veiligeRows.length === 0 ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" })] })) : gefilterd.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen orders gevonden." })) : (_jsx("div", { className: "max-h-[420px] overflow-y-auto rounded-md border border-border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "sticky top-0 bg-muted/60 text-xs text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Order" }), _jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Klant" }), _jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Bron" }), _jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Levering" }), _jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Stand" }), _jsx("th", { className: "px-2 py-1.5 text-right font-medium", children: "Versies" }), _jsx("th", { className: "px-2 py-1.5 text-right font-medium", children: "Fouten" }), _jsx("th", { className: "px-2 py-1.5 text-left font-medium", children: "Laatste activiteit" })] }) }), _jsx("tbody", { children: gefilterd.map((r) => {
|
|
153
|
+
const st = orderStand(r);
|
|
154
|
+
const actief = r.orderNumber === orderRef;
|
|
155
|
+
return (_jsxs("tr", { className: `cursor-pointer border-t border-border hover:bg-muted/40 ${actief ? "bg-primary/10" : ""}`, onClick: () => kies(r.orderNumber), children: [_jsx("td", { className: "px-2 py-1.5 font-medium", children: _jsxs("span", { className: "inline-flex items-center gap-1", children: [actief ? _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : _jsx(ChevronRight, { className: "h-3.5 w-3.5" }), r.orderNumber] }) }), _jsx("td", { className: "max-w-[180px] truncate px-2 py-1.5", children: r.customerName ?? "—" }), _jsx("td", { className: "px-2 py-1.5", children: r.orderSource === "floriday" ? "Floriday" : r.orderSource ? "Eigen verkoop" : "—" }), _jsx("td", { className: "px-2 py-1.5", children: r.deliveryDate ?? "—" }), _jsxs("td", { className: "px-2 py-1.5", children: [_jsx(Badge, { variant: st.kleur, children: st.label }), r.reasonCode && st.kleur === "destructive" && (_jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: redenTekst(r.reasonCode) }))] }), _jsx("td", { className: "px-2 py-1.5 text-right tabular-nums", children: r.versions }), _jsx("td", { className: `px-2 py-1.5 text-right tabular-nums ${r.errors > 0 ? "text-destructive" : ""}`, children: r.errors }), _jsx("td", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: tijd(r.lastAt ?? r.createdAt) })] }, r.orderNumber));
|
|
156
|
+
}) })] }) })), orderRef && (_jsxs("div", { className: "space-y-3 rounded-md border border-primary/40 bg-primary/5 p-3", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: orderRef }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [versies.order?.customerName ?? "", versies.order?.status ? ` · status: ${versies.order.status}` : "", versies.carts && versies.carts.total > 0
|
|
157
|
+
? ` · karren: ${versies.carts.total} (${versies.carts.printed} geprint, ${versies.carts.controlled} gecontroleerd)`
|
|
158
|
+
: "", versies.order?.planLockedAt ? " · indeling bevroren" : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { size: "sm", onClick: () => void opnieuwDoorFlow(orderRef), disabled: herbezig, children: [_jsx(RotateCcw, { className: "mr-1 h-4 w-4" }), herbezig ? "Bezig…" : "Opnieuw door de flowchart"] }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Order sluiten", onClick: () => kies(orderRef), children: _jsx(X, { className: "h-4 w-4" }) })] })] }), versies.error && _jsx("p", { className: "text-sm text-destructive", children: versies.error }), versies.loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : veiligeVersies.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen herberekening vastgelegd voor deze order. De kaart toont waar de order nu staat." })) : (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("p", { className: "text-xs font-medium text-muted-foreground", children: [veiligeVersies.length, " herberekening", veiligeVersies.length === 1 ? "" : "en", " \u2014 nieuwste bovenaan"] }), veiligeVersies.map((v) => (_jsx(VersieRij, { versie: v, laatste: v.id === laatsteId, gekozen: (gekozen?.id ?? (versieId ? null : laatsteId)) === v.id, onKies: () => setVersieId((cur) => (cur === v.id ? null : v.id)) }, v.id)))] })), (gekozen ?? (versieId ? null : veiligeVersies[0])) && (_jsx(VersieDetail, { orderRef: orderRef, versie: (gekozen ?? veiligeVersies[0]), route: bepaalRoute(gekozen ?? veiligeVersies[0], versies.order, versies.carts, (gekozen ?? veiligeVersies[0])?.id === laatsteId) }))] }))] })] }));
|
|
159
|
+
}
|
|
160
|
+
function VersieRij({ versie, laatste, gekozen, onKies, }) {
|
|
161
|
+
const st = VERSIE_STATUS[(versie.status ?? "pending").toLowerCase()] ?? { label: versie.status ?? "—", kleur: "outline" };
|
|
162
|
+
const d = duur(versie.claimedAt ?? versie.startedAt, versie.finishedAt);
|
|
163
|
+
return (_jsxs("button", { type: "button", onClick: onKies, className: `flex w-full flex-wrap items-center gap-2 rounded-md border px-2 py-1.5 text-left text-sm hover:bg-muted/40 ${gekozen ? "border-primary bg-background ring-1 ring-primary" : "border-border bg-background/60"}`, children: [_jsxs("span", { className: "w-20 font-medium", children: ["Versie ", versie.nr] }), _jsx(Badge, { variant: st.kleur, children: st.label }), laatste && _jsx(Badge, { variant: "outline", children: "laatste" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: [_jsx(Clock, { className: "mr-1 inline h-3 w-3" }), tijd(versie.startedAt), d ? ` · ${d}` : ""] }), versie.reason && !/^(insert|update|delete)$/i.test(versie.reason) && (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["aanleiding: ", versie.reason] })), _jsxs("span", { className: "ml-auto flex items-center gap-2 text-xs text-muted-foreground", children: [_jsxs("span", { children: [versie.events, " gebeurtenissen"] }), versie.printJobs > 0 && _jsxs("span", { children: [versie.printJobs, " print"] }), versie.errors > 0 && (_jsxs("span", { className: "text-destructive", children: [_jsx(AlertTriangle, { className: "mr-0.5 inline h-3 w-3" }), versie.errors] })), versie.attempts > 1 && _jsxs("span", { children: [versie.attempts, " pogingen"] })] }), (versie.reasonCode || versie.error) && (_jsx("p", { className: "w-full text-xs text-destructive", children: redenTekst(versie.reasonCode, versie.error) }))] }));
|
|
164
|
+
}
|
|
165
|
+
function VersieDetail({ orderRef, versie, route }) {
|
|
166
|
+
const { events, loading, error } = useVersieEvents(orderRef, versie);
|
|
167
|
+
const veilig = Array.isArray(events) ? events : [];
|
|
168
|
+
const [alles, setAlles] = useState(false);
|
|
169
|
+
const getoond = alles ? veilig : veilig.filter((e) => e.status !== "info" || e.phase !== "database");
|
|
170
|
+
return (_jsxs("div", { className: "space-y-2 rounded-md border border-border bg-background p-3", children: [_jsxs("div", { className: `flex items-start gap-2 rounded-md px-2 py-1.5 text-sm ${route.stuck ? "bg-destructive/10 text-destructive" : route.finished ? "bg-primary/10 text-primary" : "bg-muted text-foreground"}`, children: [route.stuck ? _jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }) : route.finished ? _jsx(CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0" }) : _jsx(Clock, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: route.summary }), _jsx("p", { className: "text-xs opacity-80", children: "Deze route is blauw op de kaart. De kaart toont de werkwijze van nu; welke keuze binnen een stap destijds gold is niet apart bewaard." })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-xs font-medium text-muted-foreground", children: ["Gebeurtenissen van versie ", versie.nr, " (", getoond.length, getoond.length !== veilig.length ? ` van ${veilig.length}` : "", ")"] }), veilig.length !== getoond.length || alles ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setAlles((v) => !v), children: alles ? "Alleen belangrijk" : "Alles tonen (ook databaseregels)" })) : null] }), error && _jsx("p", { className: "text-sm text-destructive", children: error }), loading ? (_jsx(Skeleton, { className: "h-20 w-full" })) : getoond.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen gebeurtenissen vastgelegd voor deze versie." })) : (_jsx("div", { className: "max-h-[520px] space-y-1 overflow-y-auto pr-1", children: getoond.map((e) => (_jsx(Gebeurtenis, { event: e }, e.id))) }))] }));
|
|
171
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowPage.d.ts","sourceRoot":"","sources":["../../src/components/FlowPage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,QAAQ,gCAitBvB"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/FlowPage.tsx — visuele flow van de Logistiek Engine
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Het schema wordt opgebouwd uit dezelfde werkwijze die de engine gebruikt
|
|
6
|
+
// (WIZARD_STEPS + de opgeslagen instellingen). Klik je een stap aan, dan pas
|
|
7
|
+
// je diezelfde instellingen aan; opslaan werkt door in de engine én in dit
|
|
8
|
+
// schema. GEEN <Header /> hier — die levert de shell.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Skeleton, toast, } from "@flowselections/core";
|
|
12
|
+
import { CheckCircle2, RefreshCw, Save, X } from "lucide-react";
|
|
13
|
+
import { fieldVisible, } from "../lib/ruleEngineSchema";
|
|
14
|
+
import { WIZARD_STEPS, groupSummary, isAdvancedField } from "../lib/ruleEngineWizard";
|
|
15
|
+
import { buildFlowTree, pasMergeOverridesToe, verbergEindnodeLijnen, verduidelijkVerzendbrief, mergeVoorwaarde, MERGE_MAX_WACHT_MINUTEN, } from "../lib/flowTree";
|
|
16
|
+
import { applyRoute } from "../lib/flowRoute";
|
|
17
|
+
import { ontbrekendeJourneyEvents } from "../lib/flowJourney";
|
|
18
|
+
import { FlowCanvas } from "./FlowCanvas";
|
|
19
|
+
import { FlowOrders } from "./FlowOrders";
|
|
20
|
+
import { OrderJourneyBar } from "./OrderJourneyBar";
|
|
21
|
+
import { OrderStopBanner } from "./OrderStopBanner";
|
|
22
|
+
import { useOrderNodeTrace } from "../hooks/useOrderNodeTrace";
|
|
23
|
+
import { useShippingNoteDiagnose } from "../hooks/useShippingNoteDiagnose";
|
|
24
|
+
import { stopReden, verzendbriefStop } from "../lib/stopReden";
|
|
25
|
+
import { useOrderTraceStand } from "../hooks/useOrderTrace";
|
|
26
|
+
import { useRuleEngine } from "../hooks/useRuleEngine";
|
|
27
|
+
import { useLogistiekStrategieen } from "../hooks/useLogistiekStrategieen";
|
|
28
|
+
import { useRecalcJob, phaseLabel } from "../hooks/useRecalcJob";
|
|
29
|
+
import { RuleFieldInput } from "./rule-engine/RuleFieldInput";
|
|
30
|
+
import { CollapsibleGroup } from "./rule-engine/CollapsibleGroup";
|
|
31
|
+
import { ConditionsEditor } from "./rule-engine/ConditionsEditor";
|
|
32
|
+
const WIDE_TYPES = ["priority", "toggle", "days", "radio", "textarea", "guidance"];
|
|
33
|
+
function fieldClass(field) {
|
|
34
|
+
return WIDE_TYPES.includes(field.type) ? "md:col-span-2" : "";
|
|
35
|
+
}
|
|
36
|
+
export function FlowPage() {
|
|
37
|
+
const { configs, activeConfig, activeId, setActiveId, loading, saving, error, patchSettings, saveConfig, activateConfig, } = useRuleEngine();
|
|
38
|
+
const { items: strategieen } = useLogistiekStrategieen();
|
|
39
|
+
const [openStep, setOpenStep] = useState(null);
|
|
40
|
+
// Naam van de werkwijze = de naam uit het Werkwijzen-overzicht (strategie),
|
|
41
|
+
// zodat beide schermen exact hetzelfde zeggen.
|
|
42
|
+
const werkwijzeNaam = (config) => {
|
|
43
|
+
if (!config)
|
|
44
|
+
return "";
|
|
45
|
+
const lijst = Array.isArray(strategieen) ? strategieen : [];
|
|
46
|
+
const match = config.strategy_id
|
|
47
|
+
? lijst.find((s) => s.id === config.strategy_id)
|
|
48
|
+
: undefined;
|
|
49
|
+
return match?.name ?? config.name.replace(/^Werkwijze\s+/i, "");
|
|
50
|
+
};
|
|
51
|
+
const settings = activeConfig?.settings ?? {};
|
|
52
|
+
// Handmatige keuzes op de samenkomstpunten (de ruitjes) horen bij de
|
|
53
|
+
// werkwijze, zodat de kaart voor iedereen hetzelfde blijft.
|
|
54
|
+
const mergeOverrides = useMemo(() => {
|
|
55
|
+
const raw = (settings?.["flow_weergave"]?.fields ?? {})["merges"];
|
|
56
|
+
return raw && typeof raw === "object" ? raw : {};
|
|
57
|
+
}, [settings]);
|
|
58
|
+
const ruweTree = useMemo(() => buildFlowTree(settings, mergeOverrides), [settings, mergeOverrides]);
|
|
59
|
+
const basisTree = useMemo(() => pasMergeOverridesToe(ruweTree, mergeOverrides), [ruweTree, mergeOverrides]);
|
|
60
|
+
const [mergeId, setMergeId] = useState(null);
|
|
61
|
+
const zetMergeOverride = (id, patch) => {
|
|
62
|
+
if (!activeConfig)
|
|
63
|
+
return;
|
|
64
|
+
const huidig = settings["flow_weergave"] ?? { fields: {}, conditions: [] };
|
|
65
|
+
const merges = { ...mergeOverrides, [id]: { ...(mergeOverrides[id] ?? {}), ...patch } };
|
|
66
|
+
patchSettings(activeConfig.id, {
|
|
67
|
+
...settings,
|
|
68
|
+
flow_weergave: { ...huidig, fields: { ...(huidig.fields ?? {}), merges } },
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
// Gekozen order/versie: de echte reis wordt als laag over de kaart gelegd.
|
|
72
|
+
const [orderRoute, setOrderRoute] = useState(null);
|
|
73
|
+
const [routeLabel, setRouteLabel] = useState(null);
|
|
74
|
+
const [orderRef, setOrderRef] = useState(null);
|
|
75
|
+
const [versie, setVersie] = useState(null);
|
|
76
|
+
const [traceNodeId, setTraceNodeId] = useState(null);
|
|
77
|
+
const kaartRef = useRef(null);
|
|
78
|
+
const onRoute = useCallback((route, label, ref, v) => {
|
|
79
|
+
setOrderRoute(route);
|
|
80
|
+
setRouteLabel(label);
|
|
81
|
+
setOrderRef(ref);
|
|
82
|
+
setVersie(v);
|
|
83
|
+
setTraceNodeId(null);
|
|
84
|
+
if (route && label) {
|
|
85
|
+
// De kaart staat boven de orderlijst: breng hem meteen in beeld.
|
|
86
|
+
setTimeout(() => kaartRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 50);
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
const { groepen, reis, loading: traceLoading } = useOrderNodeTrace(orderRef, versie);
|
|
90
|
+
const { stand: orderStand } = useOrderTraceStand(orderRef);
|
|
91
|
+
// Auditpunt 5: het openen van een order verandert nooit iets aan die order.
|
|
92
|
+
// De keuzevakjes die de Engine zelf niet vastlegt worden hier alléén afgeleid
|
|
93
|
+
// en getoond (herkenbaar als "automatisch afgeleid"). Het echte logboek wordt
|
|
94
|
+
// bijgewerkt door de Engine zelf: bij een herberekening en in elke planronde.
|
|
95
|
+
// Samenkomstpunten waar deze order wacht; daar loopt de route niet voorbij.
|
|
96
|
+
const wachtpuntenRef = useRef(new Set());
|
|
97
|
+
const [wachtpunten, setWachtpunten] = useState(() => new Set());
|
|
98
|
+
const alleGroepen = useMemo(() => {
|
|
99
|
+
if (!orderRef || traceLoading || groepen.size === 0)
|
|
100
|
+
return groepen;
|
|
101
|
+
const vastgelegd = new Set(groepen.keys());
|
|
102
|
+
const events = ontbrekendeJourneyEvents({
|
|
103
|
+
tree: basisTree,
|
|
104
|
+
orderRef,
|
|
105
|
+
orderSource: orderRoute?.source ?? null,
|
|
106
|
+
vastgelegd,
|
|
107
|
+
wachtpunten,
|
|
108
|
+
});
|
|
109
|
+
if (events.length === 0)
|
|
110
|
+
return groepen;
|
|
111
|
+
const samen = new Map(groepen);
|
|
112
|
+
for (const e of events) {
|
|
113
|
+
if (!e.nodeId || samen.has(e.nodeId))
|
|
114
|
+
continue;
|
|
115
|
+
samen.set(e.nodeId, {
|
|
116
|
+
nodeId: e.nodeId,
|
|
117
|
+
events: [],
|
|
118
|
+
aantal: 0,
|
|
119
|
+
eerste: null,
|
|
120
|
+
laatste: null,
|
|
121
|
+
duurMs: null,
|
|
122
|
+
status: "geslaagd",
|
|
123
|
+
pogingen: 0,
|
|
124
|
+
reden: e.reason ?? null,
|
|
125
|
+
errorClass: null,
|
|
126
|
+
volgendeStap: e.nextStep ?? null,
|
|
127
|
+
afgeleid: true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return samen;
|
|
131
|
+
}, [groepen, traceLoading, orderRef, basisTree, orderRoute?.source, wachtpunten]);
|
|
132
|
+
const overlay = useMemo(() => applyRoute(basisTree, orderRoute, orderRef ? alleGroepen : null, orderStand), [basisTree, orderRoute, alleGroepen, orderRef, orderStand]);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
// Wachtpunten komen uit de kaart en sturen de volgende afleiding aan.
|
|
135
|
+
const nieuw = overlay.wachtpunten;
|
|
136
|
+
const oud = wachtpuntenRef.current;
|
|
137
|
+
const gelijk = nieuw.size === oud.size && [...nieuw].every((id) => oud.has(id));
|
|
138
|
+
if (gelijk)
|
|
139
|
+
return;
|
|
140
|
+
wachtpuntenRef.current = nieuw;
|
|
141
|
+
setWachtpunten(new Set(nieuw));
|
|
142
|
+
}, [overlay]);
|
|
143
|
+
/** Het laatste vakje van de route, met de reden waarom het daar ophoudt. */
|
|
144
|
+
const eindpunt = useMemo(() => {
|
|
145
|
+
for (const [nodeId, m] of overlay.markers) {
|
|
146
|
+
if (m.eindpunt && m.stop)
|
|
147
|
+
return { nodeId, stop: m.stop };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}, [overlay]);
|
|
151
|
+
// Zonder geselecteerde order: geen doorlopende lijnen onder een eindstation.
|
|
152
|
+
// Met een order: het verzendbriefvakje toont de tekst die bij die herkomst hoort.
|
|
153
|
+
const tree = useMemo(() => orderRoute
|
|
154
|
+
? verduidelijkVerzendbrief(overlay.tree, orderRoute.source)
|
|
155
|
+
: verbergEindnodeLijnen(overlay.tree), [overlay.tree, orderRoute]);
|
|
156
|
+
// Alleen-lezen: waarom wacht deze order nog op zijn verzendbrief?
|
|
157
|
+
const { diagnose: verzendbrief } = useShippingNoteDiagnose(orderRef);
|
|
158
|
+
const verzendbriefReden = useMemo(() => verzendbriefStop(verzendbrief), [verzendbrief]);
|
|
159
|
+
// Vakjes die vastlopen, met de reden erbij: die krijgen een foutmelding op de
|
|
160
|
+
// kaart en in het zijpaneel. Alleen weergave; er verandert niets aan de order.
|
|
161
|
+
const problemen = useMemo(() => {
|
|
162
|
+
const map = new Map();
|
|
163
|
+
if (!orderRef)
|
|
164
|
+
return map;
|
|
165
|
+
for (const [nodeId, groep] of alleGroepen) {
|
|
166
|
+
if (groep.status === "mislukt") {
|
|
167
|
+
map.set(nodeId, stopReden({ nodeId, groep, stand: orderStand }));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (verzendbriefReden) {
|
|
171
|
+
for (const n of tree.nodes) {
|
|
172
|
+
if (n.id.includes(":verzendbrief:") || n.id.endsWith(":verzendbrief")) {
|
|
173
|
+
map.set(n.id, verzendbriefReden);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return map;
|
|
178
|
+
}, [orderRef, alleGroepen, orderStand, verzendbriefReden, tree]);
|
|
179
|
+
// Gegevens voor het ruitje-paneel komen uit de kaart zonder overrides, zodat
|
|
180
|
+
// een weggehaald ruitje ook weer terug te zetten is.
|
|
181
|
+
const mergeNode = mergeId ? (ruweTree.nodes.find((n) => n.id === mergeId) ?? null) : null;
|
|
182
|
+
const mergeVast = !!mergeId && (mergeId.startsWith("samen:stap:") || mergeId === "__bron_merge");
|
|
183
|
+
const mergeWeg = !!mergeId && mergeOverrides[mergeId]?.verwijderd === true;
|
|
184
|
+
const naamVan = (id) => {
|
|
185
|
+
const n = ruweTree.nodes.find((x) => x.id === id);
|
|
186
|
+
if (!n)
|
|
187
|
+
return id;
|
|
188
|
+
if (n.label)
|
|
189
|
+
return n.label;
|
|
190
|
+
return n.kind === "merge" ? "samenkomst" : id;
|
|
191
|
+
};
|
|
192
|
+
const mergeLijnen = mergeId
|
|
193
|
+
? ruweTree.edges
|
|
194
|
+
.filter((e) => e.kind === "flow" && e.to === mergeId)
|
|
195
|
+
.map((e) => ({ id: e.id, naam: e.label ? `${naamVan(e.from)} — ${e.label}` : naamVan(e.from) }))
|
|
196
|
+
: [];
|
|
197
|
+
const mergeKeuze = mergeId ? mergeVoorwaarde(ruweTree, mergeId, mergeOverrides[mergeId]) : null;
|
|
198
|
+
// Altijd de opgeloste voorwaarde tonen: een opgeslagen "alle" die hier
|
|
199
|
+
// onmogelijk is, valt terug op "één" en wordt ook zo weergegeven.
|
|
200
|
+
const mergeWanneer = mergeKeuze?.wanneer ?? "een";
|
|
201
|
+
const mergeUitsluitend = mergeKeuze?.uitsluitend === true;
|
|
202
|
+
const alleOnmogelijkOpgeslagen = mergeUitsluitend && mergeOverrides[mergeId ?? ""]?.wanneer === "alle";
|
|
203
|
+
const weggehaaldeRuitjes = Object.entries(mergeOverrides)
|
|
204
|
+
.filter(([, k]) => k?.verwijderd)
|
|
205
|
+
.map(([id]) => id);
|
|
206
|
+
const step = WIZARD_STEPS.find((s) => s.id === openStep) ?? null;
|
|
207
|
+
const stepValues = step ? (settings?.[step.storeId]?.fields ?? {}) : {};
|
|
208
|
+
const stepConditions = step
|
|
209
|
+
? (settings?.[step.storeId]?.conditions ?? [])
|
|
210
|
+
: [];
|
|
211
|
+
const setFieldValue = (key, value) => {
|
|
212
|
+
if (!activeConfig || !step)
|
|
213
|
+
return;
|
|
214
|
+
patchSettings(activeConfig.id, {
|
|
215
|
+
...settings,
|
|
216
|
+
[step.storeId]: {
|
|
217
|
+
...(settings[step.storeId] ?? { fields: {}, conditions: [] }),
|
|
218
|
+
fields: { ...stepValues, [key]: value },
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
// Keuze veranderen vanuit een ruit in het schema (elke stap, niet alleen de
|
|
223
|
+
// geopende): schrijft naar dezelfde werkwijze die de engine gebruikt.
|
|
224
|
+
const setFieldByStore = (storeId, key, value) => {
|
|
225
|
+
if (!activeConfig)
|
|
226
|
+
return;
|
|
227
|
+
const huidig = settings[storeId] ?? { fields: {}, conditions: [] };
|
|
228
|
+
patchSettings(activeConfig.id, {
|
|
229
|
+
...settings,
|
|
230
|
+
[storeId]: { ...huidig, fields: { ...(huidig.fields ?? {}), [key]: value } },
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
const setConditions = (conditions) => {
|
|
234
|
+
if (!activeConfig || !step)
|
|
235
|
+
return;
|
|
236
|
+
patchSettings(activeConfig.id, {
|
|
237
|
+
...settings,
|
|
238
|
+
[step.storeId]: {
|
|
239
|
+
...(settings[step.storeId] ?? { fields: {}, conditions: [] }),
|
|
240
|
+
conditions,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
const { job, starting, startRecalculation } = useRecalcJob();
|
|
245
|
+
const running = job?.status === "running";
|
|
246
|
+
const [busy, setBusy] = useState(false);
|
|
247
|
+
const opslaan = async (opnieuwBerekenen) => {
|
|
248
|
+
if (!activeConfig)
|
|
249
|
+
return;
|
|
250
|
+
setBusy(true);
|
|
251
|
+
try {
|
|
252
|
+
await saveConfig(activeConfig);
|
|
253
|
+
if (opnieuwBerekenen) {
|
|
254
|
+
if (!activeConfig.is_active)
|
|
255
|
+
await activateConfig(activeConfig.id);
|
|
256
|
+
await startRecalculation(null);
|
|
257
|
+
toast.success("Opgeslagen — de herberekening loopt op de achtergrond");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
toast.error(e?.message ?? "Opslaan is niet gelukt. Probeer het nog een keer.");
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
setBusy(false);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const veiligeConfigs = Array.isArray(configs) ? configs : [];
|
|
268
|
+
return (_jsxs("div", { className: "space-y-4 p-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold tracking-tight", children: "Flow van de Logistiek Engine" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle mogelijke routes door de engine in \u00E9\u00E9n kaart. De gekleurde route geldt nu; kies in een beslistegel iets anders en de actieve tak springt direct mee." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [veiligeConfigs.length > 1 && (_jsxs(Select, { value: activeId ?? "", onValueChange: (v) => setActiveId(v), children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Kies een werkwijze" }) }), _jsx(SelectContent, { children: veiligeConfigs.map((c) => (_jsxs(SelectItem, { value: c.id, children: [werkwijzeNaam(c), c.is_active ? " (actief)" : ""] }, c.id))) })] })), _jsxs(Button, { variant: "outline", size: "sm", disabled: !activeConfig || saving || busy, onClick: () => void opslaan(false), children: [_jsx(Save, { className: "h-4 w-4" }), " Opslaan"] }), _jsxs(Button, { size: "sm", disabled: !activeConfig || saving || busy || starting || running, onClick: () => void opslaan(true), children: [busy || starting || running ? (_jsx(RefreshCw, { className: "h-4 w-4 animate-spin" })) : (_jsx(CheckCircle2, { className: "h-4 w-4" })), running ? `${phaseLabel(job?.phase)}…` : "Opslaan & opnieuw berekenen"] })] })] }), error && (_jsx("p", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: "De werkwijze kon niet worden geladen. Probeer het opnieuw." })), loading ? (_jsxs("div", { className: "space-y-3", children: [_jsx(Skeleton, { className: "h-24 w-full" }), _jsx(Skeleton, { className: "h-24 w-full" }), _jsx(Skeleton, { className: "h-24 w-full" })] })) : !activeConfig ? (_jsx(Card, { children: _jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Nog geen werkwijze" }), _jsx(CardDescription, { children: "Maak eerst een werkwijze aan bij Werkwijze instellen; daarna verschijnt de flow hier vanzelf." })] }) })) : (_jsxs("div", { className: `grid gap-6 ${openStep || mergeId ? "lg:grid-cols-[minmax(0,1fr)_420px]" : "lg:grid-cols-1"}`, children: [_jsxs("div", { className: "space-y-2", ref: kaartRef, children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["werkwijze: ", werkwijzeNaam(activeConfig), activeConfig.is_active ? " (actief)" : " (concept)"] }), orderRef && (_jsx(OrderStopBanner, { orderRef: orderRef, stop: eindpunt?.stop ?? null, stopNode: eindpunt?.nodeId ?? null, onNode: setTraceNodeId })), verzendbriefReden && (_jsxs("div", { className: `rounded-md border px-3 py-2 text-sm ${verzendbriefReden.soort === "mislukt"
|
|
269
|
+
? "border-destructive/50 bg-destructive/5"
|
|
270
|
+
: "border-amber-500/40 bg-amber-500/5"}`, children: [_jsxs("p", { className: "font-medium", children: [verzendbriefReden.titel, ": ", verzendbriefReden.reden] }), verzendbriefReden.feiten.length > 0 && (_jsx("p", { className: "text-muted-foreground", children: verzendbriefReden.feiten.join(" · ") })), verzendbriefReden.volgendeStap && (_jsxs("p", { className: "text-muted-foreground", children: ["Wat moet er gebeuren: ", verzendbriefReden.volgendeStap] }))] })), _jsx(FlowCanvas, { tree: tree, selectedStepId: openStep, settings: settings, markers: overlay.markers, problemen: problemen, nodeTrace: orderRef ? alleGroepen : null, routeLabel: routeLabel, routeSummary: orderRoute?.summary ?? null, traceNodeId: traceNodeId, onTraceNode: setTraceNodeId, onClearRoute: () => onRoute(null, null, null, null), mergeId: mergeId, onSelectMerge: (id) => setMergeId((v) => (v === id ? null : id)), onSelectStep: (stepId) => setOpenStep((v) => (v === stepId ? null : stepId)), onChangeChoice: setFieldByStore }), orderRef && (_jsx(OrderJourneyBar, { reis: reis, actiefNodeId: traceNodeId, onKies: setTraceNodeId })), weggehaaldeRuitjes.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [weggehaaldeRuitjes.length, " samenkomstpunt", weggehaaldeRuitjes.length === 1 ? "" : "en", " weggehaald.", " ", _jsx("button", { type: "button", className: "underline underline-offset-2", onClick: () => {
|
|
271
|
+
if (!activeConfig)
|
|
272
|
+
return;
|
|
273
|
+
const huidig = settings["flow_weergave"] ?? { fields: {}, conditions: [] };
|
|
274
|
+
const merges = {};
|
|
275
|
+
for (const [id, keuze] of Object.entries(mergeOverrides)) {
|
|
276
|
+
merges[id] = { ...keuze, verwijderd: false };
|
|
277
|
+
}
|
|
278
|
+
patchSettings(activeConfig.id, {
|
|
279
|
+
...settings,
|
|
280
|
+
flow_weergave: { ...huidig, fields: { ...(huidig.fields ?? {}), merges } },
|
|
281
|
+
});
|
|
282
|
+
}, children: "Allemaal terugzetten" })] })), orderRef && !traceLoading && reis.length === 0 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Voor deze order zijn nog geen stappen per vakje vastgelegd; de kaart toont dan de afgeleide route." }))] }), mergeNode && (_jsx("aside", { className: "space-y-3 lg:sticky lg:top-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { className: "text-base", children: "Samenkomstpunt" }), _jsx(CardDescription, { children: "Hier komen lijnen weer samen tot \u00E9\u00E9n route. Het punt zelf doet niets met een order." })] }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Paneel sluiten", onClick: () => setMergeId(null), children: _jsx(X, { className: "h-4 w-4" }) })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "space-y-2 rounded-md border border-border p-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Wanneer mag de flow verder?" }), _jsxs(Select, { value: mergeWanneer, onValueChange: (v) => {
|
|
283
|
+
if (v === "alle" && mergeUitsluitend)
|
|
284
|
+
return;
|
|
285
|
+
zetMergeOverride(mergeNode.id, {
|
|
286
|
+
wanneer: v,
|
|
287
|
+
...(v === "specifiek" && !mergeOverrides[mergeNode.id]?.verplichteLijn
|
|
288
|
+
? { verplichteLijn: mergeLijnen[0]?.id }
|
|
289
|
+
: {}),
|
|
290
|
+
});
|
|
291
|
+
}, children: [_jsx(SelectTrigger, { className: "h-9 w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "een", children: "Zodra \u00E9\u00E9n flow klaar is" }), _jsx(SelectItem, { value: "alle", disabled: mergeUitsluitend, children: "Pas als alle flows klaar zijn" }), _jsx(SelectItem, { value: "specifiek", children: "Als een specifieke flow klaar is" })] })] }), mergeUitsluitend && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Deze lijnen sluiten elkaar uit: een order komt maar via \u00E9\u00E9n van ze binnen. Wachten op alle flows kan hier dus nooit \u2014 die keuze is niet beschikbaar." })), alleOnmogelijkOpgeslagen && (_jsx("p", { className: "text-xs text-amber-700", children: "\u201CPas als alle flows klaar zijn\u201D was hier opgeslagen, maar is onmogelijk. Er is terugezet op: zodra \u00E9\u00E9n flow klaar is." })), mergeWanneer === "specifiek" && (_jsxs(Select, { value: mergeOverrides[mergeNode.id]?.verplichteLijn ?? "", onValueChange: (v) => zetMergeOverride(mergeNode.id, { verplichteLijn: v }), children: [_jsx(SelectTrigger, { className: "h-9 w-full", children: _jsx(SelectValue, { placeholder: "Kies de verplichte flow" }) }), _jsx(SelectContent, { children: mergeLijnen.map((l) => (_jsx(SelectItem, { value: l.id, children: l.naam }, l.id))) })] })), mergeWanneer !== "een" && (_jsxs("label", { className: "flex items-center justify-between gap-2 text-xs text-muted-foreground", children: [_jsx("span", { children: "Ga na zoveel minuten toch verder" }), _jsx("input", { type: "number", min: 1, className: "h-8 w-20 rounded-md border border-border bg-background px-2 text-right text-xs", value: mergeOverrides[mergeNode.id]?.maxWachtMinuten ?? MERGE_MAX_WACHT_MINUTEN, onChange: (e) => zetMergeOverride(mergeNode.id, {
|
|
292
|
+
maxWachtMinuten: Math.max(1, Number(e.target.value) || MERGE_MAX_WACHT_MINUTEN),
|
|
293
|
+
}) })] })), mergeKeuze?.terugval && (_jsx("p", { className: "text-xs text-amber-700", children: "De gekozen flow bestaat niet meer of is uitgeschakeld; zolang dat zo is, gaat de flow verder zodra \u00E9\u00E9n flow klaar is." })), _jsx("p", { className: "text-xs text-muted-foreground", children: "Lijnen die nu uitgeschakeld zijn of op \u201CStopt hier\u201D staan, tellen niet mee. Zo blijft de flow nooit wachten op een tak die toch nooit loopt." })] }), _jsx(Button, { variant: "outline", size: "sm", disabled: mergeVast, onClick: () => zetMergeOverride(mergeNode.id, { verwijderd: !mergeWeg }), children: mergeWeg ? "Ruitje terugzetten" : "Ruitje weghalen" }), mergeVast && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Dit punt houdt de doorgaande route tussen de stappen heel en kan niet worden weggehaald." })), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Binnenkomende lijnen" }), mergeLijnen.length === 0 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Er komt hier niets binnen." })), mergeLijnen.map((lijn) => {
|
|
294
|
+
const stand = mergeOverrides[mergeNode.id]?.lijnen?.[lijn.id] ?? "door";
|
|
295
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-2 rounded-md border border-border px-2 py-1.5", children: [_jsx("span", { className: "truncate text-xs", children: lijn.naam }), _jsxs(Select, { value: stand, onValueChange: (v) => zetMergeOverride(mergeNode.id, {
|
|
296
|
+
lijnen: {
|
|
297
|
+
...(mergeOverrides[mergeNode.id]?.lijnen ?? {}),
|
|
298
|
+
[lijn.id]: v,
|
|
299
|
+
},
|
|
300
|
+
}), children: [_jsx(SelectTrigger, { className: "h-8 w-36", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "door", children: "Gaat door" }), _jsx(SelectItem, { value: "stop", children: "Stopt hier" })] })] })] }, lijn.id));
|
|
301
|
+
}), _jsx("p", { className: "text-xs text-muted-foreground", children: "Staat een lijn op \u201CStopt hier\u201D, dan gaat een order die via die lijn binnenkomt niet verder; de route eindigt op dat punt." })] }), _jsx("div", { className: "flex flex-wrap gap-2 pt-1", children: _jsxs(Button, { variant: "outline", size: "sm", disabled: saving || busy, onClick: () => void opslaan(false), children: [_jsx(Save, { className: "h-4 w-4" }), " Opslaan"] }) })] })] }) })), step && (_jsx("aside", { className: "space-y-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-8rem)] lg:overflow-y-auto", children: _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { className: "text-base", children: step.label }), _jsx(CardDescription, { children: step.description })] }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Paneel sluiten", onClick: () => setOpenStep(null), children: _jsx(X, { className: "h-4 w-4" }) })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [step.groups.map((group, groupIndex) => {
|
|
302
|
+
const visibleFields = group.fields.filter((field) => fieldVisible(field, stepValues, settings));
|
|
303
|
+
if (visibleFields.length === 0)
|
|
304
|
+
return null;
|
|
305
|
+
const basicFields = visibleFields.filter((f) => !isAdvancedField(f));
|
|
306
|
+
const advancedFields = visibleFields.filter((f) => isAdvancedField(f));
|
|
307
|
+
const isLastGroup = groupIndex === step.groups.length - 1;
|
|
308
|
+
return (_jsxs(CollapsibleGroup, { title: group.label, help: group.description, summary: groupSummary(group, stepValues), defaultOpen: groupIndex === 0, children: [basicFields.length > 0 && (_jsx("div", { className: "grid gap-4 md:grid-cols-2", children: basicFields.map((field) => (_jsx("div", { className: fieldClass(field), children: _jsx(RuleFieldInput, { field: field, value: stepValues[field.key], stepValues: stepValues, onChange: (value) => setFieldValue(field.key, value) }) }, field.key))) })), (advancedFields.length > 0 || (step.showConditions && isLastGroup)) && (_jsxs("details", { className: "rounded-lg border border-dashed border-border", children: [_jsx("summary", { className: "cursor-pointer px-3 py-2 text-sm font-medium text-muted-foreground", children: "Geavanceerd" }), _jsxs("div", { className: "space-y-4 border-t border-border px-3 py-4", children: [advancedFields.length > 0 && (_jsx("div", { className: "grid gap-4 md:grid-cols-2", children: advancedFields.map((field) => (_jsx("div", { className: fieldClass(field), children: _jsx(RuleFieldInput, { field: field, value: stepValues[field.key], stepValues: stepValues, onChange: (value) => setFieldValue(field.key, value) }) }, field.key))) })), step.showConditions && isLastGroup && (_jsx(ConditionsEditor, { conditions: stepConditions, onChange: setConditions }))] })] }))] }, group.id));
|
|
309
|
+
}), _jsxs("div", { className: "flex flex-wrap gap-2 pt-1", children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: saving || busy, onClick: () => void opslaan(false), children: [_jsx(Save, { className: "h-4 w-4" }), " Opslaan"] }), _jsxs(Button, { size: "sm", disabled: saving || busy, onClick: () => void opslaan(true), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), " Opslaan & opnieuw berekenen"] })] })] })] }) }))] })), !loading && activeConfig && _jsx(FlowOrders, { onRoute: onRoute })] }));
|
|
310
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeblokkeerdeOrdersCard.d.ts","sourceRoot":"","sources":["../../src/components/GeblokkeerdeOrdersCard.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"GeblokkeerdeOrdersCard.d.ts","sourceRoot":"","sources":["../../src/components/GeblokkeerdeOrdersCard.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,sBAAsB,gCA6KrC"}
|
|
@@ -18,7 +18,7 @@ function datum(waarde) {
|
|
|
18
18
|
return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString("nl-NL");
|
|
19
19
|
}
|
|
20
20
|
export function GeblokkeerdeOrdersCard() {
|
|
21
|
-
const { rows, overzicht, loading, error, busy, refresh, retry } = useGeblokkeerdeOrders();
|
|
21
|
+
const { rows, overzicht, loading, error, busy, refresh, retry, dubbelenOpruimen } = useGeblokkeerdeOrders();
|
|
22
22
|
const [filter, setFilter] = useState("");
|
|
23
23
|
const [melding, setMelding] = useState(null);
|
|
24
24
|
const safeRows = Array.isArray(rows) ? rows : [];
|
|
@@ -40,5 +40,24 @@ export function GeblokkeerdeOrdersCard() {
|
|
|
40
40
|
setMelding(e?.message ? String(e.message) : "Opnieuw proberen is niet gelukt.");
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
|
-
|
|
43
|
+
const opruimen = async (orderId, orderRef) => {
|
|
44
|
+
try {
|
|
45
|
+
const r = await dubbelenOpruimen(orderId);
|
|
46
|
+
if (!r.ok) {
|
|
47
|
+
setMelding(r.reden === "bevroren"
|
|
48
|
+
? `${orderRef} is niet aangeraakt: er is al een pakbon geprint, gescand of gecontroleerd.`
|
|
49
|
+
: r.reden === "bezig"
|
|
50
|
+
? `${orderRef} wordt op dit moment al opgeruimd; probeer het zo opnieuw.`
|
|
51
|
+
: `Opruimen van ${orderRef} is niet gelukt.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
setMelding(`${orderRef}: ${r.verwijderd} dubbele bestelregel(s) verwijderd, ${r.na} regel(s) over.` +
|
|
55
|
+
(r.meerTeDoen ? " Er staan er nog meer klaar; klik nogmaals." : "") +
|
|
56
|
+
" Klik daarna op Opnieuw om de order te laten indelen.");
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
setMelding(e?.message ? String(e.message) : "Opruimen is niet gelukt.");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
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(AlertTriangle, { className: "h-4 w-4 text-destructive" }), "Vastgelopen orders"] }), _jsx(CardDescription, { children: "Deze orders krijgen geen karrenindeling tot er iets aan de order verandert of je ze hier opnieuw aanbiedt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [zichtbaar.length > 0 && (_jsxs(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => void opnieuw(zichtbaar.map((r) => r.orderId)), children: [_jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), "Alles opnieuw proberen"] })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void refresh(), disabled: loading, children: _jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }) })] })] }), _jsxs(CardContent, { className: "space-y-4", children: [overzicht?.tickStale && (_jsx("p", { className: "rounded-md bg-muted p-3 text-sm text-muted-foreground", children: "De verwerking op de achtergrond heeft zich langer dan tien minuten niet gemeld. Opnieuw proberen zet de order wel klaar, maar hij wordt pas opgepakt zodra de verwerking weer draait." })), error && _jsx("p", { className: "text-sm text-destructive", children: error }), melding && _jsx("p", { className: "text-sm text-muted-foreground", children: melding }), loading && safeRows.length === 0 ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-6 w-full" }), _jsx(Skeleton, { className: "h-6 w-full" })] })) : safeRows.length === 0 ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Geen vastgelopen orders. Alles is ingedeeld of staat nog in de wachtrij", overzicht ? ` (${overzicht.pending} wachtend)` : "", "."] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Badge, { variant: filter === "" ? "default" : "outline", className: "cursor-pointer", onClick: () => setFilter(""), children: ["Alles (", safeRows.length, ")"] }), codes.map(([code, aantal]) => (_jsxs(Badge, { variant: filter === code ? "default" : "outline", className: "cursor-pointer", onClick: () => setFilter(code), children: [redenLabel(code), " (", aantal, ")"] }, code)))] }), _jsx("div", { className: "divide-y rounded-md border", children: zichtbaar.map((r) => (_jsxs("div", { className: "flex items-start justify-between gap-4 p-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.orderRef }), _jsx(Badge, { variant: "secondary", children: redenLabel(r.reasonCode) }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Leverdatum ", datum(r.deliveryDate), " \u00B7 sinds ", datum(r.blockedSince)] })] }), r.reason && (_jsx("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: r.reason }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [r.reasonCode === "dubbele_regels" && (_jsx(Button, { variant: "secondary", size: "sm", disabled: busy, onClick: () => void opruimen(r.orderId, r.orderRef), children: "Dubbele regels opruimen" })), _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => void opnieuw([r.orderId]), children: "Opnieuw" })] })] }, r.queueId))) })] }))] })] }));
|
|
44
63
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface InklapbaarVensterProps {
|
|
3
|
+
titel: string;
|
|
4
|
+
omschrijving?: string;
|
|
5
|
+
standaardOpen?: boolean;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function InklapbaarVenster({ titel, omschrijving, standaardOpen, children, }: InklapbaarVensterProps): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=InklapbaarVenster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InklapbaarVenster.d.ts","sourceRoot":"","sources":["../../src/components/InklapbaarVenster.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,YAAY,EACZ,aAAqB,EACrB,QAAQ,GACT,EAAE,sBAAsB,+BA0BxB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/InklapbaarVenster.tsx — venster dat in- en uitgeklapt kan worden
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { ChevronDown } from "lucide-react";
|
|
7
|
+
export function InklapbaarVenster({ titel, omschrijving, standaardOpen = false, children, }) {
|
|
8
|
+
const [open, setOpen] = useState(standaardOpen);
|
|
9
|
+
return (_jsxs("section", { className: "rounded-lg border border-border", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, className: "flex w-full items-center justify-between gap-3 rounded-lg px-4 py-3 text-left transition-colors hover:bg-muted/60", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block text-sm font-semibold", children: titel }), omschrijving && (_jsx("span", { className: "mt-0.5 block truncate text-xs text-muted-foreground", children: omschrijving }))] }), _jsx(ChevronDown, { className: `h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })] }), open && _jsx("div", { className: "border-t border-border p-4", children: children })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KardekkingCard.d.ts","sourceRoot":"","sources":["../../src/components/KardekkingCard.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,cAAc,gCA6C7B"}
|