@flowselections/floriday-afhandeling-module 1.0.12 → 1.0.14
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/AutoProcessStatus.d.ts +12 -0
- package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/AutoProcessStatus.js +103 -0
- package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts +3 -1
- package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControleLijstPrint.js +56 -17
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +96 -4
- package/dist-lib/components/afhandeling/LeveringenPage.js +1 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.js +2 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +3 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +58 -17
- package/dist-lib/components/pickstation/KioskControle.d.ts +2 -0
- package/dist-lib/components/pickstation/KioskControle.d.ts.map +1 -0
- package/dist-lib/components/pickstation/KioskControle.js +7 -0
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts +2 -1
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
- package/dist-lib/components/pickstation/PakbonTemplate.js +20 -17
- package/dist-lib/components/pickstation/PickstationPage.d.ts +2 -0
- package/dist-lib/components/pickstation/PickstationPage.d.ts.map +1 -0
- package/dist-lib/components/pickstation/PickstationPage.js +266 -0
- package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/AfhandelingSettingsCard.js +14 -2
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +28 -11
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +4 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1243 -63
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1243 -63
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1243 -63
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1264 -56
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +2483 -111
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.js +28 -1
- package/dist-lib/lib/carrierPlanning.d.ts +25 -0
- package/dist-lib/lib/carrierPlanning.d.ts.map +1 -0
- package/dist-lib/lib/carrierPlanning.js +65 -0
- package/dist-lib/lib/carrierSignupMail.server.d.ts +0 -2
- package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -1
- package/dist-lib/lib/carrierSignupMail.server.js +53 -216
- package/dist-lib/lib/carriers.functions.d.ts +5920 -20
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/carriers.functions.js +5 -0
- package/dist-lib/lib/cartAssignments.functions.d.ts +5920 -20
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +11843 -40
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +4 -1
- package/dist-lib/lib/cartRescheduling.d.ts.map +1 -1
- package/dist-lib/lib/cartRescheduling.js +8 -6
- package/dist-lib/lib/countries.d.ts +2 -0
- package/dist-lib/lib/countries.d.ts.map +1 -1
- package/dist-lib/lib/countries.js +51 -0
- package/dist-lib/lib/deliveryNotePrint.d.ts +26 -0
- package/dist-lib/lib/deliveryNotePrint.d.ts.map +1 -0
- package/dist-lib/lib/deliveryNotePrint.js +172 -0
- package/dist-lib/lib/floriday.functions.d.ts +1251 -63
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +126 -8
- package/dist-lib/lib/floridayAutoProcess.d.ts +30 -0
- package/dist-lib/lib/floridayAutoProcess.d.ts.map +1 -0
- package/dist-lib/lib/floridayAutoProcess.js +70 -0
- package/dist-lib/lib/pickstationI18n.d.ts +46 -0
- package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
- package/dist-lib/lib/pickstationI18n.js +251 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
- package/dist-lib/lib/pickstationOrders.server.js +56 -19
- package/dist-lib/lib/pickstationTypes.d.ts +2 -0
- package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
- package/dist-lib/lib/planning.d.ts +34 -0
- package/dist-lib/lib/planning.d.ts.map +1 -1
- package/dist-lib/lib/planning.js +84 -0
- package/package.json +15 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
3
|
+
import { ControlePage } from "../afhandeling/ControlePage";
|
|
4
|
+
export function KioskControle() {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
return (_jsxs("div", { className: "min-h-screen bg-slate-50 text-slate-900 flex flex-col", children: [_jsx("header", { className: "bg-white border-b sticky top-0 z-20", children: _jsxs("div", { className: "max-w-6xl mx-auto px-4 py-3 flex items-center gap-3", children: [_jsx("button", { onClick: () => navigate({ to: "/pickstation" }), className: "h-12 px-4 rounded-xl bg-white border-2 border-slate-300 hover:bg-slate-100 font-semibold", children: "\u2190 Terug naar Picking" }), _jsx("h1", { className: "text-xl font-semibold", children: "Picking-modus" })] }) }), _jsx("main", { className: "flex-1", children: _jsx(ControlePage, { onExit: () => navigate({ to: "/pickstation" }) }) })] }));
|
|
7
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PickstationOrder } from "../../lib/pickstationTypes";
|
|
2
|
+
import { type PickstationLang } from "../../lib/pickstationI18n";
|
|
2
3
|
/** Returns a fully self-contained printable HTML document for one packing slip per cart. */
|
|
3
4
|
export declare function renderPakbonHtml(order: {
|
|
4
5
|
id: string;
|
|
@@ -16,6 +17,6 @@ export declare function renderPakbonHtml(order: {
|
|
|
16
17
|
crates: number | null;
|
|
17
18
|
unit: string | null;
|
|
18
19
|
}[];
|
|
19
|
-
}): string;
|
|
20
|
+
}, lang?: PickstationLang | null | undefined): string;
|
|
20
21
|
export type { PickstationOrder };
|
|
21
22
|
//# sourceMappingURL=PakbonTemplate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PakbonTemplate.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/PakbonTemplate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"PakbonTemplate.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/PakbonTemplate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,2BAA2B,CAAC;AAEnC,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;CAC9E,EACD,IAAI,GAAE,eAAe,GAAG,IAAI,GAAG,SAAgB,GAC9C,MAAM,CA+FR;AAWD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { getPrintDict, PICKSTATION_LOCALE, } from "../../lib/pickstationI18n";
|
|
1
2
|
/** Returns a fully self-contained printable HTML document for one packing slip per cart. */
|
|
2
|
-
export function renderPakbonHtml(order) {
|
|
3
|
+
export function renderPakbonHtml(order, lang = "nl") {
|
|
4
|
+
const t = getPrintDict(lang);
|
|
5
|
+
const locale = PICKSTATION_LOCALE[lang ?? "nl"] ?? "nl-NL";
|
|
6
|
+
const htmlLang = lang ?? "nl";
|
|
3
7
|
const fmt = (d) => {
|
|
4
8
|
if (!d)
|
|
5
9
|
return "—";
|
|
6
10
|
const dt = new Date(d);
|
|
7
11
|
if (Number.isNaN(dt.getTime()))
|
|
8
12
|
return d;
|
|
9
|
-
return dt.toLocaleDateString(
|
|
13
|
+
return dt.toLocaleDateString(locale, { day: "2-digit", month: "2-digit", year: "numeric" });
|
|
10
14
|
};
|
|
11
15
|
const rows = order.lines.length
|
|
12
16
|
? order.lines
|
|
@@ -17,15 +21,15 @@ export function renderPakbonHtml(order) {
|
|
|
17
21
|
<td>${escapeHtml(l.unit ?? "")}</td>
|
|
18
22
|
</tr>`)
|
|
19
23
|
.join("")
|
|
20
|
-
: `<tr><td colspan="3" style="text-align:center;color:#666"
|
|
21
|
-
const cartLabel =
|
|
24
|
+
: `<tr><td colspan="3" style="text-align:center;color:#666">${escapeHtml(t.noLines)}</td></tr>`;
|
|
25
|
+
const cartLabel = t.cartOf(order.cartIndex, order.cartTotal);
|
|
22
26
|
const fustenLabel = order.cratesOnCart != null ? `${order.cratesOnCart}` : "—";
|
|
23
27
|
const cartNumber = order.cartNumber ?? `${order.orderNumber}-${order.cartIndex}`;
|
|
24
28
|
return `<!doctype html>
|
|
25
|
-
<html lang="
|
|
29
|
+
<html lang="${escapeHtml(htmlLang)}">
|
|
26
30
|
<head>
|
|
27
31
|
<meta charset="utf-8">
|
|
28
|
-
<title
|
|
32
|
+
<title>${escapeHtml(t.packingSlip)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartNumber)}</title>
|
|
29
33
|
<style>
|
|
30
34
|
@page {
|
|
31
35
|
size: A4;
|
|
@@ -36,7 +40,6 @@ export function renderPakbonHtml(order) {
|
|
|
36
40
|
h1 { margin: 0 0 4px; font-size: 26px; }
|
|
37
41
|
.sub { color:#444; margin-bottom: 8px; }
|
|
38
42
|
|
|
39
|
-
/* Groot, vetgedrukt karnummer direct onder de titel — ook running header op elke pagina. */
|
|
40
43
|
.cart-number-hero {
|
|
41
44
|
position: running(cartno);
|
|
42
45
|
font-size: 88px;
|
|
@@ -64,28 +67,28 @@ export function renderPakbonHtml(order) {
|
|
|
64
67
|
</style>
|
|
65
68
|
</head>
|
|
66
69
|
<body>
|
|
67
|
-
<h1
|
|
68
|
-
<div class="sub"
|
|
70
|
+
<h1>${escapeHtml(t.packingSlip)}</h1>
|
|
71
|
+
<div class="sub">${escapeHtml(t.order)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</div>
|
|
69
72
|
|
|
70
73
|
<div class="cart-number-hero">${escapeHtml(cartNumber)}</div>
|
|
71
74
|
|
|
72
75
|
<div class="grid">
|
|
73
|
-
<div><div class="label"
|
|
74
|
-
<div><div class="label"
|
|
75
|
-
<div><div class="label"
|
|
76
|
-
<div><div class="label"
|
|
77
|
-
<div><div class="label"
|
|
78
|
-
<div><div class="label"
|
|
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>
|
|
79
82
|
</div>
|
|
80
83
|
|
|
81
84
|
<table>
|
|
82
|
-
<thead><tr><th
|
|
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>
|
|
83
86
|
<tbody>${rows}</tbody>
|
|
84
87
|
</table>
|
|
85
88
|
|
|
86
89
|
<div class="barcode-fallback">*${escapeHtml(order.orderNumber)}*</div>
|
|
87
90
|
|
|
88
|
-
<div class="footer"
|
|
91
|
+
<div class="footer">${escapeHtml(t.printedVia)} · ${new Date().toLocaleString(locale)}</div>
|
|
89
92
|
</body>
|
|
90
93
|
</html>`;
|
|
91
94
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PickstationPage.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/PickstationPage.tsx"],"names":[],"mappings":"AA2EA,wBAAgB,eAAe,gCA+X9B"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
|
+
import { LanguagePicker } from "./LanguagePicker";
|
|
6
|
+
import { renderPakbonHtml } from "./PakbonTemplate";
|
|
7
|
+
import { printHtml, printPdfBase64 } from "./printDocuments";
|
|
8
|
+
import { ControleLijstPrint } from "../afhandeling/ControleLijstPrint";
|
|
9
|
+
import { countryCode } from "../../lib/countries";
|
|
10
|
+
import { PICKSTATION_DICT, PICKSTATION_LANGUAGES, } from "../../lib/pickstationI18n";
|
|
11
|
+
const LANG_KEY = "pickstation.lang";
|
|
12
|
+
const LAST_GOOD_ORDERS_KEY = "pickstation.lastGoodOrders";
|
|
13
|
+
function statusBadge(status, t, printedCount) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case "not_printed":
|
|
16
|
+
return { label: t.notPrinted, className: "bg-slate-200 text-slate-700" };
|
|
17
|
+
case "printed":
|
|
18
|
+
return {
|
|
19
|
+
label: `✓ ${t.printed(printedCount || 1)}`,
|
|
20
|
+
className: "bg-emerald-100 text-emerald-800",
|
|
21
|
+
};
|
|
22
|
+
case "checked":
|
|
23
|
+
return { label: `✓ ${t.statusChecked}`, className: "bg-sky-100 text-sky-800" };
|
|
24
|
+
case "ready":
|
|
25
|
+
return { label: t.statusReady, className: "bg-indigo-100 text-indigo-800" };
|
|
26
|
+
case "shipped":
|
|
27
|
+
return { label: t.statusShipped, className: "bg-amber-100 text-amber-900" };
|
|
28
|
+
case "delivered":
|
|
29
|
+
return { label: `✓ ${t.statusDelivered}`, className: "bg-emerald-600 text-white" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const DAY_ABBR_BY_LANG = {
|
|
33
|
+
nl: ["ZO", "MA", "DI", "WO", "DO", "VR", "ZA"],
|
|
34
|
+
en: ["SU", "MO", "TU", "WE", "TH", "FR", "SA"],
|
|
35
|
+
pl: ["ND", "PN", "WT", "ŚR", "CZ", "PT", "SB"],
|
|
36
|
+
ro: ["DU", "LU", "MA", "MI", "JO", "VI", "SÂ"],
|
|
37
|
+
bg: ["НД", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"],
|
|
38
|
+
uk: ["НД", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"],
|
|
39
|
+
};
|
|
40
|
+
function computeKlaarzet(o, lang) {
|
|
41
|
+
if (!o.pickupDate || !o.pickupTime) {
|
|
42
|
+
return { label: "—", sortKey: Number.MAX_SAFE_INTEGER };
|
|
43
|
+
}
|
|
44
|
+
const [hh, mm] = o.pickupTime.split(":").map((n) => parseInt(n, 10));
|
|
45
|
+
const d = new Date(o.pickupDate);
|
|
46
|
+
if (Number.isNaN(d.getTime()) || Number.isNaN(hh) || Number.isNaN(mm)) {
|
|
47
|
+
return { label: "—", sortKey: Number.MAX_SAFE_INTEGER };
|
|
48
|
+
}
|
|
49
|
+
d.setHours(hh, mm, 0, 0);
|
|
50
|
+
// Klaarzet = ophaaltijd minus 1 uur.
|
|
51
|
+
d.setHours(d.getHours() - 1);
|
|
52
|
+
const day = (DAY_ABBR_BY_LANG[lang] ?? DAY_ABBR_BY_LANG.nl)[d.getDay()];
|
|
53
|
+
const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
54
|
+
return { label: `${day} - ${time}`, sortKey: d.getTime() };
|
|
55
|
+
}
|
|
56
|
+
export function PickstationPage() {
|
|
57
|
+
const navigate = useNavigate();
|
|
58
|
+
const [lang, setLang] = useState(null);
|
|
59
|
+
const [printingId, setPrintingId] = useState(null);
|
|
60
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
61
|
+
const [lastGoodOrders, setLastGoodOrders] = useState([]);
|
|
62
|
+
const [pendingConfirm, setPendingConfirm] = useState(null);
|
|
63
|
+
const [confirming, setConfirming] = useState(false);
|
|
64
|
+
const [printWarning, setPrintWarning] = useState(null);
|
|
65
|
+
const [showChecklist, setShowChecklist] = useState(false);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const stored = (typeof localStorage !== "undefined"
|
|
68
|
+
? localStorage.getItem(LANG_KEY)
|
|
69
|
+
: null);
|
|
70
|
+
const storedOrders = typeof localStorage !== "undefined"
|
|
71
|
+
? localStorage.getItem(LAST_GOOD_ORDERS_KEY)
|
|
72
|
+
: null;
|
|
73
|
+
if (storedOrders) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(storedOrders);
|
|
76
|
+
if (Array.isArray(parsed))
|
|
77
|
+
setLastGoodOrders(parsed);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
localStorage.removeItem(LAST_GOOD_ORDERS_KEY);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (stored && PICKSTATION_LANGUAGES.some((l) => l.code === stored)) {
|
|
84
|
+
setLang(stored);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
setPickerOpen(true);
|
|
88
|
+
}
|
|
89
|
+
}, []);
|
|
90
|
+
const pickLang = useCallback((l) => {
|
|
91
|
+
setLang(l);
|
|
92
|
+
setPickerOpen(false);
|
|
93
|
+
try {
|
|
94
|
+
localStorage.setItem(LANG_KEY, l);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* ignore */
|
|
98
|
+
}
|
|
99
|
+
}, []);
|
|
100
|
+
const ordersQuery = useQuery({
|
|
101
|
+
queryKey: ["pickstation", "orders"],
|
|
102
|
+
queryFn: async () => {
|
|
103
|
+
const res = await fetch("/api/public/pickstation/orders", { cache: "no-store" });
|
|
104
|
+
if (!res.ok)
|
|
105
|
+
throw new Error(`HTTP ${res.status}`);
|
|
106
|
+
return res.json();
|
|
107
|
+
},
|
|
108
|
+
refetchInterval: 30000,
|
|
109
|
+
refetchOnWindowFocus: true,
|
|
110
|
+
enabled: lang !== null,
|
|
111
|
+
});
|
|
112
|
+
const t = useMemo(() => PICKSTATION_DICT[lang ?? "nl"], [lang]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (ordersQuery.data && !ordersQuery.data.error) {
|
|
115
|
+
const next = ordersQuery.data.orders ?? [];
|
|
116
|
+
setLastGoodOrders(next);
|
|
117
|
+
try {
|
|
118
|
+
localStorage.setItem(LAST_GOOD_ORDERS_KEY, JSON.stringify(next));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* ignore */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [ordersQuery.data]);
|
|
125
|
+
const queryError = ordersQuery.data?.error ??
|
|
126
|
+
(ordersQuery.error instanceof Error ? ordersQuery.error.message : undefined);
|
|
127
|
+
const freshOrders = ordersQuery.data?.orders ?? [];
|
|
128
|
+
const unsortedOrders = queryError ? lastGoodOrders : freshOrders;
|
|
129
|
+
const orders = useMemo(() => {
|
|
130
|
+
return unsortedOrders
|
|
131
|
+
.map((o) => ({ o, k: computeKlaarzet(o, lang ?? "nl") }))
|
|
132
|
+
.sort((a, b) => {
|
|
133
|
+
if (a.k.sortKey !== b.k.sortKey)
|
|
134
|
+
return a.k.sortKey - b.k.sortKey;
|
|
135
|
+
const ac = a.o.country ?? "\uFFFF";
|
|
136
|
+
const bc = b.o.country ?? "\uFFFF";
|
|
137
|
+
if (ac !== bc)
|
|
138
|
+
return ac.localeCompare(bc);
|
|
139
|
+
const an = a.o.cartNumber ?? "\uFFFF";
|
|
140
|
+
const bn = b.o.cartNumber ?? "\uFFFF";
|
|
141
|
+
return an.localeCompare(bn);
|
|
142
|
+
})
|
|
143
|
+
.map((x) => x.o);
|
|
144
|
+
}, [unsortedOrders]);
|
|
145
|
+
const printedCount = orders.filter((o) => o.printedCount > 0).length;
|
|
146
|
+
const handlePrint = useCallback(async (order) => {
|
|
147
|
+
if (printingId || pendingConfirm)
|
|
148
|
+
return;
|
|
149
|
+
setPrintingId(order.id);
|
|
150
|
+
setPrintWarning(null);
|
|
151
|
+
try {
|
|
152
|
+
let auctionLetter = null;
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch("/api/public/pickstation/print", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
orderId: order.id,
|
|
159
|
+
sourceOrderId: order.orderId,
|
|
160
|
+
orderNumber: `${order.orderNumber} (kar ${order.cartIndex}/${order.cartTotal})`,
|
|
161
|
+
customerName: order.customerName,
|
|
162
|
+
documents: ["packing-slip", "auction-letter"],
|
|
163
|
+
confirmed: false,
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
if (res.ok) {
|
|
167
|
+
const body = (await res.json());
|
|
168
|
+
auctionLetter = body.auctionLetter ?? null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* network error – continue with pakbon only */
|
|
173
|
+
}
|
|
174
|
+
const html = renderPakbonHtml(order, lang ?? "nl");
|
|
175
|
+
await printHtml(html);
|
|
176
|
+
if (auctionLetter) {
|
|
177
|
+
await printPdfBase64(auctionLetter.base64, auctionLetter.mime);
|
|
178
|
+
}
|
|
179
|
+
setPendingConfirm(order);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
setPrintingId(null);
|
|
183
|
+
}
|
|
184
|
+
}, [printingId, pendingConfirm]);
|
|
185
|
+
const confirmPrinted = useCallback(async (yes) => {
|
|
186
|
+
const order = pendingConfirm;
|
|
187
|
+
if (!order)
|
|
188
|
+
return;
|
|
189
|
+
if (!yes) {
|
|
190
|
+
setPendingConfirm(null);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
setConfirming(true);
|
|
194
|
+
setPrintWarning(null);
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch("/api/public/pickstation/print", {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { "Content-Type": "application/json" },
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
orderId: order.id,
|
|
201
|
+
sourceOrderId: order.orderId,
|
|
202
|
+
orderNumber: `${order.orderNumber} (kar ${order.cartIndex}/${order.cartTotal})`,
|
|
203
|
+
customerName: order.customerName,
|
|
204
|
+
documents: ["packing-slip", "auction-letter"],
|
|
205
|
+
confirmed: true,
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
if (res.ok) {
|
|
209
|
+
const body = (await res.json());
|
|
210
|
+
if (body.warning)
|
|
211
|
+
setPrintWarning(body.warning);
|
|
212
|
+
setLang(null);
|
|
213
|
+
setPickerOpen(true);
|
|
214
|
+
try {
|
|
215
|
+
localStorage.removeItem(LANG_KEY);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* ignore */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
setPrintWarning(`Bevestigen mislukt (HTTP ${res.status}).`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
setPrintWarning(e instanceof Error ? e.message : "Bevestigen mislukt.");
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
setConfirming(false);
|
|
230
|
+
setPendingConfirm(null);
|
|
231
|
+
void ordersQuery.refetch();
|
|
232
|
+
}
|
|
233
|
+
}, [pendingConfirm, ordersQuery]);
|
|
234
|
+
const printNext = useCallback(() => {
|
|
235
|
+
const next = orders.find((o) => o.status === "not_printed" && o.printedCount === 0) ??
|
|
236
|
+
orders.find((o) => o.printedCount === 0);
|
|
237
|
+
if (next)
|
|
238
|
+
void handlePrint(next);
|
|
239
|
+
}, [orders, handlePrint]);
|
|
240
|
+
if (!lang || pickerOpen) {
|
|
241
|
+
return _jsx(LanguagePicker, { onPick: pickLang });
|
|
242
|
+
}
|
|
243
|
+
return (_jsxs("div", { className: "min-h-screen bg-slate-50 text-slate-900", children: [_jsx("header", { className: "bg-white border-b sticky top-0 z-10", children: _jsxs("div", { className: "max-w-6xl mx-auto px-4 py-4 flex items-center gap-3 flex-wrap", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: t.title }), _jsxs("p", { className: "text-sm text-slate-500", children: [t.subtitle, " \u00B7 ", t.ofTotal(printedCount, orders.length)] })] }), _jsxs("button", { onClick: () => setPickerOpen(true), className: "h-14 px-5 rounded-xl bg-white border-2 border-slate-300 hover:bg-slate-100 font-semibold text-lg", children: ["\uD83C\uDF10 ", t.changeLang] }), _jsxs("button", { onClick: () => setShowChecklist(true), className: "h-14 px-5 rounded-xl bg-white border-2 border-slate-300 hover:bg-slate-100 font-semibold text-lg", children: ["\uD83D\uDCCB ", t.printChecklist] }), _jsxs("button", { onClick: () => navigate({ to: "/controle" }), className: "h-14 px-5 rounded-xl bg-sky-600 hover:bg-sky-700 text-white font-semibold text-lg shadow", children: ["\u2713 ", t.runControl] }), _jsxs("button", { onClick: printNext, disabled: printingId !== null || orders.length === 0, className: "h-14 px-6 rounded-xl bg-emerald-600 hover:bg-emerald-700 disabled:bg-slate-300 text-white font-bold text-lg shadow", children: ["\uD83D\uDDA8 ", t.printNext] })] }) }), _jsxs("main", { className: "max-w-6xl mx-auto px-4 py-6", children: [ordersQuery.isLoading && _jsx("p", { className: "text-xl text-center py-12", children: t.loading }), queryError && orders.length > 0 && (_jsxs("div", { className: "bg-amber-50 border-2 border-amber-300 text-amber-900 rounded-xl p-4 mb-4", children: [_jsx("strong", { children: "Let op:" }), " verversen is niet gelukt, daarom blijven de laatst bekende pakbonnen zichtbaar. ", queryError] })), printWarning && (_jsxs("div", { className: "bg-amber-50 border-2 border-amber-300 text-amber-900 rounded-xl p-4 mb-4", children: [_jsx("strong", { children: "Let op:" }), " ", printWarning] })), queryError && orders.length === 0 && (_jsxs("div", { className: "bg-rose-50 border-2 border-rose-300 text-rose-900 rounded-xl p-4 mb-4", children: [_jsx("strong", { children: "Picklijst niet bijgewerkt:" }), " ", queryError, " De orders zijn hierdoor niet verwijderd; controleer de Pickagenda of probeer opnieuw."] })), !ordersQuery.isLoading && orders.length === 0 && !queryError && (_jsx("p", { className: "text-2xl text-center py-16 text-slate-500", children: t.empty })), orders.length > 0 && (_jsx("div", { className: "bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-sm", children: _jsxs("table", { className: "w-full text-lg", children: [_jsx("thead", { className: "bg-slate-100 text-slate-600 text-sm uppercase tracking-wide", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left px-5 py-3", children: "Karnr" }), _jsx("th", { className: "text-left px-5 py-3", children: t.orderNumber }), _jsx("th", { className: "text-right px-5 py-3", children: "Fusten" }), _jsx("th", { className: "text-left px-5 py-3", children: "Land" }), _jsx("th", { className: "text-left px-5 py-3", children: "Klaarzetten" }), _jsx("th", { className: "text-left px-5 py-3", children: t.status })] }) }), _jsx("tbody", { children: orders.map((o) => {
|
|
244
|
+
const isPrinting = printingId === o.id;
|
|
245
|
+
const badge = statusBadge(o.status, t, o.printedCount);
|
|
246
|
+
const klaarzet = computeKlaarzet(o, lang ?? "nl");
|
|
247
|
+
return (_jsxs("tr", { className: `border-t border-slate-100 transition select-none ${isPrinting ? "bg-emerald-50" : "hover:bg-slate-50"}`, style: { minHeight: 64 }, children: [_jsx("td", { className: "px-5 py-4 whitespace-nowrap cursor-pointer", onClick: () => void handlePrint(o), children: _jsx("span", { className: "inline-block px-3 py-1.5 rounded-md bg-black text-white font-black text-xl tracking-widest font-mono whitespace-nowrap", children: o.cartNumber ?? "—" }) }), _jsx("td", { className: "px-5 py-4 font-semibold whitespace-nowrap cursor-pointer", onClick: () => void handlePrint(o), children: o.orderNumber ?? "—" }), _jsx("td", { className: "px-5 py-4 text-right font-semibold", children: o.cratesOnCart != null ? o.cratesOnCart : "—" }), _jsx("td", { className: "px-5 py-4 font-semibold whitespace-nowrap", children: countryCode(o.country) ?? o.country ?? "—" }), _jsx("td", { className: "px-5 py-4 font-semibold whitespace-nowrap", children: klaarzet.label }), _jsx("td", { className: "px-5 py-4", children: isPrinting ? (_jsx("span", { className: "inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-600 text-white font-semibold", children: t.printingNow })) : (_jsx("span", { className: `inline-flex items-center gap-2 px-3 py-1 rounded-full font-semibold ${badge.className}`, children: badge.label })) })] }, o.id));
|
|
248
|
+
}) })] }) })), _jsx("p", { className: "text-xs text-slate-400 mt-6 text-center", children: t.printDialogHint })] }), pendingConfirm && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: _jsxs("div", { className: "bg-white rounded-2xl shadow-2xl max-w-md w-full p-6", children: [_jsx("h2", { className: "text-2xl font-bold mb-2", children: t.confirmPrintedTitle }), _jsx("p", { className: "text-slate-600 mb-2", children: t.confirmPrintedBody }), _jsx("div", { className: "mb-4 inline-block px-4 py-2 rounded-md bg-black text-white font-black text-3xl tracking-widest font-mono", children: pendingConfirm.cartNumber ?? "—" }), _jsxs("p", { className: "text-slate-800 font-semibold mb-6", children: [pendingConfirm.orderNumber, " \u00B7 kar ", pendingConfirm.cartIndex, "/", pendingConfirm.cartTotal] }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("button", { onClick: () => void confirmPrinted(true), disabled: confirming, className: "h-14 rounded-xl bg-emerald-600 hover:bg-emerald-700 disabled:bg-slate-300 text-white font-bold text-lg shadow", children: ["\u2713 ", t.confirmYes] }), _jsxs("button", { onClick: () => void confirmPrinted(false), disabled: confirming, className: "h-14 rounded-xl bg-white border-2 border-slate-300 hover:bg-slate-100 disabled:opacity-60 font-semibold text-lg", children: ["\u2715 ", t.confirmNo] })] })] }) })), showChecklist && (_jsx(ControleLijstPrint, { lang: lang ?? "nl", carts: orders.map((o) => ({
|
|
249
|
+
cartNumberId: null,
|
|
250
|
+
cartNumber: o.cartNumber,
|
|
251
|
+
orderId: o.orderId,
|
|
252
|
+
orderSource: o.orderSource,
|
|
253
|
+
cartIndex: o.cartIndex - 1,
|
|
254
|
+
orderNumber: o.orderNumber,
|
|
255
|
+
customerName: o.customerName,
|
|
256
|
+
carrierName: o.carrierName,
|
|
257
|
+
deliveryDate: o.deliveryDate,
|
|
258
|
+
pickupDate: o.pickupDate,
|
|
259
|
+
pickupTime: o.pickupTime,
|
|
260
|
+
country: o.country,
|
|
261
|
+
cratesOnCart: o.cratesOnCart,
|
|
262
|
+
pickDate: o.deliveryDate ?? "",
|
|
263
|
+
printedCount: o.printedCount,
|
|
264
|
+
status: o.status,
|
|
265
|
+
})), onClose: () => setShowChecklist(false) }))] }));
|
|
266
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AfhandelingSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AfhandelingSettingsCard.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"AfhandelingSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AfhandelingSettingsCard.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,uBAAuB,gCA4TtC"}
|
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label, Switch, toast, } from "@flowselections/core";
|
|
6
|
-
import { CalendarClock, CalendarDays, Mail, Scale } from "lucide-react";
|
|
6
|
+
import { CalendarClock, CalendarDays, Mail, Pause, Printer, Scale, Sticker } from "lucide-react";
|
|
7
7
|
import { getAfhandelingSettings, updateAfhandelingSettings, } from "../../lib/afhandelingSettings.functions";
|
|
8
8
|
import { WeekdayPicker } from "../afhandeling/WeekdayPicker";
|
|
9
9
|
export function AfhandelingSettingsCard() {
|
|
@@ -26,6 +26,9 @@ export function AfhandelingSettingsCard() {
|
|
|
26
26
|
signupMailLeadMinutes: 60,
|
|
27
27
|
signupMailPurpose: "afhandeling_carrier_signup",
|
|
28
28
|
pickWeekdays: [1, 2, 3, 4, 5],
|
|
29
|
+
extraPickDaysWithStickers: 0,
|
|
30
|
+
autoProcessPaused: false,
|
|
31
|
+
autoPrintDeliveryNote: false,
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
},
|
|
@@ -38,6 +41,9 @@ export function AfhandelingSettingsCard() {
|
|
|
38
41
|
const [mailLead, setMailLead] = useState(60);
|
|
39
42
|
const [mailPurpose, setMailPurpose] = useState("afhandeling_carrier_signup");
|
|
40
43
|
const [pickWeekdays, setPickWeekdays] = useState([1, 2, 3, 4, 5]);
|
|
44
|
+
const [extraStickerDays, setExtraStickerDays] = useState(0);
|
|
45
|
+
const [autoProcessPaused, setAutoProcessPaused] = useState(false);
|
|
46
|
+
const [autoPrintDelivery, setAutoPrintDelivery] = useState(false);
|
|
41
47
|
useEffect(() => {
|
|
42
48
|
if (data) {
|
|
43
49
|
setPickDays(data.pickDaysBeforeTransport);
|
|
@@ -50,6 +56,9 @@ export function AfhandelingSettingsCard() {
|
|
|
50
56
|
setPickWeekdays(Array.isArray(data.pickWeekdays) && data.pickWeekdays.length > 0
|
|
51
57
|
? data.pickWeekdays
|
|
52
58
|
: [1, 2, 3, 4, 5]);
|
|
59
|
+
setExtraStickerDays(data.extraPickDaysWithStickers ?? 0);
|
|
60
|
+
setAutoProcessPaused(Boolean(data.autoProcessPaused));
|
|
61
|
+
setAutoPrintDelivery(Boolean(data.autoPrintDeliveryNote));
|
|
53
62
|
}
|
|
54
63
|
}, [data]);
|
|
55
64
|
const mutation = useMutation({
|
|
@@ -63,6 +72,9 @@ export function AfhandelingSettingsCard() {
|
|
|
63
72
|
signupMailLeadMinutes: mailLead,
|
|
64
73
|
signupMailPurpose: mailPurpose,
|
|
65
74
|
pickWeekdays,
|
|
75
|
+
extraPickDaysWithStickers: extraStickerDays,
|
|
76
|
+
autoProcessPaused,
|
|
77
|
+
autoPrintDeliveryNote: autoPrintDelivery,
|
|
66
78
|
},
|
|
67
79
|
}),
|
|
68
80
|
onSuccess: async () => {
|
|
@@ -75,5 +87,5 @@ export function AfhandelingSettingsCard() {
|
|
|
75
87
|
},
|
|
76
88
|
});
|
|
77
89
|
const clamp = (n) => Math.max(0, Math.min(30, Math.floor(n) || 0));
|
|
78
|
-
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Afhandeling \u2014 Planning" })] }), _jsx(CardDescription, { children: "Centrale werkdag-instellingen voor de automatische order\u00ADplanning. Alle berekeningen tellen alleen werkdagen (ma\u2013vr)." })] }), _jsxs(CardContent, { className: "space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "pickDays", children: "Aantal pickdagen (werkdagen v\u00F3\u00F3r ophaaldag)" }), _jsx(Input, { id: "pickDays", type: "number", min: 0, max: 30, value: pickDays, onChange: (e) => setPickDays(clamp(Number(e.target.value))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt vanaf de berekende ophaaldag teruggeteld om de uiterste pickdatum te bepalen." })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarDays, { className: "h-4 w-4 text-primary" }), _jsx(Label, { className: "font-semibold", children: "Orderpickdagen" })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Selecteer de dagen waarop het systeem orders mag inplannen om te picken. Andere dagen worden automatisch overgeslagen." }), _jsx(WeekdayPicker, { value: pickWeekdays, onChange: setPickWeekdays, idPrefix: "pick-wd" }), pickWeekdays.length === 0 ? (_jsx("p", { className: "text-xs text-destructive", children: "Kies minimaal \u00E9\u00E9n pickdag; anders kan het systeem geen orders inplannen." })) : null] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Scale, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "pickDistribution", className: "font-semibold", children: "Pickverdeling" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Verdeelt de pickbelasting automatisch over werkdagen. Orders mogen eerder ingepland worden, maar nooit later dan de berekende uiterste pickdatum \u2014 leverbetrouwbaarheid blijft gegarandeerd." })] }), _jsx(Switch, { id: "pickDistribution", checked: distributionEnabled, onCheckedChange: setDistributionEnabled })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "maxEarlier", children: "Maximaal aantal werkdagen eerder picken" }), _jsx(Input, { id: "maxEarlier", type: "number", min: 0, max: 30, value: maxEarlier, disabled: !distributionEnabled, onChange: (e) => setMaxEarlier(clamp(Number(e.target.value))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "0 = geen vervroeging. Bijv. 2 = orders mogen maximaal 2 werkdagen voor hun uiterste pickdatum ingepland worden." })] })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "mailEnabled", className: "font-semibold", children: "Aanmeldmail vervoerders" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Verstuurt automatisch een mail met de te verwachten karren naar de vervoerder v\u00F3\u00F3r de aanmelddeadline van het ophaalmoment. E\u00E9n mail per ophaaldag/-moment per vervoerder." })] }), _jsx(Switch, { id: "mailEnabled", checked: mailEnabled, onCheckedChange: setMailEnabled })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailLead", children: "Minuten v\u00F3\u00F3r aanmelddeadline versturen" }), _jsx(Input, { id: "mailLead", type: "number", min: 0, max: 1440, value: mailLead, disabled: !mailEnabled, onChange: (e) => setMailLead(Math.max(0, Math.min(1440, Math.floor(Number(e.target.value)) || 0))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailPurpose", children: "E-mail-doeleinde (purpose)" }), _jsx(Input, { id: "mailPurpose", value: mailPurpose, disabled: !mailEnabled, onChange: (e) => setMailPurpose(e.target.value) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Koppel dit doeleinde in de module ", _jsx("strong", { children: "E-mailkoppeling" }), " aan een actief send-adres. Standaard: ", _jsx("code", { children: "afhandeling_carrier_signup" }), "."] })] })] }), _jsxs("div", { className: "rounded-md bg-muted/50 p-3 text-xs text-muted-foreground", children: [_jsx("strong", { children: "Ophaaltijd" }), " en ", _jsx("strong", { children: "Aanmeldtijd" }), " stel je per vervoerder in (werkdagen v\u00F3\u00F3r bezorgdag). De vervoerder die als ", _jsx("strong", { children: "standaard" }), " is gemarkeerd, wordt gebruikt voor orders zonder eigen vervoerder."] }), _jsx(Button, { onClick: () => mutation.mutate(), disabled: mutation.isPending, children: mutation.isPending ? "Opslaan…" : "Opslaan" })] })] }));
|
|
90
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Afhandeling \u2014 Planning" })] }), _jsx(CardDescription, { children: "Centrale werkdag-instellingen voor de automatische order\u00ADplanning. Alle berekeningen tellen alleen werkdagen (ma\u2013vr)." })] }), _jsxs(CardContent, { className: "space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "pickDays", children: "Aantal pickdagen (werkdagen v\u00F3\u00F3r ophaaldag)" }), _jsx(Input, { id: "pickDays", type: "number", min: 0, max: 30, value: pickDays, onChange: (e) => setPickDays(clamp(Number(e.target.value))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt vanaf de berekende ophaaldag teruggeteld om de uiterste pickdatum te bepalen." })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Sticker, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "extraStickerDays", className: "font-semibold", children: "Extra pickdagen bij stickers" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Wanneer een order stickers bevat, mag het picken langer duren. Deze werkdagen worden bovenop het standaard aantal pickdagen opgeteld, zodat de uiterste pickdatum vroeger valt." }), _jsx(Input, { id: "extraStickerDays", type: "number", min: 0, max: 30, value: extraStickerDays, onChange: (e) => setExtraStickerDays(clamp(Number(e.target.value))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Voorbeeld: standaard 1 pickdag + 2 extra = 3 werkdagen picktijd voor orders met stickers." })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarDays, { className: "h-4 w-4 text-primary" }), _jsx(Label, { className: "font-semibold", children: "Orderpickdagen" })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Selecteer de dagen waarop het systeem orders mag inplannen om te picken. Andere dagen worden automatisch overgeslagen." }), _jsx(WeekdayPicker, { value: pickWeekdays, onChange: setPickWeekdays, idPrefix: "pick-wd" }), pickWeekdays.length === 0 ? (_jsx("p", { className: "text-xs text-destructive", children: "Kies minimaal \u00E9\u00E9n pickdag; anders kan het systeem geen orders inplannen." })) : null] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Scale, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "pickDistribution", className: "font-semibold", children: "Pickverdeling" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Verdeelt de pickbelasting automatisch over werkdagen. Orders mogen eerder ingepland worden, maar nooit later dan de berekende uiterste pickdatum \u2014 leverbetrouwbaarheid blijft gegarandeerd." })] }), _jsx(Switch, { id: "pickDistribution", checked: distributionEnabled, onCheckedChange: setDistributionEnabled })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "maxEarlier", children: "Maximaal aantal werkdagen eerder picken" }), _jsx(Input, { id: "maxEarlier", type: "number", min: 0, max: 30, value: maxEarlier, disabled: !distributionEnabled, onChange: (e) => setMaxEarlier(clamp(Number(e.target.value))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "0 = geen vervroeging. Bijv. 2 = orders mogen maximaal 2 werkdagen voor hun uiterste pickdatum ingepland worden." })] })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "mailEnabled", className: "font-semibold", children: "Aanmeldmail vervoerders" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Verstuurt automatisch een mail met de te verwachten karren naar de vervoerder v\u00F3\u00F3r de aanmelddeadline van het ophaalmoment. E\u00E9n mail per ophaaldag/-moment per vervoerder." })] }), _jsx(Switch, { id: "mailEnabled", checked: mailEnabled, onCheckedChange: setMailEnabled })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailLead", children: "Minuten v\u00F3\u00F3r aanmelddeadline versturen" }), _jsx(Input, { id: "mailLead", type: "number", min: 0, max: 1440, value: mailLead, disabled: !mailEnabled, onChange: (e) => setMailLead(Math.max(0, Math.min(1440, Math.floor(Number(e.target.value)) || 0))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailPurpose", children: "E-mail-doeleinde (purpose)" }), _jsx(Input, { id: "mailPurpose", value: mailPurpose, disabled: !mailEnabled, onChange: (e) => setMailPurpose(e.target.value) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Koppel dit doeleinde in de module ", _jsx("strong", { children: "E-mailkoppeling" }), " aan een actief send-adres. Standaard: ", _jsx("code", { children: "afhandeling_carrier_signup" }), "."] })] })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Pause, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "autoPause", className: "font-semibold", children: "Automatische Floriday-verwerking pauzeren" })] }), _jsxs("p", { className: "text-xs text-muted-foreground max-w-prose", children: ["Zet dit aan om ", _jsx("strong", { children: "tijdelijk" }), " te stoppen met automatisch Sales Orders acknowledgen, delivery orders aanmaken en fulfillment orders versturen. Bestaande orders in de wachtrij blijven staan."] })] }), _jsx(Switch, { id: "autoPause", checked: autoProcessPaused, onCheckedChange: setAutoProcessPaused })] }), _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Printer, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "autoPrint", className: "font-semibold", children: "Afleverbrief automatisch printen" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Wanneer een afleverbrief beschikbaar komt, wordt hij direct naar de standaardprinter gestuurd. Handig voor onbemande balies." })] }), _jsx(Switch, { id: "autoPrint", checked: autoPrintDelivery, onCheckedChange: setAutoPrintDelivery })] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Timing van de automatische verwerking wordt bepaald door", " ", _jsx("strong", { children: "Maximaal aantal werkdagen eerder picken" }), ",", " ", _jsx("strong", { children: "Orderpickdagen" }), " en", " ", _jsx("strong", { children: "Extra pickdagen bij stickers" }), " hierboven."] })] }), _jsxs("div", { className: "rounded-md bg-muted/50 p-3 text-xs text-muted-foreground", children: [_jsx("strong", { children: "Ophaaltijd" }), " en ", _jsx("strong", { children: "Aanmeldtijd" }), " stel je per vervoerder in (werkdagen v\u00F3\u00F3r bezorgdag). De vervoerder die als ", _jsx("strong", { children: "standaard" }), " is gemarkeerd, wordt gebruikt voor orders zonder eigen vervoerder."] }), _jsx(Button, { onClick: () => mutation.mutate(), disabled: mutation.isPending, children: mutation.isPending ? "Opslaan…" : "Opslaan" })] })] }));
|
|
79
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAI/D,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAI/D,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAExF,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,gBAAgB,IAAI,sBAAsB,CAkJzD"}
|
|
@@ -5,6 +5,7 @@ import { getFloridayOrders } from "../lib/floriday.functions";
|
|
|
5
5
|
import { getAfhandelingSettings } from "../lib/afhandelingSettings.functions";
|
|
6
6
|
import { listCarriers } from "../lib/carriers.functions";
|
|
7
7
|
import { listCartOverrides } from "../lib/cartAssignments.functions";
|
|
8
|
+
import { normalizePickupMoments, sanitizeWeekdays, selectCarrierForTarget } from "../lib/carrierPlanning";
|
|
8
9
|
import { calculateOrderPlanning, distributePickDates, DEFAULT_PICKUP_MOMENT, } from "../lib/planning";
|
|
9
10
|
export function useOrderPlanning() {
|
|
10
11
|
const fetchOrders = useServerFn(getFloridayOrders);
|
|
@@ -52,7 +53,7 @@ export function useOrderPlanning() {
|
|
|
52
53
|
return await fetchSettings();
|
|
53
54
|
}
|
|
54
55
|
catch {
|
|
55
|
-
return { pickDaysBeforeTransport: 1, transportRegisterDaysBefore: 1, pickDistributionEnabled: false, maxWorkdaysEarlierPick: 2, signupMailEnabled: false, signupMailLeadMinutes: 60, signupMailPurpose: "afhandeling_carrier_signup", pickWeekdays: [1, 2, 3, 4, 5] };
|
|
56
|
+
return { pickDaysBeforeTransport: 1, transportRegisterDaysBefore: 1, pickDistributionEnabled: false, maxWorkdaysEarlierPick: 2, signupMailEnabled: false, signupMailLeadMinutes: 60, signupMailPurpose: "afhandeling_carrier_signup", pickWeekdays: [1, 2, 3, 4, 5], extraPickDaysWithStickers: 0 };
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
});
|
|
@@ -88,15 +89,10 @@ export function useOrderPlanning() {
|
|
|
88
89
|
pickDistributionEnabled: false,
|
|
89
90
|
maxWorkdaysEarlierPick: 2,
|
|
90
91
|
pickWeekdays: [1, 2, 3, 4, 5],
|
|
92
|
+
extraPickDaysWithStickers: 0,
|
|
91
93
|
};
|
|
92
94
|
const carriers = carriersQuery.data?.carriers ?? [];
|
|
93
95
|
const overrides = overridesQuery.data?.overrides ?? [];
|
|
94
|
-
const defaultCarrier = carriers.find((c) => c.is_default);
|
|
95
|
-
const moments = defaultCarrier && defaultCarrier.pickup_moments.length > 0
|
|
96
|
-
? defaultCarrier.pickup_moments
|
|
97
|
-
: [DEFAULT_PICKUP_MOMENT];
|
|
98
|
-
const carrierPickupWeekdays = defaultCarrier?.pickup_weekdays ?? [1, 2, 3, 4, 5];
|
|
99
|
-
const carrierDeliveryWeekdays = defaultCarrier?.delivery_weekdays ?? [1, 2, 3, 4, 5];
|
|
100
96
|
const pickWeekdays = settings.pickWeekdays ?? [1, 2, 3, 4, 5];
|
|
101
97
|
const overrideMap = new Map();
|
|
102
98
|
for (const o of overrides) {
|
|
@@ -106,16 +102,32 @@ export function useOrderPlanning() {
|
|
|
106
102
|
});
|
|
107
103
|
}
|
|
108
104
|
const initial = orders.map((order) => {
|
|
105
|
+
const carrier = selectCarrierForTarget(carriers, {
|
|
106
|
+
carrierId: order.carrierId,
|
|
107
|
+
country: order.country,
|
|
108
|
+
province: order.province,
|
|
109
|
+
city: order.city,
|
|
110
|
+
});
|
|
111
|
+
const moments = carrier
|
|
112
|
+
? normalizePickupMoments(carrier.pickup_moments)
|
|
113
|
+
: [DEFAULT_PICKUP_MOMENT];
|
|
114
|
+
const carrierPickupWeekdays = sanitizeWeekdays(carrier?.pickup_weekdays);
|
|
115
|
+
const carrierDeliveryWeekdays = sanitizeWeekdays(carrier?.delivery_weekdays);
|
|
116
|
+
const extraStickerDays = Math.max(0, Math.floor(settings.extraPickDaysWithStickers ?? 0));
|
|
117
|
+
const pickDuration = settings.pickDaysBeforeTransport + (order.hasStickers ? extraStickerDays : 0);
|
|
109
118
|
const planning = calculateOrderPlanning(order.deliveryDate, {
|
|
110
119
|
pickupMoments: moments,
|
|
111
|
-
pickDurationWorkdays:
|
|
120
|
+
pickDurationWorkdays: pickDuration,
|
|
112
121
|
orderCreatedAt: order.createdAt,
|
|
113
122
|
pickWeekdays,
|
|
114
123
|
carrierPickupWeekdays,
|
|
115
124
|
carrierDeliveryWeekdays,
|
|
125
|
+
carrierId: carrier?.id ?? null,
|
|
126
|
+
carrierName: carrier?.name ?? null,
|
|
127
|
+
orderId: order.id,
|
|
116
128
|
});
|
|
117
|
-
if (planning &&
|
|
118
|
-
planning.carrierName =
|
|
129
|
+
if (planning && carrier) {
|
|
130
|
+
planning.carrierName = carrier.name;
|
|
119
131
|
}
|
|
120
132
|
return { order, planning };
|
|
121
133
|
});
|
|
@@ -137,6 +149,11 @@ export function useOrderPlanning() {
|
|
|
137
149
|
isLoading: ordersQuery.isLoading || settingsQuery.isLoading || carriersQuery.isLoading || overridesQuery.isLoading,
|
|
138
150
|
error: ordersQuery.data?.orders?.length ? undefined : ordersQuery.data?.error,
|
|
139
151
|
warning: ordersQuery.data?.orders?.length ? ordersQuery.data?.error : undefined,
|
|
140
|
-
refetch: () => {
|
|
152
|
+
refetch: () => {
|
|
153
|
+
void ordersQuery.refetch();
|
|
154
|
+
void settingsQuery.refetch();
|
|
155
|
+
void carriersQuery.refetch();
|
|
156
|
+
void overridesQuery.refetch();
|
|
157
|
+
},
|
|
141
158
|
};
|
|
142
159
|
}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -9,5 +9,9 @@ export { PlanningAgendaPage } from "./components/afhandeling/PlanningAgendaPage"
|
|
|
9
9
|
export { ControlePage } from "./components/afhandeling/ControlePage";
|
|
10
10
|
export { AfhandelingSettingsCard } from "./components/settings/AfhandelingSettingsCard";
|
|
11
11
|
export { VervoerdersSettingsCard } from "./components/settings/VervoerdersSettingsCard";
|
|
12
|
+
export { PickstationPage } from "./components/pickstation/PickstationPage";
|
|
13
|
+
export { KioskControle } from "./components/pickstation/KioskControle";
|
|
14
|
+
export { calculateEarliestDeliveryDate, calculateOrderPlanning, DEFAULT_PICKUP_MOMENT, type EarliestDeliveryRules, type OrderPlanning, type PickupMoment, } from "./lib/planning";
|
|
15
|
+
export { carrierLeadDaysFromMoments, normalizePickupMoments, sanitizeWeekdays, selectCarrierForTarget, type CarrierOrderTarget, type CarrierPlanningInfo, } from "./lib/carrierPlanning";
|
|
12
16
|
export declare const floridayAfhandelingModule: FlowModule;
|
|
13
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACvE,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC;AAK/B,eAAO,MAAM,yBAAyB,EAAE,UAkBvC,CAAC"}
|
package/dist-lib/index.js
CHANGED
|
@@ -9,6 +9,10 @@ export { PlanningAgendaPage } from "./components/afhandeling/PlanningAgendaPage"
|
|
|
9
9
|
export { ControlePage } from "./components/afhandeling/ControlePage";
|
|
10
10
|
export { AfhandelingSettingsCard } from "./components/settings/AfhandelingSettingsCard";
|
|
11
11
|
export { VervoerdersSettingsCard } from "./components/settings/VervoerdersSettingsCard";
|
|
12
|
+
export { PickstationPage } from "./components/pickstation/PickstationPage";
|
|
13
|
+
export { KioskControle } from "./components/pickstation/KioskControle";
|
|
14
|
+
export { calculateEarliestDeliveryDate, calculateOrderPlanning, DEFAULT_PICKUP_MOMENT, } from "./lib/planning";
|
|
15
|
+
export { carrierLeadDaysFromMoments, normalizePickupMoments, sanitizeWeekdays, selectCarrierForTarget, } from "./lib/carrierPlanning";
|
|
12
16
|
import { AfhandelingSettingsCard } from "./components/settings/AfhandelingSettingsCard";
|
|
13
17
|
import { VervoerdersSettingsCard } from "./components/settings/VervoerdersSettingsCard";
|
|
14
18
|
export const floridayAfhandelingModule = {
|