@flowselections/floriday-afhandeling-module 1.0.10 → 1.0.12
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/afhandeling/ControleLijstPrint.d.ts +7 -0
- package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/ControleLijstPrint.js +67 -0
- package/dist-lib/components/afhandeling/ControlePage.d.ts +4 -0
- package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/ControlePage.js +460 -0
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +9 -5
- package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerderForm.js +10 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.js +3 -2
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +15 -5
- package/dist-lib/components/afhandeling/WeekdayPicker.d.ts +12 -0
- package/dist-lib/components/afhandeling/WeekdayPicker.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/WeekdayPicker.js +30 -0
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts +1 -0
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
- package/dist-lib/components/pickstation/PakbonTemplate.js +31 -8
- package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/AfhandelingSettingsCard.js +9 -2
- package/dist-lib/components/ui/responsive-dialog.d.ts +20 -0
- package/dist-lib/components/ui/responsive-dialog.d.ts.map +1 -0
- package/dist-lib/components/ui/responsive-dialog.js +44 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +17 -6
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +4 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +755 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +755 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +755 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +765 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +1565 -57
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.js +16 -1
- package/dist-lib/lib/carrierMatch.server.d.ts +27 -0
- package/dist-lib/lib/carrierMatch.server.d.ts.map +1 -0
- package/dist-lib/lib/carrierMatch.server.js +75 -0
- package/dist-lib/lib/carriers.functions.d.ts +3781 -15
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/carriers.functions.js +17 -2
- package/dist-lib/lib/cartAssignments.functions.d.ts +3775 -15
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +100519 -0
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -0
- package/dist-lib/lib/cartControl.functions.js +489 -0
- package/dist-lib/lib/cartRescheduling.d.ts +30 -0
- package/dist-lib/lib/cartRescheduling.d.ts.map +1 -0
- package/dist-lib/lib/cartRescheduling.js +71 -0
- package/dist-lib/lib/floriday.functions.d.ts +755 -3
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/pickstationI18n.d.ts +8 -0
- package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
- package/dist-lib/lib/pickstationI18n.js +48 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts +1 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
- package/dist-lib/lib/pickstationOrders.server.js +94 -7
- package/dist-lib/lib/pickstationTypes.d.ts +12 -0
- package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
- package/dist-lib/lib/planning.d.ts +23 -0
- package/dist-lib/lib/planning.d.ts.map +1 -1
- package/dist-lib/lib/planning.js +66 -6
- package/package.json +6 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ControlCart } from "../../lib/cartControl.functions";
|
|
2
|
+
/** Printbare controlelijst — één regel per kar met checkbox en opmerkingveld. */
|
|
3
|
+
export declare function ControleLijstPrint({ carts, onClose, }: {
|
|
4
|
+
carts: ControlCart[];
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}): import("react").ReactPortal | null;
|
|
7
|
+
//# sourceMappingURL=ControleLijstPrint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ControleLijstPrint.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControleLijstPrint.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAanE,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,sCAkIA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { Button } from "@flowselections/core";
|
|
5
|
+
import { X, Printer } from "lucide-react";
|
|
6
|
+
function fmt(iso) {
|
|
7
|
+
if (!iso)
|
|
8
|
+
return "—";
|
|
9
|
+
const d = new Date(iso);
|
|
10
|
+
if (Number.isNaN(d.getTime()))
|
|
11
|
+
return iso;
|
|
12
|
+
return d.toLocaleDateString("nl-NL", {
|
|
13
|
+
day: "2-digit",
|
|
14
|
+
month: "2-digit",
|
|
15
|
+
year: "numeric",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Printbare controlelijst — één regel per kar met checkbox en opmerkingveld. */
|
|
19
|
+
export function ControleLijstPrint({ carts, onClose, }) {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const t = setTimeout(() => window.print(), 300);
|
|
22
|
+
const handleAfterPrint = () => onClose();
|
|
23
|
+
window.addEventListener("afterprint", handleAfterPrint);
|
|
24
|
+
return () => {
|
|
25
|
+
clearTimeout(t);
|
|
26
|
+
window.removeEventListener("afterprint", handleAfterPrint);
|
|
27
|
+
};
|
|
28
|
+
}, [onClose]);
|
|
29
|
+
const today = new Date().toLocaleDateString("nl-NL", {
|
|
30
|
+
weekday: "long",
|
|
31
|
+
day: "2-digit",
|
|
32
|
+
month: "long",
|
|
33
|
+
year: "numeric",
|
|
34
|
+
});
|
|
35
|
+
const content = (_jsxs("div", { id: "controle-print-root", className: "fixed inset-0 z-[100] bg-white overflow-auto", children: [_jsxs("div", { className: "print:hidden sticky top-0 flex items-center justify-between p-3 border-b bg-white", children: [_jsx("div", { className: "font-semibold", children: "Controlelijst \u2014 printweergave" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => window.print(), children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), "Opnieuw printen"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: onClose, children: [_jsx(X, { className: "h-4 w-4 mr-2" }), "Sluiten"] })] })] }), _jsxs("div", { className: "print-sheet p-8 print:p-0 max-w-[210mm] mx-auto text-black", children: [_jsxs("div", { className: "flex items-baseline justify-between border-b border-black pb-2 mb-4", children: [_jsx("h1", { className: "text-2xl font-bold", children: "Controlelijst karren" }), _jsx("div", { className: "text-sm", children: today })] }), _jsxs("div", { className: "text-sm mb-3", children: ["Totaal karren: ", _jsx("strong", { children: carts.length })] }), _jsxs("table", { className: "w-full text-[11px] border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-black", children: [_jsx("th", { className: "text-left py-1 pr-2", children: "Karnr" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Klant" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Vervoerder" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Lever" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Ophaal" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Print" }), _jsx("th", { className: "text-center py-1 px-2", children: "Klaar" }), _jsx("th", { className: "text-left py-1 pr-2", children: "Opmerkingen" })] }) }), _jsxs("tbody", { children: [carts.map((c) => (_jsxs("tr", { className: "border-b border-gray-300", children: [_jsx("td", { className: "py-2 pr-2 font-mono font-bold whitespace-nowrap", children: c.cartNumber ?? "—" }), _jsx("td", { className: "py-2 pr-2", children: c.customerName }), _jsx("td", { className: "py-2 pr-2", children: c.carrierName ?? "—" }), _jsx("td", { className: "py-2 pr-2 whitespace-nowrap", children: fmt(c.deliveryDate) }), _jsx("td", { className: "py-2 pr-2 whitespace-nowrap", children: fmt(c.pickupDate) }), _jsx("td", { className: "py-2 pr-2 text-center", children: c.printedCount > 0 ? "✓" : "" }), _jsx("td", { className: "py-2 px-2 text-center", children: _jsx("span", { className: "inline-block w-4 h-4 border border-black" }) }), _jsx("td", { className: "py-2 pr-2 w-[30%]", children: _jsx("span", { className: "inline-block w-full border-b border-gray-400 h-4" }) })] }, `${c.orderSource}#${c.orderId}#${c.cartIndex}`))), carts.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 8, className: "py-6 text-center text-gray-500", children: "Geen karren om vandaag te controleren." }) }))] })] }), _jsxs("div", { className: "mt-6 text-[11px]", children: [_jsxs("div", { children: ["Gecontroleerd door: ", _jsx("span", { className: "inline-block border-b border-black w-64" })] }), _jsxs("div", { className: "mt-2", children: ["Datum / tijd: ", _jsx("span", { className: "inline-block border-b border-black w-64" })] }), _jsxs("div", { className: "mt-2", children: ["Handtekening: ", _jsx("span", { className: "inline-block border-b border-black w-64" })] })] })] }), _jsx("style", { children: `
|
|
36
|
+
@media print {
|
|
37
|
+
@page { size: A4 portrait; margin: 12mm; }
|
|
38
|
+
html, body { background: #fff !important; margin: 0 !important; padding: 0 !important; }
|
|
39
|
+
/* Hide the entire app; show only the print root */
|
|
40
|
+
body > *:not(#controle-print-portal) { display: none !important; }
|
|
41
|
+
#controle-print-portal, #controle-print-root {
|
|
42
|
+
position: static !important;
|
|
43
|
+
overflow: visible !important;
|
|
44
|
+
height: auto !important;
|
|
45
|
+
width: auto !important;
|
|
46
|
+
background: #fff !important;
|
|
47
|
+
}
|
|
48
|
+
.print-sheet {
|
|
49
|
+
max-width: none !important;
|
|
50
|
+
padding: 0 !important;
|
|
51
|
+
margin: 0 !important;
|
|
52
|
+
}
|
|
53
|
+
table { page-break-inside: auto; }
|
|
54
|
+
tr { page-break-inside: avoid; page-break-after: auto; }
|
|
55
|
+
thead { display: table-header-group; }
|
|
56
|
+
}
|
|
57
|
+
` })] }));
|
|
58
|
+
if (typeof document === "undefined")
|
|
59
|
+
return null;
|
|
60
|
+
let portalNode = document.getElementById("controle-print-portal");
|
|
61
|
+
if (!portalNode) {
|
|
62
|
+
portalNode = document.createElement("div");
|
|
63
|
+
portalNode.id = "controle-print-portal";
|
|
64
|
+
document.body.appendChild(portalNode);
|
|
65
|
+
}
|
|
66
|
+
return createPortal(content, portalNode);
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AA4GA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BA+tBpE"}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
4
|
+
import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, } from "@flowselections/core";
|
|
5
|
+
import { Printer, CheckCircle2, AlertTriangle, History } from "lucide-react";
|
|
6
|
+
import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
|
|
7
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
9
|
+
import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSession, listControlHistory, getControlSessionDetail, getControlDraft, saveControlDraft, } from "../../lib/cartControl.functions";
|
|
10
|
+
import { ControleLijstPrint } from "./ControleLijstPrint";
|
|
11
|
+
function fmtDate(iso) {
|
|
12
|
+
if (!iso)
|
|
13
|
+
return "—";
|
|
14
|
+
const d = new Date(iso);
|
|
15
|
+
if (Number.isNaN(d.getTime()))
|
|
16
|
+
return iso;
|
|
17
|
+
return d.toLocaleDateString("nl-NL", {
|
|
18
|
+
weekday: "short",
|
|
19
|
+
day: "2-digit",
|
|
20
|
+
month: "2-digit",
|
|
21
|
+
year: "numeric",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function isTomorrowIso(iso) {
|
|
25
|
+
if (!iso)
|
|
26
|
+
return false;
|
|
27
|
+
const d = new Date(iso);
|
|
28
|
+
if (Number.isNaN(d.getTime()))
|
|
29
|
+
return false;
|
|
30
|
+
const tomorrow = new Date();
|
|
31
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
32
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
33
|
+
d.setHours(0, 0, 0, 0);
|
|
34
|
+
return d.getTime() === tomorrow.getTime();
|
|
35
|
+
}
|
|
36
|
+
function blockedKey(b) {
|
|
37
|
+
return `${b.orderSource}#${b.orderId}#${b.cartIndex}`;
|
|
38
|
+
}
|
|
39
|
+
function controlCartKey(c) {
|
|
40
|
+
return `${c.orderSource}#${c.orderId}#${c.cartIndex}`;
|
|
41
|
+
}
|
|
42
|
+
function stableStringifyDraft(input) {
|
|
43
|
+
const checkedKeys = [...input.checkedKeys].sort();
|
|
44
|
+
const cartStates = Object.fromEntries(Object.entries(input.cartStates).sort(([a], [b]) => a.localeCompare(b)));
|
|
45
|
+
return JSON.stringify({ notes: input.notes, checkedKeys, cartStates });
|
|
46
|
+
}
|
|
47
|
+
const controlDraftQueryKey = ["control", "draft"];
|
|
48
|
+
function buildDraftDto(input, formState, updatedAt) {
|
|
49
|
+
return {
|
|
50
|
+
notes: input.notes,
|
|
51
|
+
checkedKeys: [...input.checkedKeys].sort(),
|
|
52
|
+
cartStates: input.cartStates,
|
|
53
|
+
formState,
|
|
54
|
+
updatedAt,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function ControlePage({ onExit } = {}) {
|
|
58
|
+
const qc = useQueryClient();
|
|
59
|
+
const [tab, setTab] = useState("controle");
|
|
60
|
+
const [notes, setNotes] = useState("");
|
|
61
|
+
const [checked, setChecked] = useState(new Set());
|
|
62
|
+
const [cartStates, setCartStates] = useState({});
|
|
63
|
+
const [showPrint, setShowPrint] = useState(false);
|
|
64
|
+
const [result, setResult] = useState(null);
|
|
65
|
+
const [pendingProposal, setPendingProposal] = useState(null);
|
|
66
|
+
const [confirmApproval, setConfirmApproval] = useState(null);
|
|
67
|
+
const fetchToday = useServerFn(getTodaysControlCarts);
|
|
68
|
+
const fetchHistory = useServerFn(listControlHistory);
|
|
69
|
+
const fetchProposals = useServerFn(getControlRescheduleProposals);
|
|
70
|
+
const submit = useServerFn(submitControlSession);
|
|
71
|
+
const fetchDraft = useServerFn(getControlDraft);
|
|
72
|
+
const saveDraft = useServerFn(saveControlDraft);
|
|
73
|
+
const cartsQ = useQuery({
|
|
74
|
+
queryKey: ["control", "today"],
|
|
75
|
+
queryFn: () => fetchToday(),
|
|
76
|
+
});
|
|
77
|
+
const histQ = useQuery({
|
|
78
|
+
queryKey: ["control", "history"],
|
|
79
|
+
queryFn: () => fetchHistory(),
|
|
80
|
+
enabled: tab === "historie",
|
|
81
|
+
});
|
|
82
|
+
const draftQ = useQuery({
|
|
83
|
+
queryKey: controlDraftQueryKey,
|
|
84
|
+
queryFn: async () => (await fetchDraft()),
|
|
85
|
+
staleTime: 0,
|
|
86
|
+
refetchOnMount: "always",
|
|
87
|
+
refetchOnWindowFocus: true,
|
|
88
|
+
});
|
|
89
|
+
const carts = cartsQ.data?.carts ?? [];
|
|
90
|
+
const currentDraftPayload = useMemo(() => {
|
|
91
|
+
const nextStates = { ...cartStates };
|
|
92
|
+
for (const cart of carts) {
|
|
93
|
+
const key = controlCartKey(cart);
|
|
94
|
+
const status = checked.has(key) ? "ready" : "pending";
|
|
95
|
+
const previous = nextStates[key];
|
|
96
|
+
nextStates[key] = previous ? { ...previous, status } : { status };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
notes,
|
|
100
|
+
checkedKeys: [...checked].sort(),
|
|
101
|
+
cartStates: nextStates,
|
|
102
|
+
};
|
|
103
|
+
}, [cartStates, checked, carts, notes]);
|
|
104
|
+
const latestDraftPayloadRef = useRef(currentDraftPayload);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
latestDraftPayloadRef.current = currentDraftPayload;
|
|
107
|
+
}, [currentDraftPayload]);
|
|
108
|
+
const draftFormStateRef = useRef({});
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (draftQ.data?.formState)
|
|
111
|
+
draftFormStateRef.current = draftQ.data.formState;
|
|
112
|
+
}, [draftQ.data?.formState]);
|
|
113
|
+
const saveDraftRef = useRef(saveDraft);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
saveDraftRef.current = saveDraft;
|
|
116
|
+
}, [saveDraft]);
|
|
117
|
+
// Hydrate notes, checked cart keys and per-cart states from the server draft once loaded.
|
|
118
|
+
const draftLoadedRef = useRef(false);
|
|
119
|
+
const [draftHydrated, setDraftHydrated] = useState(false);
|
|
120
|
+
const lastSavedSnapshotRef = useRef(null);
|
|
121
|
+
const lastSavedRef = useRef(null);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (draftLoadedRef.current)
|
|
124
|
+
return;
|
|
125
|
+
if (draftQ.isLoading)
|
|
126
|
+
return;
|
|
127
|
+
if (draftQ.isFetching)
|
|
128
|
+
return;
|
|
129
|
+
if (draftQ.isError)
|
|
130
|
+
return;
|
|
131
|
+
const draft = draftQ.data ?? {
|
|
132
|
+
notes: "",
|
|
133
|
+
checkedKeys: [],
|
|
134
|
+
cartStates: {},
|
|
135
|
+
formState: {},
|
|
136
|
+
updatedAt: null,
|
|
137
|
+
};
|
|
138
|
+
const restoredStates = draft.cartStates ?? {};
|
|
139
|
+
const restoredKeys = draft.checkedKeys?.length
|
|
140
|
+
? draft.checkedKeys
|
|
141
|
+
: Object.entries(restoredStates)
|
|
142
|
+
.filter(([, state]) => state.status === "ready")
|
|
143
|
+
.map(([key]) => key);
|
|
144
|
+
setNotes(draft.notes ?? "");
|
|
145
|
+
setChecked(new Set(restoredKeys));
|
|
146
|
+
setCartStates(restoredStates);
|
|
147
|
+
draftFormStateRef.current = draft.formState ?? {};
|
|
148
|
+
const hydratedPayload = {
|
|
149
|
+
notes: draft.notes ?? "",
|
|
150
|
+
checkedKeys: [...restoredKeys].sort(),
|
|
151
|
+
cartStates: restoredStates,
|
|
152
|
+
};
|
|
153
|
+
latestDraftPayloadRef.current = hydratedPayload;
|
|
154
|
+
lastSavedSnapshotRef.current = stableStringifyDraft(hydratedPayload);
|
|
155
|
+
lastSavedRef.current = draft.notes ?? "";
|
|
156
|
+
draftLoadedRef.current = true;
|
|
157
|
+
setDraftHydrated(true);
|
|
158
|
+
}, [draftQ.data, draftQ.isError, draftQ.isFetching, draftQ.isLoading]);
|
|
159
|
+
// Debounced autosave of the complete draft state.
|
|
160
|
+
const [savingState, setSavingState] = useState("idle");
|
|
161
|
+
const autosaveTimer = useRef(null);
|
|
162
|
+
const controlFinalizedRef = useRef(false);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (controlFinalizedRef.current)
|
|
165
|
+
return;
|
|
166
|
+
if (!draftHydrated)
|
|
167
|
+
return;
|
|
168
|
+
const snapshot = stableStringifyDraft(currentDraftPayload);
|
|
169
|
+
if (lastSavedSnapshotRef.current === snapshot)
|
|
170
|
+
return;
|
|
171
|
+
if (autosaveTimer.current)
|
|
172
|
+
clearTimeout(autosaveTimer.current);
|
|
173
|
+
autosaveTimer.current = setTimeout(async () => {
|
|
174
|
+
try {
|
|
175
|
+
if (controlFinalizedRef.current)
|
|
176
|
+
return;
|
|
177
|
+
setSavingState("saving");
|
|
178
|
+
const r = await saveDraft({ data: currentDraftPayload });
|
|
179
|
+
lastSavedSnapshotRef.current = snapshot;
|
|
180
|
+
lastSavedRef.current = currentDraftPayload.notes;
|
|
181
|
+
qc.setQueryData(controlDraftQueryKey, buildDraftDto(currentDraftPayload, draftFormStateRef.current, r.updatedAt));
|
|
182
|
+
setSavingState("saved");
|
|
183
|
+
setTimeout(() => setSavingState((s) => (s === "saved" ? "idle" : s)), 2000);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
setSavingState("idle");
|
|
187
|
+
}
|
|
188
|
+
}, 300);
|
|
189
|
+
return () => {
|
|
190
|
+
if (autosaveTimer.current)
|
|
191
|
+
clearTimeout(autosaveTimer.current);
|
|
192
|
+
};
|
|
193
|
+
}, [currentDraftPayload, draftHydrated, qc, saveDraft]);
|
|
194
|
+
// Flush pending progress when navigating away, refreshing or hiding the tab.
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const flush = () => {
|
|
197
|
+
if (controlFinalizedRef.current)
|
|
198
|
+
return;
|
|
199
|
+
if (!draftLoadedRef.current)
|
|
200
|
+
return;
|
|
201
|
+
const payload = latestDraftPayloadRef.current;
|
|
202
|
+
const snapshot = stableStringifyDraft(payload);
|
|
203
|
+
if (lastSavedSnapshotRef.current === snapshot)
|
|
204
|
+
return;
|
|
205
|
+
qc.setQueryData(controlDraftQueryKey, buildDraftDto(payload, draftFormStateRef.current, new Date().toISOString()));
|
|
206
|
+
lastSavedSnapshotRef.current = snapshot;
|
|
207
|
+
lastSavedRef.current = payload.notes;
|
|
208
|
+
void saveDraftRef.current({ data: payload });
|
|
209
|
+
};
|
|
210
|
+
const onVisibilityChange = () => {
|
|
211
|
+
if (document.visibilityState === "hidden")
|
|
212
|
+
flush();
|
|
213
|
+
};
|
|
214
|
+
window.addEventListener("pagehide", flush);
|
|
215
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
216
|
+
return () => {
|
|
217
|
+
flush();
|
|
218
|
+
window.removeEventListener("pagehide", flush);
|
|
219
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
220
|
+
};
|
|
221
|
+
}, [qc]);
|
|
222
|
+
const toggle = (k, on) => {
|
|
223
|
+
setChecked((prev) => {
|
|
224
|
+
const next = new Set(prev);
|
|
225
|
+
if (on)
|
|
226
|
+
next.add(k);
|
|
227
|
+
else
|
|
228
|
+
next.delete(k);
|
|
229
|
+
return next;
|
|
230
|
+
});
|
|
231
|
+
setCartStates((prev) => ({
|
|
232
|
+
...prev,
|
|
233
|
+
[k]: {
|
|
234
|
+
...(prev[k] ?? {}),
|
|
235
|
+
status: on ? "ready" : "pending",
|
|
236
|
+
updatedAt: new Date().toISOString(),
|
|
237
|
+
},
|
|
238
|
+
}));
|
|
239
|
+
};
|
|
240
|
+
const toggleAll = (on) => {
|
|
241
|
+
const keys = carts.map(controlCartKey);
|
|
242
|
+
setChecked(on ? new Set(keys) : new Set());
|
|
243
|
+
const nowIso = new Date().toISOString();
|
|
244
|
+
setCartStates((prev) => {
|
|
245
|
+
const next = { ...prev };
|
|
246
|
+
for (const key of keys) {
|
|
247
|
+
next[key] = {
|
|
248
|
+
...(next[key] ?? {}),
|
|
249
|
+
status: on ? "ready" : "pending",
|
|
250
|
+
updatedAt: nowIso,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return next;
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
const submitM = useMutation({
|
|
257
|
+
mutationFn: (input) => {
|
|
258
|
+
const visibleKeys = new Set(carts.map(controlCartKey));
|
|
259
|
+
const checkedCartKeys = [...checked].filter((k) => visibleKeys.has(k));
|
|
260
|
+
return submit({
|
|
261
|
+
data: {
|
|
262
|
+
checkedCartKeys,
|
|
263
|
+
confirmedRescheduleCartKeys: input?.confirmedRescheduleCartKeys ?? [],
|
|
264
|
+
notes,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
onMutate: () => {
|
|
269
|
+
controlFinalizedRef.current = true;
|
|
270
|
+
if (autosaveTimer.current)
|
|
271
|
+
clearTimeout(autosaveTimer.current);
|
|
272
|
+
},
|
|
273
|
+
onSuccess: (r) => {
|
|
274
|
+
setResult(r);
|
|
275
|
+
toast.success(`Controle opgeslagen — ${r.checkedCount} klaar, ${r.rescheduledCount} doorgeschoven, ${r.blockedCount} geblokkeerd.`);
|
|
276
|
+
qc.invalidateQueries({ queryKey: ["control"] });
|
|
277
|
+
qc.invalidateQueries({ queryKey: ["floriday", "orders"] });
|
|
278
|
+
qc.invalidateQueries({ queryKey: ["cart-overrides"] });
|
|
279
|
+
setChecked(new Set());
|
|
280
|
+
setCartStates({});
|
|
281
|
+
setNotes("");
|
|
282
|
+
lastSavedRef.current = "";
|
|
283
|
+
const emptyDraftPayload = {
|
|
284
|
+
notes: "",
|
|
285
|
+
checkedKeys: [],
|
|
286
|
+
cartStates: {},
|
|
287
|
+
};
|
|
288
|
+
latestDraftPayloadRef.current = emptyDraftPayload;
|
|
289
|
+
lastSavedSnapshotRef.current = stableStringifyDraft(emptyDraftPayload);
|
|
290
|
+
setSavingState("idle");
|
|
291
|
+
qc.setQueryData(controlDraftQueryKey, buildDraftDto(emptyDraftPayload, {}, null));
|
|
292
|
+
qc.invalidateQueries({ queryKey: controlDraftQueryKey });
|
|
293
|
+
},
|
|
294
|
+
onError: (e) => {
|
|
295
|
+
controlFinalizedRef.current = false;
|
|
296
|
+
toast.error(e instanceof Error ? e.message : "Opslaan mislukt");
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
const proposalM = useMutation({
|
|
300
|
+
mutationFn: () => fetchProposals({ data: { checkedCartKeys: [...checked] } }),
|
|
301
|
+
onSuccess: (r) => {
|
|
302
|
+
if (r.proposals.length > 0 || r.blocked.length > 0) {
|
|
303
|
+
setPendingProposal(r);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// All carts already checked → require explicit approval before submitting.
|
|
307
|
+
setConfirmApproval({ mode: "all-checked" });
|
|
308
|
+
},
|
|
309
|
+
onError: (e) => {
|
|
310
|
+
toast.error(e instanceof Error ? e.message : "Controlevoorstel kon niet worden geladen");
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
const runControl = () => {
|
|
314
|
+
proposalM.mutate();
|
|
315
|
+
};
|
|
316
|
+
const requestApproveMoveProposals = () => {
|
|
317
|
+
const keys = pendingProposal?.proposals.map((p) => p.key) ?? [];
|
|
318
|
+
setPendingProposal(null);
|
|
319
|
+
setConfirmApproval({ mode: "with-reschedule", confirmedRescheduleCartKeys: keys });
|
|
320
|
+
};
|
|
321
|
+
const requestApproveKeepOnDay = () => {
|
|
322
|
+
setPendingProposal(null);
|
|
323
|
+
setConfirmApproval({ mode: "keep-on-day" });
|
|
324
|
+
};
|
|
325
|
+
const markPendingCartReady = (key) => {
|
|
326
|
+
setChecked((prev) => {
|
|
327
|
+
const next = new Set(prev);
|
|
328
|
+
next.add(key);
|
|
329
|
+
return next;
|
|
330
|
+
});
|
|
331
|
+
setCartStates((prev) => ({
|
|
332
|
+
...prev,
|
|
333
|
+
[key]: {
|
|
334
|
+
...(prev[key] ?? {}),
|
|
335
|
+
status: "ready",
|
|
336
|
+
updatedAt: new Date().toISOString(),
|
|
337
|
+
},
|
|
338
|
+
}));
|
|
339
|
+
setPendingProposal((prev) => {
|
|
340
|
+
if (!prev)
|
|
341
|
+
return prev;
|
|
342
|
+
const proposals = prev.proposals.filter((x) => x.key !== key);
|
|
343
|
+
const blocked = prev.blocked.filter((x) => blockedKey(x) !== key);
|
|
344
|
+
if (proposals.length === 0 && blocked.length === 0) {
|
|
345
|
+
setConfirmApproval({ mode: "all-checked" });
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return { proposals, blocked };
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
const performApproval = () => {
|
|
352
|
+
if (!confirmApproval)
|
|
353
|
+
return;
|
|
354
|
+
const keys = confirmApproval.mode === "with-reschedule"
|
|
355
|
+
? confirmApproval.confirmedRescheduleCartKeys
|
|
356
|
+
: [];
|
|
357
|
+
setConfirmApproval(null);
|
|
358
|
+
submitM.mutate({ confirmedRescheduleCartKeys: keys });
|
|
359
|
+
};
|
|
360
|
+
const stats = useMemo(() => {
|
|
361
|
+
const total = carts.length;
|
|
362
|
+
const printed = carts.filter((c) => c.printedCount > 0).length;
|
|
363
|
+
const checkedCount = carts.filter((c) => checked.has(controlCartKey(c))).length;
|
|
364
|
+
return { total, printed, checked: checkedCount };
|
|
365
|
+
}, [carts, checked]);
|
|
366
|
+
const pendingProposals = pendingProposal?.proposals ?? [];
|
|
367
|
+
const pendingBlocked = pendingProposal?.blocked ?? [];
|
|
368
|
+
const pendingNotReadyCount = pendingProposals.length + pendingBlocked.length;
|
|
369
|
+
const criticalTomorrowCount = pendingProposals.filter((p) => isTomorrowIso(p.pickupDate)).length +
|
|
370
|
+
pendingBlocked.filter((b) => isTomorrowIso(b.pickupDate)).length;
|
|
371
|
+
// Realtime sync: invalidate control queries on any change to relevant tables.
|
|
372
|
+
const invalidateTimer = useRef(null);
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
const scheduleInvalidate = () => {
|
|
375
|
+
if (invalidateTimer.current)
|
|
376
|
+
clearTimeout(invalidateTimer.current);
|
|
377
|
+
invalidateTimer.current = setTimeout(() => {
|
|
378
|
+
qc.invalidateQueries({ queryKey: ["control"] });
|
|
379
|
+
}, 300);
|
|
380
|
+
};
|
|
381
|
+
const tables = [
|
|
382
|
+
"cart_numbers",
|
|
383
|
+
"cart_control_sessions",
|
|
384
|
+
"cart_control_items",
|
|
385
|
+
"cart_pick_overrides",
|
|
386
|
+
"orders",
|
|
387
|
+
"floriday_salesorders_cache",
|
|
388
|
+
"dashboard_critical_alerts",
|
|
389
|
+
];
|
|
390
|
+
const channel = supabase.channel("controle-page-realtime");
|
|
391
|
+
for (const table of tables) {
|
|
392
|
+
channel.on("postgres_changes", { event: "*", schema: "public", table }, scheduleInvalidate);
|
|
393
|
+
}
|
|
394
|
+
channel.subscribe();
|
|
395
|
+
return () => {
|
|
396
|
+
if (invalidateTimer.current)
|
|
397
|
+
clearTimeout(invalidateTimer.current);
|
|
398
|
+
supabase.removeChannel(channel);
|
|
399
|
+
};
|
|
400
|
+
}, [qc]);
|
|
401
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Controle karren" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Dagelijkse eindcontrole \u2014 vandaag ", new Date().toLocaleDateString("nl-NL")] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setShowPrint(true), children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), "Controlelijst afdrukken"] }), _jsxs(Button, { onClick: runControl, disabled: submitM.isPending || proposalM.isPending || carts.length === 0 || tab !== "controle", children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), proposalM.isPending ? "Controleren…" : "Controle uitvoeren"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: tab === "controle" ? "default" : "outline", onClick: () => setTab("controle"), children: "Controle" }), _jsxs(Button, { variant: tab === "historie" ? "default" : "outline", onClick: () => setTab("historie"), children: [_jsx(History, { className: "h-4 w-4 mr-2" }), "Historie"] })] }), tab === "controle" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-3 items-center", children: [_jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Totaal karren vandaag" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.total })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Geprint" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.printed })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Aangevinkt" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.checked })] })] }), _jsx(Card, { className: "p-0 overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: _jsx(Checkbox, { checked: carts.length > 0 && carts.every((c) => checked.has(controlCartKey(c)))
|
|
402
|
+
? true
|
|
403
|
+
: carts.some((c) => checked.has(controlCartKey(c)))
|
|
404
|
+
? "indeterminate"
|
|
405
|
+
: false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Karnummer" }), _jsx(TableHead, { children: "Ophaaldatum" }), _jsx(TableHead, { children: "Uitgeprint" })] }) }), _jsxs(TableBody, { children: [cartsQ.isLoading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })), !cartsQ.isLoading && carts.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Geen karren om vandaag te controleren." }) })), carts.map((c) => {
|
|
406
|
+
const k = controlCartKey(c);
|
|
407
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: c.cartNumber ?? "—" }), _jsx(TableCell, { children: fmtDate(c.pickupDate) }), _jsx(TableCell, { children: (c.printedCount ?? 0) > 0 ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
|
|
408
|
+
})] })] }) }), _jsxs("div", { className: "max-w-xl", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { className: "text-sm font-medium", children: "Opmerkingen bij deze controle" }), _jsx("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: savingState === "saving"
|
|
409
|
+
? "Opslaan…"
|
|
410
|
+
: savingState === "saved"
|
|
411
|
+
? "Voortgang automatisch opgeslagen"
|
|
412
|
+
: lastSavedRef.current && notes === lastSavedRef.current
|
|
413
|
+
? "Concept bewaard"
|
|
414
|
+
: "" })] }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: "Bijzonderheden, afwijkingen\u2026", rows: 3 }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Wordt automatisch bewaard tot de controle definitief is goedgekeurd." })] })] })), tab === "historie" && _jsx(HistorieView, { loading: histQ.isLoading, sessions: histQ.data?.sessions ?? [] }), showPrint && (_jsx(ControleLijstPrint, { carts: carts, onClose: () => setShowPrint(false) })), _jsx(Dialog, { open: !!result, onOpenChange: (v) => !v && setResult(null), children: _jsxs(ResponsiveDialogContent, { size: "md", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsx(DialogTitle, { children: "Resultaat controle" }), _jsxs(DialogDescription, { children: [result?.checkedCount, " klaar \u00B7 ", result?.rescheduledCount, " doorgeschoven \u00B7", " ", result?.blockedCount, " geblokkeerd"] })] }), _jsxs(ResponsiveDialogBody, { className: "space-y-4", children: [result?.blocked && result.blocked.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2 text-destructive font-medium", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: "De volgende karren kunnen niet automatisch worden doorgeschoven omdat hierdoor de planning of levering in gevaar komt." })] }), _jsx(BlockedTable, { blocked: result.blocked })] })), result?.rescheduled && result.rescheduled.length > 0 && (_jsxs("div", { className: "border rounded-md p-3", children: [_jsx("div", { className: "font-medium mb-2", children: "Doorgeschoven karren" }), _jsx("ul", { className: "text-sm space-y-1", children: result.rescheduled.map((r, i) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono", children: r.cartNumber ?? "—" }), " \u2014", " ", r.customerName, " \u2192 nieuwe pickdatum", " ", _jsx("strong", { children: fmtDate(r.newPickDate) })] }, i))) })] }))] }), _jsx(ResponsiveDialogFooter, { children: _jsx(Button, { onClick: () => {
|
|
415
|
+
setResult(null);
|
|
416
|
+
if (onExit)
|
|
417
|
+
onExit();
|
|
418
|
+
}, children: "Sluiten" }) })] }) }), _jsx(Dialog, { open: !!pendingProposal, onOpenChange: (v) => !v && setPendingProposal(null), children: _jsxs(ResponsiveDialogContent, { size: "lg", rows: "with-alert", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsx(DialogTitle, { children: "Controle beslisscherm" }), _jsx(DialogDescription, { children: "Bepaal wat er met de niet-gereed gemelde karren moet gebeuren voordat je de controle definitief afrondt." })] }), _jsx("div", { className: criticalTomorrowCount > 0 ? "border-b bg-destructive/10 px-6 py-4" : "border-b bg-muted/30 px-6 py-4", children: _jsxs("div", { className: criticalTomorrowCount > 0 ? "flex min-w-0 items-start gap-3 text-destructive" : "flex min-w-0 items-start gap-3 text-muted-foreground", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0" }), _jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("div", { className: "font-semibold", children: criticalTomorrowCount > 0
|
|
419
|
+
? `LET OP: Er ${criticalTomorrowCount === 1 ? "staat 1 kar" : `staan ${criticalTomorrowCount} karren`} gepland om morgen opgehaald te worden die nog niet gereed ${criticalTomorrowCount === 1 ? "is" : "zijn"}.`
|
|
420
|
+
: "Geen kritieke karren met ophaaldatum morgen." }), _jsx("div", { className: criticalTomorrowCount > 0 ? "text-sm text-destructive/90" : "text-sm text-muted-foreground", children: criticalTomorrowCount > 0
|
|
421
|
+
? "Controleer deze karren voordat je verder gaat met de vervolgprocessen."
|
|
422
|
+
: "Controleer de overige niet-gereed gemelde karren hieronder." })] })] }) }), _jsxs(ResponsiveDialogBody, { className: "space-y-5", children: [_jsxs("section", { className: "space-y-3", children: [_jsx(SectionTitle, { tone: "critical", title: "Kritieke waarschuwingen", count: criticalTomorrowCount }), criticalTomorrowCount > 0 ? (_jsxs("div", { className: "grid gap-2", children: [pendingProposals.filter((p) => isTomorrowIso(p.pickupDate)).map((p) => (_jsx(CartDecisionRow, { cartNumber: p.cartNumber, customerName: p.customerName, pickupDate: p.pickupDate, status: "Niet gereed", critical: true, onReady: () => markPendingCartReady(p.key) }, p.key))), pendingBlocked.filter((b) => isTomorrowIso(b.pickupDate)).map((b) => (_jsx(CartDecisionRow, { cartNumber: b.cartNumber, customerName: b.customerName, pickupDate: b.pickupDate, status: "Niet gereed", critical: true, onReady: () => markPendingCartReady(blockedKey(b)) }, blockedKey(b))))] })) : (_jsx("p", { className: "rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground", children: "Geen niet-gereed gemelde karren met ophaaldatum morgen." }))] }), (() => {
|
|
423
|
+
const nonCriticalProposals = pendingProposals.filter((p) => !isTomorrowIso(p.pickupDate));
|
|
424
|
+
const nonCriticalBlocked = pendingBlocked.filter((b) => !isTomorrowIso(b.pickupDate));
|
|
425
|
+
const nonCriticalCount = nonCriticalProposals.length + nonCriticalBlocked.length;
|
|
426
|
+
return (_jsxs("section", { className: "space-y-3", children: [_jsx(SectionTitle, { title: "Overige karren die nog niet gereed zijn", count: nonCriticalCount }), nonCriticalCount > 0 ? (_jsxs("div", { className: "grid gap-2", children: [nonCriticalProposals.map((p) => (_jsx(CartDecisionRow, { cartNumber: p.cartNumber, customerName: p.customerName, pickupDate: p.pickupDate, status: "Nog niet afgevinkt", onReady: () => markPendingCartReady(p.key) }, p.key))), nonCriticalBlocked.map((b) => (_jsx(CartDecisionRow, { cartNumber: b.cartNumber, customerName: b.customerName, pickupDate: b.pickupDate, status: "Niet veilig te verplaatsen", onReady: () => markPendingCartReady(blockedKey(b)) }, blockedKey(b))))] })) : (_jsx("p", { className: "rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground", children: "Geen overige niet-gereed gemelde karren." }))] }));
|
|
427
|
+
})(), _jsxs("section", { className: "space-y-3", children: [_jsx(SectionTitle, { title: "Samenvatting" }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsx(SummaryTile, { label: "Niet gereed", value: pendingNotReadyCount }), _jsx(SummaryTile, { label: "Kritiek morgen", value: criticalTomorrowCount, tone: "critical" }), _jsx(SummaryTile, { label: "Niet veilig te verplaatsen", value: pendingBlocked.length, tone: pendingBlocked.length > 0 ? "critical" : "default" })] })] })] }), _jsxs(ResponsiveDialogFooter, { children: [_jsx(Button, { className: "w-full sm:w-auto", variant: "ghost", onClick: () => setPendingProposal(null), children: "Annuleren" }), _jsx(Button, { className: "w-full min-w-0 sm:w-auto", variant: "outline", onClick: requestApproveKeepOnDay, disabled: submitM.isPending, children: "Op huidige dag laten staan" }), _jsx(Button, { className: "w-full min-w-0 sm:w-auto", onClick: requestApproveMoveProposals, disabled: submitM.isPending, children: "Verplaatsen naar morgen" })] })] }) }), _jsx(Dialog, { open: !!confirmApproval, onOpenChange: (v) => !v && setConfirmApproval(null), children: _jsxs(ResponsiveDialogContent, { size: "sm", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsx(DialogTitle, { children: "Controle definitief goedkeuren" }), _jsx(DialogDescription, { children: "Alle controlepunten zijn afgerond. Weet je zeker dat je de controle definitief wilt goedkeuren? Deze actie start de vervolgprocessen." })] }), _jsx(ResponsiveDialogBody, { className: "py-0" }), _jsxs(ResponsiveDialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setConfirmApproval(null), children: "Nee, terug naar de controle" }), _jsx(Button, { onClick: performApproval, disabled: submitM.isPending, children: "Ja, controle goedkeuren" })] })] }) })] }));
|
|
428
|
+
}
|
|
429
|
+
function BlockedTable({ blocked }) {
|
|
430
|
+
return (_jsx("div", { className: "grid gap-2", children: blocked.map((b, i) => (_jsxs("div", { className: "rounded-md border bg-background/70 p-3", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-[1fr_1.5fr_1fr_1fr]", children: [_jsx(InfoCell, { label: "Karnummer", value: b.cartNumber ?? "—", mono: true }), _jsx(InfoCell, { label: "Klant", value: b.customerName }), _jsx(InfoCell, { label: "Ophaaldatum", value: fmtDate(b.pickupDate) }), _jsx(InfoCell, { label: "Vervoerder", value: b.carrierName ?? "—" })] }), _jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: b.reason })] }, i))) }));
|
|
431
|
+
}
|
|
432
|
+
function HistorieView({ loading, sessions, }) {
|
|
433
|
+
const [openId, setOpenId] = useState(null);
|
|
434
|
+
const fetchDetail = useServerFn(getControlSessionDetail);
|
|
435
|
+
const detailQ = useQuery({
|
|
436
|
+
queryKey: ["control", "detail", openId],
|
|
437
|
+
queryFn: () => fetchDetail({ data: { sessionId: openId } }),
|
|
438
|
+
enabled: !!openId,
|
|
439
|
+
});
|
|
440
|
+
return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "p-0 overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { children: "Tijd" }), _jsx(TableHead, { children: "Totaal" }), _jsx(TableHead, { children: "Gecontroleerd" }), _jsx(TableHead, { children: "Doorgeschoven" }), _jsx(TableHead, { children: "Kritiek" }), _jsx(TableHead, { children: "Opmerkingen" }), _jsx(TableHead, {})] }) }), _jsxs(TableBody, { children: [loading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, className: "text-center py-8 text-muted-foreground", children: "Laden\u2026" }) })), !loading && sessions.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, className: "text-center py-8 text-muted-foreground", children: "Nog geen controles uitgevoerd." }) })), sessions.map((s) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: fmtDate(s.controlDate) }), _jsx(TableCell, { children: new Date(s.performedAt).toLocaleTimeString("nl-NL", {
|
|
441
|
+
hour: "2-digit",
|
|
442
|
+
minute: "2-digit",
|
|
443
|
+
}) }), _jsx(TableCell, { children: s.totalCarts }), _jsx(TableCell, { children: s.checkedCount }), _jsx(TableCell, { children: s.rescheduledCount }), _jsx(TableCell, { children: s.blockedCount > 0 ? (_jsx(Badge, { variant: "destructive", children: s.blockedCount })) : (_jsx("span", { className: "text-muted-foreground", children: "0" })) }), _jsx(TableCell, { className: "max-w-[220px] truncate", children: s.notes ?? "—" }), _jsx(TableCell, { children: _jsx(Button, { size: "sm", variant: "outline", onClick: () => setOpenId(s.id), children: "Details" }) })] }, s.id)))] })] }) }), _jsx(Dialog, { open: !!openId, onOpenChange: (v) => !v && setOpenId(null), children: _jsxs(ResponsiveDialogContent, { size: "lg", children: [_jsx(ResponsiveDialogHeader, { children: _jsx(DialogTitle, { children: "Controle-details" }) }), _jsx(ResponsiveDialogBody, { children: _jsx("div", { className: "grid gap-2", children: detailQ.data?.items.map((it, i) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-[1fr_1fr_1.5fr_1fr_auto] md:items-center", children: [_jsx(InfoCell, { label: "Karnummer", value: it.cartNumberLabel ?? "—", mono: true }), _jsx(InfoCell, { label: "Order", value: it.orderNumber ?? "—" }), _jsx(InfoCell, { label: "Klant", value: it.customerName ?? "—" }), _jsx(InfoCell, { label: "Leverdatum", value: fmtDate(it.deliveryDate) }), _jsxs("div", { children: [it.action === "checked" && _jsx(Badge, { children: "Gecontroleerd" }), it.action === "rescheduled" && _jsx(Badge, { variant: "secondary", children: "Doorgeschoven" }), it.action === "blocked" && _jsx(Badge, { variant: "destructive", children: "Geblokkeerd" })] })] }), _jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: it.action === "rescheduled" && it.newPickDate
|
|
444
|
+
? `Nieuwe pickdatum: ${fmtDate(it.newPickDate)}`
|
|
445
|
+
: it.blockReason ?? "—" })] }, i))) }) }), _jsx(ResponsiveDialogFooter, { children: _jsx(Button, { onClick: () => setOpenId(null), children: "Sluiten" }) })] }) })] }));
|
|
446
|
+
}
|
|
447
|
+
function SectionTitle({ title, count, tone = "default", }) {
|
|
448
|
+
return (_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3", children: [_jsxs("h3", { className: tone === "critical" ? "truncate font-semibold text-destructive" : "truncate font-semibold", children: [tone === "critical" ? "🔴 " : "", title] }), typeof count === "number" && (_jsx(Badge, { variant: tone === "critical" && count > 0 ? "destructive" : "secondary", children: count }))] }));
|
|
449
|
+
}
|
|
450
|
+
function CartDecisionRow({ cartNumber, customerName, pickupDate, status, critical = false, onReady, }) {
|
|
451
|
+
return (_jsx("div", { className: critical ? "rounded-md border border-destructive/40 bg-destructive/10 p-3" : "rounded-md border bg-card p-3", children: _jsxs("div", { className: "grid gap-3 lg:grid-cols-[1.1fr_1.4fr_1fr_1fr_auto] lg:items-center", children: [_jsx(InfoCell, { label: "Karnummer", value: cartNumber ?? "—", mono: true }), _jsx(InfoCell, { label: "Klant", value: customerName }), _jsx(InfoCell, { label: "Ophaallocatie", value: "\u2014" }), _jsx(InfoCell, { label: "Geplande ophaaldatum", value: fmtDate(pickupDate) }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2 lg:justify-end", children: [_jsx(Badge, { variant: critical ? "destructive" : "outline", children: status }), _jsxs("label", { className: "inline-flex items-center gap-2 text-sm font-medium", children: [_jsx(Checkbox, { onCheckedChange: (v) => v && onReady() }), "Gereed"] })] })] }) }));
|
|
452
|
+
}
|
|
453
|
+
function InfoCell({ label, value, mono = false }) {
|
|
454
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { className: mono ? "truncate font-mono font-semibold" : "truncate font-medium", title: value, children: value })] }));
|
|
455
|
+
}
|
|
456
|
+
function SummaryTile({ label, value, tone = "default", }) {
|
|
457
|
+
return (_jsxs("div", { className: tone === "critical" ? "rounded-md border border-destructive/40 bg-destructive/10 p-3" : "rounded-md border bg-muted/30 p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { className: tone === "critical" ? "text-2xl font-semibold text-destructive" : "text-2xl font-semibold", children: value })] }));
|
|
458
|
+
}
|
|
459
|
+
// Silence unused Input import (may be used later); keep tree-shakeable
|
|
460
|
+
void Input;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAyBjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAID,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,oBAAoB,sCA2E1F"}
|