@flowselections/floriday-afhandeling-module 1.0.40 → 1.0.41
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.js +54 -54
- package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControlePage.js +36 -2
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +3 -2
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +38 -45
- package/dist-lib/components/pickstation/PakbonTemplate.js +70 -70
- package/dist-lib/hooks/useCartIndex.d.ts +11 -0
- package/dist-lib/hooks/useCartIndex.d.ts.map +1 -0
- package/dist-lib/hooks/useCartIndex.js +56 -0
- package/dist-lib/hooks/useConsistencyCheck.d.ts +8 -0
- package/dist-lib/hooks/useConsistencyCheck.d.ts.map +1 -0
- package/dist-lib/hooks/useConsistencyCheck.js +22 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +6 -2
- package/dist-lib/hooks/usePickAgenda.d.ts +32 -0
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -0
- package/dist-lib/hooks/usePickAgenda.js +75 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +164 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +164 -1
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +164 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +164 -1
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +330 -4
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/carrierSignupMail.server.js +26 -26
- package/dist-lib/lib/carriers.functions.d.ts +820 -5
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +820 -5
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +1743 -124
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +92 -127
- package/dist-lib/lib/floriday.functions.d.ts +165 -2
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -70,62 +70,62 @@ export function renderControleLijstHtml(carts, lang = "nl") {
|
|
|
70
70
|
});
|
|
71
71
|
const rows = sorted.length
|
|
72
72
|
? sorted
|
|
73
|
-
.map(({ cart: c, klaarzet }) => `
|
|
74
|
-
<tr>
|
|
75
|
-
<td class="mono bold">${esc(c.cartNumber ?? "—")}</td>
|
|
76
|
-
<td class="right">${esc(c.cratesOnCart ?? "—")}</td>
|
|
77
|
-
<td class="bold">${esc(countryCode(c.country) ?? c.country ?? "—")}</td>
|
|
78
|
-
<td class="bold">${esc(klaarzet.label)}</td>
|
|
79
|
-
<td>${esc(statusLabel(c.status, c.printedCount))}</td>
|
|
73
|
+
.map(({ cart: c, klaarzet }) => `
|
|
74
|
+
<tr>
|
|
75
|
+
<td class="mono bold">${esc(c.cartNumber ?? "—")}</td>
|
|
76
|
+
<td class="right">${esc(c.cratesOnCart ?? "—")}</td>
|
|
77
|
+
<td class="bold">${esc(countryCode(c.country) ?? c.country ?? "—")}</td>
|
|
78
|
+
<td class="bold">${esc(klaarzet.label)}</td>
|
|
79
|
+
<td>${esc(statusLabel(c.status, c.printedCount))}</td>
|
|
80
80
|
</tr>`)
|
|
81
81
|
.join("")
|
|
82
82
|
: `<tr><td colspan="5" class="empty">${esc(t.noCartsToday)}</td></tr>`;
|
|
83
|
-
return `<!DOCTYPE html>
|
|
84
|
-
<html lang="${esc(lang)}">
|
|
85
|
-
<head>
|
|
86
|
-
<meta charset="utf-8" />
|
|
87
|
-
<title>${esc(t.checklistTitle)}</title>
|
|
88
|
-
<style>
|
|
89
|
-
@page { size: A4 portrait; margin: 12mm; }
|
|
90
|
-
body { font-family: Arial, Helvetica, sans-serif; color: #000; margin: 0; }
|
|
91
|
-
h1 { font-size: 20px; margin: 0; }
|
|
92
|
-
.head { display: flex; align-items: baseline; justify-content: space-between;
|
|
93
|
-
border-bottom: 1px solid #000; padding-bottom: 6px; margin-bottom: 12px; }
|
|
94
|
-
.total { font-size: 12px; margin-bottom: 10px; }
|
|
95
|
-
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
|
96
|
-
th { text-align: left; border-bottom: 1px solid #000; padding: 3px 6px 3px 0; }
|
|
97
|
-
td { border-bottom: 1px solid #ccc; padding: 6px 6px 6px 0; white-space: nowrap; }
|
|
98
|
-
td.right, th.right { text-align: right; }
|
|
99
|
-
.mono { font-family: "Courier New", monospace; }
|
|
100
|
-
.bold { font-weight: 700; }
|
|
101
|
-
.empty { text-align: center; color: #666; padding: 24px 0; border: none; }
|
|
102
|
-
.sign { margin-top: 24px; font-size: 11px; }
|
|
103
|
-
.sign div { margin-top: 8px; }
|
|
104
|
-
.line { display: inline-block; border-bottom: 1px solid #000; width: 220px; }
|
|
105
|
-
tr { page-break-inside: avoid; }
|
|
106
|
-
thead { display: table-header-group; }
|
|
107
|
-
</style>
|
|
108
|
-
</head>
|
|
109
|
-
<body>
|
|
110
|
-
<div class="head"><h1>${esc(t.checklistTitle)}</h1><div>${esc(today)}</div></div>
|
|
111
|
-
<div class="total">${esc(t.totalCarts)}: <strong>${carts.length}</strong></div>
|
|
112
|
-
<table>
|
|
113
|
-
<thead>
|
|
114
|
-
<tr>
|
|
115
|
-
<th>${esc(t.colKarnr)}</th>
|
|
116
|
-
<th class="right">${esc(t.colFusten)}</th>
|
|
117
|
-
<th>${esc(t.colLand)}</th>
|
|
118
|
-
<th>${esc(t.colKlaarzetten)}</th>
|
|
119
|
-
<th>${esc(t.colStatus)}</th>
|
|
120
|
-
</tr>
|
|
121
|
-
</thead>
|
|
122
|
-
<tbody>${rows}</tbody>
|
|
123
|
-
</table>
|
|
124
|
-
<div class="sign">
|
|
125
|
-
<div>${esc(t.checkedBy)}: <span class="line"></span></div>
|
|
126
|
-
<div>${esc(t.dateTime)}: <span class="line"></span></div>
|
|
127
|
-
<div>${esc(t.signature)}: <span class="line"></span></div>
|
|
128
|
-
</div>
|
|
129
|
-
</body>
|
|
83
|
+
return `<!DOCTYPE html>
|
|
84
|
+
<html lang="${esc(lang)}">
|
|
85
|
+
<head>
|
|
86
|
+
<meta charset="utf-8" />
|
|
87
|
+
<title>${esc(t.checklistTitle)}</title>
|
|
88
|
+
<style>
|
|
89
|
+
@page { size: A4 portrait; margin: 12mm; }
|
|
90
|
+
body { font-family: Arial, Helvetica, sans-serif; color: #000; margin: 0; }
|
|
91
|
+
h1 { font-size: 20px; margin: 0; }
|
|
92
|
+
.head { display: flex; align-items: baseline; justify-content: space-between;
|
|
93
|
+
border-bottom: 1px solid #000; padding-bottom: 6px; margin-bottom: 12px; }
|
|
94
|
+
.total { font-size: 12px; margin-bottom: 10px; }
|
|
95
|
+
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
|
96
|
+
th { text-align: left; border-bottom: 1px solid #000; padding: 3px 6px 3px 0; }
|
|
97
|
+
td { border-bottom: 1px solid #ccc; padding: 6px 6px 6px 0; white-space: nowrap; }
|
|
98
|
+
td.right, th.right { text-align: right; }
|
|
99
|
+
.mono { font-family: "Courier New", monospace; }
|
|
100
|
+
.bold { font-weight: 700; }
|
|
101
|
+
.empty { text-align: center; color: #666; padding: 24px 0; border: none; }
|
|
102
|
+
.sign { margin-top: 24px; font-size: 11px; }
|
|
103
|
+
.sign div { margin-top: 8px; }
|
|
104
|
+
.line { display: inline-block; border-bottom: 1px solid #000; width: 220px; }
|
|
105
|
+
tr { page-break-inside: avoid; }
|
|
106
|
+
thead { display: table-header-group; }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<div class="head"><h1>${esc(t.checklistTitle)}</h1><div>${esc(today)}</div></div>
|
|
111
|
+
<div class="total">${esc(t.totalCarts)}: <strong>${carts.length}</strong></div>
|
|
112
|
+
<table>
|
|
113
|
+
<thead>
|
|
114
|
+
<tr>
|
|
115
|
+
<th>${esc(t.colKarnr)}</th>
|
|
116
|
+
<th class="right">${esc(t.colFusten)}</th>
|
|
117
|
+
<th>${esc(t.colLand)}</th>
|
|
118
|
+
<th>${esc(t.colKlaarzetten)}</th>
|
|
119
|
+
<th>${esc(t.colStatus)}</th>
|
|
120
|
+
</tr>
|
|
121
|
+
</thead>
|
|
122
|
+
<tbody>${rows}</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
<div class="sign">
|
|
125
|
+
<div>${esc(t.checkedBy)}: <span class="line"></span></div>
|
|
126
|
+
<div>${esc(t.dateTime)}: <span class="line"></span></div>
|
|
127
|
+
<div>${esc(t.signature)}: <span class="line"></span></div>
|
|
128
|
+
</div>
|
|
129
|
+
</body>
|
|
130
130
|
</html>`;
|
|
131
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AA6HA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAo1BpE"}
|
|
@@ -38,6 +38,10 @@ function blockedKey(b) {
|
|
|
38
38
|
return `${b.orderSource}#${b.orderId}#${b.cartIndex}`;
|
|
39
39
|
}
|
|
40
40
|
function controlCartKey(c) {
|
|
41
|
+
return c.cartNumberId ?? `${c.orderSource}#${c.orderId}#${c.cartIndex}`;
|
|
42
|
+
}
|
|
43
|
+
/** Oude concept-sleutels (`bron#order#index`) omzetten naar de nieuwe kar-sleutel. */
|
|
44
|
+
function legacyCartKey(c) {
|
|
41
45
|
return `${c.orderSource}#${c.orderId}#${c.cartIndex}`;
|
|
42
46
|
}
|
|
43
47
|
function stableStringifyDraft(input) {
|
|
@@ -162,6 +166,31 @@ export function ControlePage({ onExit } = {}) {
|
|
|
162
166
|
draftLoadedRef.current = true;
|
|
163
167
|
setDraftHydrated(true);
|
|
164
168
|
}, [draftQ.data, draftQ.isError, draftQ.isFetching, draftQ.isLoading]);
|
|
169
|
+
// Migreer concept-sleutels van het oude formaat naar de nieuwe kar-sleutel.
|
|
170
|
+
const keysMigratedRef = useRef(false);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (keysMigratedRef.current)
|
|
173
|
+
return;
|
|
174
|
+
if (!draftHydrated || carts.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
const mapping = new Map();
|
|
177
|
+
for (const cart of carts) {
|
|
178
|
+
const legacy = legacyCartKey(cart);
|
|
179
|
+
const next = controlCartKey(cart);
|
|
180
|
+
if (legacy !== next)
|
|
181
|
+
mapping.set(legacy, next);
|
|
182
|
+
}
|
|
183
|
+
keysMigratedRef.current = true;
|
|
184
|
+
if (mapping.size === 0)
|
|
185
|
+
return;
|
|
186
|
+
setChecked((prev) => new Set([...prev].map((k) => mapping.get(k) ?? k)));
|
|
187
|
+
setCartStates((prev) => {
|
|
188
|
+
const next = {};
|
|
189
|
+
for (const [k, v] of Object.entries(prev))
|
|
190
|
+
next[mapping.get(k) ?? k] = v;
|
|
191
|
+
return next;
|
|
192
|
+
});
|
|
193
|
+
}, [carts, draftHydrated]);
|
|
165
194
|
// Debounced autosave of the complete draft state.
|
|
166
195
|
const [savingState, setSavingState] = useState("idle");
|
|
167
196
|
const autosaveTimer = useRef(null);
|
|
@@ -365,7 +394,7 @@ export function ControlePage({ onExit } = {}) {
|
|
|
365
394
|
};
|
|
366
395
|
const stats = useMemo(() => {
|
|
367
396
|
const total = carts.length;
|
|
368
|
-
const printed = carts.filter((c) => c.printedCount > 0).length;
|
|
397
|
+
const printed = carts.filter((c) => c.printedToday ?? c.printedCount > 0).length;
|
|
369
398
|
const checkedCount = carts.filter((c) => checked.has(controlCartKey(c))).length;
|
|
370
399
|
return { total, printed, checked: checkedCount };
|
|
371
400
|
}, [carts, checked]);
|
|
@@ -402,6 +431,11 @@ export function ControlePage({ onExit } = {}) {
|
|
|
402
431
|
"orders",
|
|
403
432
|
"floriday_salesorders_cache",
|
|
404
433
|
"dashboard_critical_alerts",
|
|
434
|
+
"logistics_tasks",
|
|
435
|
+
"pick_queue",
|
|
436
|
+
"print_jobs",
|
|
437
|
+
"order_carts",
|
|
438
|
+
"order_cart_items",
|
|
405
439
|
];
|
|
406
440
|
const channel = supabase.channel("controle-page-realtime");
|
|
407
441
|
for (const table of tables) {
|
|
@@ -437,7 +471,7 @@ export function ControlePage({ onExit } = {}) {
|
|
|
437
471
|
? "indeterminate"
|
|
438
472
|
: false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _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." }) })), pagedCarts.map((c) => {
|
|
439
473
|
const k = controlCartKey(c);
|
|
440
|
-
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.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _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));
|
|
474
|
+
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.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtDate(c.pickupDate) }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
|
|
441
475
|
})] })] }), carts.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [carts.length, " resultaten"] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: "Resultaten per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => {
|
|
442
476
|
setPageSize(Number(v));
|
|
443
477
|
setPage(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAsCjE,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,
|
|
1
|
+
{"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAsCjE,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,sCAiF1F"}
|
|
@@ -29,7 +29,8 @@ export function DagDetailDialog({ open, date, planned, onOpenChange }) {
|
|
|
29
29
|
const cartRows = [];
|
|
30
30
|
for (const p of ordersOnDay) {
|
|
31
31
|
const assigns = p.planning.pickAssignments ?? [];
|
|
32
|
-
|
|
32
|
+
// Karren komen uit de Engine-planning (gedeelde karindex), niet uit payloads.
|
|
33
|
+
const totalCarts = assigns.reduce((sum, a) => sum + a.cartCount, 0);
|
|
33
34
|
if (totalCarts <= 0)
|
|
34
35
|
continue;
|
|
35
36
|
let runningIdx = 0;
|
|
@@ -169,7 +170,7 @@ function OrderRow({ planned: p, cartsToday }) {
|
|
|
169
170
|
/* doorgaan met lege regels */
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
|
-
const cartTotal = Math.max(1, p.
|
|
173
|
+
const cartTotal = Math.max(1, (p.planning?.pickAssignments ?? []).reduce((sum, a) => sum + a.cartCount, 0));
|
|
173
174
|
const pickDateIso = p.planning?.pickAssignments?.[0]?.date?.toISOString?.().slice(0, 10) ??
|
|
174
175
|
new Date().toISOString().slice(0, 10);
|
|
175
176
|
const pakbonLines = lines.map((l) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAOjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IACxD,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AA4DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,YAAY,EACZ,UAAU,GACX,EAAE,mBAAmB,+BA8JrB"}
|
|
@@ -4,6 +4,8 @@ import { Button, Badge } from "@flowselections/core";
|
|
|
4
4
|
import { ChevronLeft, ChevronRight, Package, Truck, Sprout, Clock, CheckCircle2, Scale } from "lucide-react";
|
|
5
5
|
import { isSameDay, startOfWeek, addDays } from "../../lib/pickDate";
|
|
6
6
|
import { useCapacityCheck } from "../../hooks/useCapacityCheck";
|
|
7
|
+
import { usePickAgenda } from "../../hooks/usePickAgenda";
|
|
8
|
+
import { useConsistencyCheck } from "../../hooks/useConsistencyCheck";
|
|
7
9
|
import { CapacityWarning } from "./CapacityWarning";
|
|
8
10
|
import { toDayKey } from "../../lib/logisticsEngine/capacity";
|
|
9
11
|
const WEEKDAY_LABELS = ["MAANDAG", "DINSDAG", "WOENSDAG", "DONDERDAG", "VRIJDAG", "ZATERDAG", "ZONDAG"];
|
|
@@ -29,11 +31,6 @@ function dateForPlanned(p, mode) {
|
|
|
29
31
|
case "pick": return p.planning.scheduledPickDate ?? p.planning.pickStartDate;
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
function isMovedEarlier(p, mode) {
|
|
33
|
-
if (mode !== "pick" || !p.planning)
|
|
34
|
-
return false;
|
|
35
|
-
return Boolean(p.planning.movedEarlier);
|
|
36
|
-
}
|
|
37
34
|
const MODE_LABELS = {
|
|
38
35
|
delivery: { summary: "Leveringen deze week" },
|
|
39
36
|
pickup: { summary: "Ophalingen deze week" },
|
|
@@ -58,6 +55,8 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
58
55
|
// de dichtstbijzijnde week met orders.
|
|
59
56
|
const jumpedToRef = useRef(null);
|
|
60
57
|
useEffect(() => {
|
|
58
|
+
if (mode === "pick")
|
|
59
|
+
return; // pickagenda komt uit de Engine-RPC
|
|
61
60
|
if (isLoading || planned.length > 0 || !jumpToDate)
|
|
62
61
|
return;
|
|
63
62
|
if (jumpedToRef.current === jumpToDate)
|
|
@@ -67,58 +66,51 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
67
66
|
return;
|
|
68
67
|
jumpedToRef.current = jumpToDate;
|
|
69
68
|
setCursor(startOfWeek(target));
|
|
70
|
-
}, [jumpToDate, planned.length, isLoading]);
|
|
69
|
+
}, [jumpToDate, planned.length, isLoading, mode]);
|
|
70
|
+
// Pickagenda komt volledig uit de Engine-RPC; deze module rekent zelf geen
|
|
71
|
+
// pickdagen meer uit en doet geen datum-, werkdagen- of tijdzonelogica.
|
|
72
|
+
const pickAgenda = usePickAgenda(mode === "pick" ? toDayKey(weekStart) : null, mode === "pick" ? toDayKey(weekEnd) : null);
|
|
71
73
|
const days = useMemo(() => {
|
|
72
74
|
return Array.from({ length: 7 }, (_, i) => {
|
|
73
75
|
const date = addDays(weekStart, i);
|
|
76
|
+
if (mode === "pick") {
|
|
77
|
+
// Dagen zonder rij tonen simpelweg nullen.
|
|
78
|
+
const row = pickAgenda.byDate.get(toDayKey(date));
|
|
79
|
+
return {
|
|
80
|
+
date,
|
|
81
|
+
entries: [],
|
|
82
|
+
shiftedCount: row?.shiftedCount ?? 0,
|
|
83
|
+
bestellingen: row?.orderCount ?? 0,
|
|
84
|
+
karren: row?.cartCount ?? 0,
|
|
85
|
+
planten: row?.plantCount ?? 0,
|
|
86
|
+
verzendklaar: row?.ready ?? 0,
|
|
87
|
+
voorbereiding: row?.inPreparation ?? 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
74
90
|
const entries = [];
|
|
75
91
|
for (const p of planned) {
|
|
76
92
|
if (!p.planning)
|
|
77
93
|
continue;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
order: p.order,
|
|
90
|
-
cartCount: a.cartCount,
|
|
91
|
-
plantCount: Math.round((p.order.plantCount ?? 0) * ratio),
|
|
92
|
-
movedEarlier: a.date.getTime() !== p.planning.pickStartDate.getTime(),
|
|
93
|
-
splitPart: total > 1 ? { index: idx + 1, total } : undefined,
|
|
94
|
-
reason: p.planning.reason,
|
|
95
|
-
carrierName: p.planning.carrierName,
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
const d = dateForPlanned(p, mode);
|
|
101
|
-
if (!d || !isSameDay(d, date))
|
|
102
|
-
continue;
|
|
103
|
-
entries.push({
|
|
104
|
-
order: p.order,
|
|
105
|
-
cartCount: p.order.cartCount ?? 0,
|
|
106
|
-
plantCount: p.order.plantCount ?? 0,
|
|
107
|
-
movedEarlier: false,
|
|
108
|
-
reason: p.planning.reason,
|
|
109
|
-
carrierName: p.planning.carrierName,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
94
|
+
const d = dateForPlanned(p, mode);
|
|
95
|
+
if (!d || !isSameDay(d, date))
|
|
96
|
+
continue;
|
|
97
|
+
entries.push({
|
|
98
|
+
order: p.order,
|
|
99
|
+
cartCount: (p.planning.pickAssignments ?? []).reduce((sum, a) => sum + a.cartCount, 0),
|
|
100
|
+
plantCount: p.order.plantCount ?? 0,
|
|
101
|
+
movedEarlier: false,
|
|
102
|
+
reason: p.planning.reason,
|
|
103
|
+
carrierName: p.planning.carrierName,
|
|
104
|
+
});
|
|
112
105
|
}
|
|
113
106
|
const karren = entries.reduce((s, e) => s + e.cartCount, 0);
|
|
114
107
|
const planten = entries.reduce((s, e) => s + e.plantCount, 0);
|
|
115
108
|
const verzendklaar = entries.filter((e) => isOrderReady(e.order)).length;
|
|
116
109
|
const voorbereiding = entries.length - verzendklaar;
|
|
117
|
-
const movedEarlierOrderIds = new Set(entries.filter((e) => e.movedEarlier).map((e) => e.order.id));
|
|
118
110
|
return {
|
|
119
111
|
date,
|
|
120
112
|
entries,
|
|
121
|
-
|
|
113
|
+
shiftedCount: 0,
|
|
122
114
|
bestellingen: entries.length,
|
|
123
115
|
karren,
|
|
124
116
|
planten,
|
|
@@ -126,7 +118,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
126
118
|
voorbereiding,
|
|
127
119
|
};
|
|
128
120
|
});
|
|
129
|
-
}, [planned, weekStart, mode]);
|
|
121
|
+
}, [planned, weekStart, mode, pickAgenda.byDate]);
|
|
130
122
|
const totals = useMemo(() => {
|
|
131
123
|
const leveringen = days.reduce((s, d) => s + d.bestellingen, 0);
|
|
132
124
|
const verzendklaar = days.reduce((s, d) => s + d.verzendklaar, 0);
|
|
@@ -134,6 +126,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
134
126
|
const totaalStuks = days.reduce((s, d) => s + d.planten, 0);
|
|
135
127
|
return { leveringen, verzendklaar, voorbereiding, totaalStuks };
|
|
136
128
|
}, [days]);
|
|
129
|
+
const { misaligned } = useConsistencyCheck();
|
|
137
130
|
const capacity = useCapacityCheck(toDayKey(weekStart), toDayKey(weekEnd));
|
|
138
131
|
const headerLabel = `Week ${weekNumber} - ${weekStart.getDate()} ${MONTHS_NL_SHORT[weekStart.getMonth()]}. t/m ${weekEnd.getDate()} ${MONTHS_NL_SHORT[weekEnd.getMonth()]}. ${weekEnd.getFullYear()}`;
|
|
139
132
|
const goPrev = () => setCursor((c) => addDays(c, -7));
|
|
@@ -143,7 +136,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
143
136
|
d.setHours(0, 0, 0, 0);
|
|
144
137
|
setCursor(startOfWeek(d));
|
|
145
138
|
};
|
|
146
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title ?? headerLabel }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goPrev, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), " Vorige week"] }), _jsx(Button, { variant: "outline", size: "sm", onClick: goToday, children: "Deze week" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goNext, children: ["Volgende week ", _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: [_jsx(SummaryCard, { label: MODE_LABELS[mode].summary, value: totals.leveringen, tone: "default" }), _jsx(SummaryCard, { label: "Verzendklaar", value: totals.verzendklaar, tone: "success" }), _jsx(SummaryCard, { label: "In voorbereiding", value: totals.voorbereiding, tone: "muted" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "max-h-[520px] space-y-2 overflow-y-auto rounded-lg border bg-muted/20 p-2", children: isLoading ? (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : (days.map((day) => (_jsx(DayRow, { day: day, onOrderClick: onOrderClick, onDayClick: onDayClick, capacity: mode === "delivery" ? undefined : capacity.byDate.get(toDayKey(day.date)) }, day.date.toISOString())))) })] }));
|
|
139
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title ?? headerLabel }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goPrev, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), " Vorige week"] }), _jsx(Button, { variant: "outline", size: "sm", onClick: goToday, children: "Deze week" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goNext, children: ["Volgende week ", _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] })] })] }), misaligned && (_jsx("div", { className: "rounded-md border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: "Aantallen worden opnieuw uitgelijnd." })), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: [_jsx(SummaryCard, { label: MODE_LABELS[mode].summary, value: totals.leveringen, tone: "default" }), _jsx(SummaryCard, { label: "Verzendklaar", value: totals.verzendklaar, tone: "success" }), _jsx(SummaryCard, { label: "In voorbereiding", value: totals.voorbereiding, tone: "muted" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "max-h-[520px] space-y-2 overflow-y-auto rounded-lg border bg-muted/20 p-2", children: isLoading || (mode === "pick" && pickAgenda.isLoading) ? (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : (days.map((day) => (_jsx(DayRow, { day: day, onOrderClick: onOrderClick, onDayClick: onDayClick, capacity: mode === "delivery" ? undefined : capacity.byDate.get(toDayKey(day.date)) }, day.date.toISOString())))) })] }));
|
|
147
140
|
}
|
|
148
141
|
function SummaryCard({ label, value, tone, }) {
|
|
149
142
|
const valueClass = tone === "success" ? "text-green-600" : "text-foreground";
|
|
@@ -154,7 +147,7 @@ function DayRow({ day, onOrderClick, onDayClick, capacity, }) {
|
|
|
154
147
|
const today = new Date();
|
|
155
148
|
today.setHours(0, 0, 0, 0);
|
|
156
149
|
const isToday = isSameDay(day.date, today);
|
|
157
|
-
return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6 px-4 py-4 sm:px-6", children: [_jsxs("div", { className: "w-20 shrink-0", children: [_jsx("div", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: WEEKDAY_LABELS[weekdayIdx] }), _jsx("div", { className: "mt-1 text-3xl font-bold leading-none", children: day.date.getDate() }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [MONTHS_NL_SHORT[day.date.getMonth()], "."] })] }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-6", children: [_jsx(Stat, { icon: _jsx(Package, { className: "h-4 w-4 text-slate-500" }), bg: "bg-slate-100", value: day.bestellingen, label: "bestellingen" }), _jsx(Stat, { icon: _jsx(Truck, { className: "h-4 w-4 text-amber-600" }), bg: "bg-amber-100", value: day.karren, label: "karren" }), _jsx(Stat, { icon: _jsx(Sprout, { className: "h-4 w-4 text-green-600" }), bg: "bg-green-100", value: day.planten, label: "planten" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [day.
|
|
150
|
+
return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6 px-4 py-4 sm:px-6", children: [_jsxs("div", { className: "w-20 shrink-0", children: [_jsx("div", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: WEEKDAY_LABELS[weekdayIdx] }), _jsx("div", { className: "mt-1 text-3xl font-bold leading-none", children: day.date.getDate() }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [MONTHS_NL_SHORT[day.date.getMonth()], "."] })] }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-6", children: [_jsx(Stat, { icon: _jsx(Package, { className: "h-4 w-4 text-slate-500" }), bg: "bg-slate-100", value: day.bestellingen, label: "bestellingen" }), _jsx(Stat, { icon: _jsx(Truck, { className: "h-4 w-4 text-amber-600" }), bg: "bg-amber-100", value: day.karren, label: "karren" }), _jsx(Stat, { icon: _jsx(Sprout, { className: "h-4 w-4 text-green-600" }), bg: "bg-green-100", value: day.planten, label: "planten" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [day.shiftedCount > 0 && (_jsxs(Badge, { className: "bg-purple-500/10 text-purple-700 hover:bg-purple-500/10", variant: "secondary", title: "Aantal orders dat te laat is aangemeld en door de Engine is verschoven.", children: [_jsx(Scale, { className: "mr-1 h-3.5 w-3.5" }), day.shiftedCount, " te laat aangemeld"] })), day.verzendklaar > 0 && (_jsxs(Badge, { className: "bg-green-500/15 text-green-700 hover:bg-green-500/15", variant: "secondary", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), day.verzendklaar, " verzendklaar"] })), day.voorbereiding > 0 && (_jsxs(Badge, { className: "bg-blue-500/10 text-blue-700 hover:bg-blue-500/10", variant: "secondary", children: [_jsx(Clock, { className: "mr-1 h-3.5 w-3.5" }), day.voorbereiding, " in voorbereiding"] })), onDayClick && day.bestellingen > 0 && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => onDayClick(day.date), children: "Dagdetails" }))] })] }), _jsx(CapacityWarning, { day: capacity, className: "mx-4 mb-4 sm:mx-6" })] }));
|
|
158
151
|
}
|
|
159
152
|
function Stat({ icon, bg, value, label, }) {
|
|
160
153
|
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `grid h-8 w-8 place-items-center rounded-full ${bg}`, children: icon }), _jsxs("div", { children: [_jsx("div", { className: "text-lg font-bold leading-none", children: value }), _jsx("div", { className: "text-xs text-muted-foreground", children: label })] })] }));
|
|
@@ -14,82 +14,82 @@ export function renderPakbonHtml(order, lang = "nl") {
|
|
|
14
14
|
};
|
|
15
15
|
const rows = order.lines.length
|
|
16
16
|
? order.lines
|
|
17
|
-
.map((l) => `
|
|
18
|
-
<tr>
|
|
19
|
-
<td>${escapeHtml(l.description)}</td>
|
|
20
|
-
<td style="text-align:right">${l.crates ?? ""}</td>
|
|
21
|
-
<td>${escapeHtml(l.unit ?? "")}</td>
|
|
17
|
+
.map((l) => `
|
|
18
|
+
<tr>
|
|
19
|
+
<td>${escapeHtml(l.description)}</td>
|
|
20
|
+
<td style="text-align:right">${l.crates ?? ""}</td>
|
|
21
|
+
<td>${escapeHtml(l.unit ?? "")}</td>
|
|
22
22
|
</tr>`)
|
|
23
23
|
.join("")
|
|
24
24
|
: `<tr><td colspan="3" style="text-align:center;color:#666">${escapeHtml(t.noLines)}</td></tr>`;
|
|
25
25
|
const cartLabel = t.cartOf(order.cartIndex, order.cartTotal);
|
|
26
26
|
const fustenLabel = order.cratesOnCart != null ? `${order.cratesOnCart}` : "—";
|
|
27
27
|
const cartNumber = order.cartNumber ?? `${order.orderNumber}-${order.cartIndex}`;
|
|
28
|
-
return `<!doctype html>
|
|
29
|
-
<html lang="${escapeHtml(htmlLang)}">
|
|
30
|
-
<head>
|
|
31
|
-
<meta charset="utf-8">
|
|
32
|
-
<title>${escapeHtml(t.packingSlip)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartNumber)}</title>
|
|
33
|
-
<style>
|
|
34
|
-
@page {
|
|
35
|
-
size: A4;
|
|
36
|
-
margin: 16mm;
|
|
37
|
-
@top-center { content: element(cartno); }
|
|
38
|
-
}
|
|
39
|
-
body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; color:#111; }
|
|
40
|
-
h1 { margin: 0 0 4px; font-size: 26px; }
|
|
41
|
-
.sub { color:#444; margin-bottom: 8px; }
|
|
42
|
-
|
|
43
|
-
.cart-number-hero {
|
|
44
|
-
position: running(cartno);
|
|
45
|
-
font-size: 88px;
|
|
46
|
-
font-weight: 900;
|
|
47
|
-
letter-spacing: 4px;
|
|
48
|
-
line-height: 1;
|
|
49
|
-
color: #000;
|
|
50
|
-
background: #fff;
|
|
51
|
-
border: 4px solid #000;
|
|
52
|
-
padding: 12px 20px;
|
|
53
|
-
display: inline-block;
|
|
54
|
-
margin: 6px 0 20px;
|
|
55
|
-
font-family: "Arial Black", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin: 8px 0 22px; }
|
|
59
|
-
.grid div { font-size: 14px; }
|
|
60
|
-
.label { color:#666; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
|
|
61
|
-
.value { font-size: 16px; font-weight: 600; }
|
|
62
|
-
table { width:100%; border-collapse: collapse; font-size: 14px; }
|
|
63
|
-
th, td { padding: 8px 6px; border-bottom: 1px solid #ddd; vertical-align: top; }
|
|
64
|
-
th { text-align: left; background:#f5f5f5; }
|
|
65
|
-
.barcode-fallback { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 18px; letter-spacing: 4px; padding: 8px 12px; border: 2px solid #111; display: inline-block; margin-top: 18px; }
|
|
66
|
-
.footer { margin-top: 28px; font-size: 11px; color:#666; }
|
|
67
|
-
</style>
|
|
68
|
-
</head>
|
|
69
|
-
<body>
|
|
70
|
-
<h1>${escapeHtml(t.packingSlip)}</h1>
|
|
71
|
-
<div class="sub">${escapeHtml(t.order)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</div>
|
|
72
|
-
|
|
73
|
-
<div class="cart-number-hero">${escapeHtml(cartNumber)}</div>
|
|
74
|
-
|
|
75
|
-
<div class="grid">
|
|
76
|
-
<div><div class="label">${escapeHtml(t.customer)}</div><div class="value">${escapeHtml(order.customerName)}</div></div>
|
|
77
|
-
<div><div class="label">${escapeHtml(t.country)}</div><div class="value">${escapeHtml(order.country ?? "—")}</div></div>
|
|
78
|
-
<div><div class="label">${escapeHtml(t.deliveryDate)}</div><div class="value">${fmt(order.deliveryDate)}</div></div>
|
|
79
|
-
<div><div class="label">${escapeHtml(t.pickDate)}</div><div class="value">${fmt(order.pickDate)}</div></div>
|
|
80
|
-
<div><div class="label">${escapeHtml(t.cart)}</div><div class="value">${escapeHtml(cartLabel)}</div></div>
|
|
81
|
-
<div><div class="label">${escapeHtml(t.cratesOnThisCart)}</div><div class="value">${escapeHtml(fustenLabel)}</div></div>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<table>
|
|
85
|
-
<thead><tr><th>${escapeHtml(t.description)}</th><th style="text-align:right;width:90px">${escapeHtml(t.crates)}</th><th style="width:90px">${escapeHtml(t.unit)}</th></tr></thead>
|
|
86
|
-
<tbody>${rows}</tbody>
|
|
87
|
-
</table>
|
|
88
|
-
|
|
89
|
-
<div class="barcode-fallback">*${escapeHtml(order.orderNumber)}*</div>
|
|
90
|
-
|
|
91
|
-
<div class="footer">${escapeHtml(t.printedVia)} · ${new Date().toLocaleString(locale)}</div>
|
|
92
|
-
</body>
|
|
28
|
+
return `<!doctype html>
|
|
29
|
+
<html lang="${escapeHtml(htmlLang)}">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8">
|
|
32
|
+
<title>${escapeHtml(t.packingSlip)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartNumber)}</title>
|
|
33
|
+
<style>
|
|
34
|
+
@page {
|
|
35
|
+
size: A4;
|
|
36
|
+
margin: 16mm;
|
|
37
|
+
@top-center { content: element(cartno); }
|
|
38
|
+
}
|
|
39
|
+
body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; color:#111; }
|
|
40
|
+
h1 { margin: 0 0 4px; font-size: 26px; }
|
|
41
|
+
.sub { color:#444; margin-bottom: 8px; }
|
|
42
|
+
|
|
43
|
+
.cart-number-hero {
|
|
44
|
+
position: running(cartno);
|
|
45
|
+
font-size: 88px;
|
|
46
|
+
font-weight: 900;
|
|
47
|
+
letter-spacing: 4px;
|
|
48
|
+
line-height: 1;
|
|
49
|
+
color: #000;
|
|
50
|
+
background: #fff;
|
|
51
|
+
border: 4px solid #000;
|
|
52
|
+
padding: 12px 20px;
|
|
53
|
+
display: inline-block;
|
|
54
|
+
margin: 6px 0 20px;
|
|
55
|
+
font-family: "Arial Black", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin: 8px 0 22px; }
|
|
59
|
+
.grid div { font-size: 14px; }
|
|
60
|
+
.label { color:#666; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
|
|
61
|
+
.value { font-size: 16px; font-weight: 600; }
|
|
62
|
+
table { width:100%; border-collapse: collapse; font-size: 14px; }
|
|
63
|
+
th, td { padding: 8px 6px; border-bottom: 1px solid #ddd; vertical-align: top; }
|
|
64
|
+
th { text-align: left; background:#f5f5f5; }
|
|
65
|
+
.barcode-fallback { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 18px; letter-spacing: 4px; padding: 8px 12px; border: 2px solid #111; display: inline-block; margin-top: 18px; }
|
|
66
|
+
.footer { margin-top: 28px; font-size: 11px; color:#666; }
|
|
67
|
+
</style>
|
|
68
|
+
</head>
|
|
69
|
+
<body>
|
|
70
|
+
<h1>${escapeHtml(t.packingSlip)}</h1>
|
|
71
|
+
<div class="sub">${escapeHtml(t.order)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</div>
|
|
72
|
+
|
|
73
|
+
<div class="cart-number-hero">${escapeHtml(cartNumber)}</div>
|
|
74
|
+
|
|
75
|
+
<div class="grid">
|
|
76
|
+
<div><div class="label">${escapeHtml(t.customer)}</div><div class="value">${escapeHtml(order.customerName)}</div></div>
|
|
77
|
+
<div><div class="label">${escapeHtml(t.country)}</div><div class="value">${escapeHtml(order.country ?? "—")}</div></div>
|
|
78
|
+
<div><div class="label">${escapeHtml(t.deliveryDate)}</div><div class="value">${fmt(order.deliveryDate)}</div></div>
|
|
79
|
+
<div><div class="label">${escapeHtml(t.pickDate)}</div><div class="value">${fmt(order.pickDate)}</div></div>
|
|
80
|
+
<div><div class="label">${escapeHtml(t.cart)}</div><div class="value">${escapeHtml(cartLabel)}</div></div>
|
|
81
|
+
<div><div class="label">${escapeHtml(t.cratesOnThisCart)}</div><div class="value">${escapeHtml(fustenLabel)}</div></div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<table>
|
|
85
|
+
<thead><tr><th>${escapeHtml(t.description)}</th><th style="text-align:right;width:90px">${escapeHtml(t.crates)}</th><th style="width:90px">${escapeHtml(t.unit)}</th></tr></thead>
|
|
86
|
+
<tbody>${rows}</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
|
|
89
|
+
<div class="barcode-fallback">*${escapeHtml(order.orderNumber)}*</div>
|
|
90
|
+
|
|
91
|
+
<div class="footer">${escapeHtml(t.printedVia)} · ${new Date().toLocaleString(locale)}</div>
|
|
92
|
+
</body>
|
|
93
93
|
</html>`;
|
|
94
94
|
}
|
|
95
95
|
function escapeHtml(s) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gedeelde karindex van de Engine. Dit is de enige bron voor karrentellingen:
|
|
3
|
+
* taak-payloads (cartCount) en eigen tellingen op order_carts zijn niet meer
|
|
4
|
+
* toegestaan. Een gedeelde kar telt bij elke order waar hij in zit.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useCartIndex(from: string | null, to: string | null): {
|
|
7
|
+
cartsForOrder: (...refs: (string | null | undefined)[]) => number | null;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
error: string | null;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=useCartIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCartIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useCartIndex.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;6BAoBrB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,MAAM,GAAG,IAAI;;;EA0B1F"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
|
+
/**
|
|
5
|
+
* Gedeelde karindex van de Engine. Dit is de enige bron voor karrentellingen:
|
|
6
|
+
* taak-payloads (cartCount) en eigen tellingen op order_carts zijn niet meer
|
|
7
|
+
* toegestaan. Een gedeelde kar telt bij elke order waar hij in zit.
|
|
8
|
+
*/
|
|
9
|
+
export function useCartIndex(from, to) {
|
|
10
|
+
const query = useQuery({
|
|
11
|
+
queryKey: ["logistics-cart-index", from, to],
|
|
12
|
+
enabled: !!from && !!to,
|
|
13
|
+
staleTime: 30000,
|
|
14
|
+
retry: false,
|
|
15
|
+
refetchInterval: 60000,
|
|
16
|
+
queryFn: async () => {
|
|
17
|
+
const { data, error } = await supabase.rpc("logistics_cart_index", {
|
|
18
|
+
p_from: from,
|
|
19
|
+
p_to: to,
|
|
20
|
+
});
|
|
21
|
+
if (error)
|
|
22
|
+
throw new Error(error.message);
|
|
23
|
+
return Array.isArray(data) ? data : [];
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const rows = query.data;
|
|
27
|
+
// Per orderreferentie de unieke karren tellen.
|
|
28
|
+
const cartsForOrder = useCallback((...refs) => {
|
|
29
|
+
const perOrder = new Map();
|
|
30
|
+
for (const r of rows ?? []) {
|
|
31
|
+
const cart = r.cart_id ?? "";
|
|
32
|
+
if (!cart)
|
|
33
|
+
continue;
|
|
34
|
+
for (const ref of [r.order_number, r.order_uuid]) {
|
|
35
|
+
if (!ref)
|
|
36
|
+
continue;
|
|
37
|
+
const set = perOrder.get(ref) ?? new Set();
|
|
38
|
+
set.add(cart);
|
|
39
|
+
perOrder.set(ref, set);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const ref of refs) {
|
|
43
|
+
if (!ref)
|
|
44
|
+
continue;
|
|
45
|
+
const set = perOrder.get(ref);
|
|
46
|
+
if (set)
|
|
47
|
+
return set.size;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}, [rows]);
|
|
51
|
+
return {
|
|
52
|
+
cartsForOrder,
|
|
53
|
+
isLoading: query.isLoading,
|
|
54
|
+
error: query.error instanceof Error ? query.error.message : null,
|
|
55
|
+
};
|
|
56
|
+
}
|