@flowselections/floriday-afhandeling-module 1.0.42 → 1.0.44
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 +3 -1
- package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/AutoProcessStatus.js +36 -3
- 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 +23 -25
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +410 -121
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickAgendaView.js +4 -4
- package/dist-lib/components/afhandeling/PickingPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickingPage.js +1 -1
- package/dist-lib/components/afhandeling/ScanControleView.d.ts +2 -0
- package/dist-lib/components/afhandeling/ScanControleView.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/ScanControleView.js +158 -0
- package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
- package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +25 -2
- package/dist-lib/components/pickstation/PakbonTemplate.js +70 -70
- package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
- package/dist-lib/hooks/useCartIndex.js +6 -1
- package/dist-lib/hooks/useEngineBufferEvents.d.ts +7 -0
- package/dist-lib/hooks/useEngineBufferEvents.d.ts.map +1 -0
- package/dist-lib/hooks/useEngineBufferEvents.js +47 -0
- package/dist-lib/hooks/useEngineTasks.js +1 -1
- package/dist-lib/hooks/useKnownOrders.d.ts +12 -0
- package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -0
- package/dist-lib/hooks/useKnownOrders.js +79 -0
- package/dist-lib/hooks/useOrderBuffers.d.ts +17 -0
- package/dist-lib/hooks/useOrderBuffers.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderBuffers.js +53 -0
- package/dist-lib/hooks/useOrderPickDates.d.ts +10 -0
- package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderPickDates.js +62 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts +1 -3
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +9 -13
- package/dist-lib/hooks/usePickAgenda.d.ts +4 -5
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
- package/dist-lib/hooks/usePickAgenda.js +65 -9
- package/dist-lib/hooks/usePickMode.d.ts +10 -0
- package/dist-lib/hooks/usePickMode.d.ts.map +1 -0
- package/dist-lib/hooks/usePickMode.js +31 -0
- package/dist-lib/index.d.ts +2 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +19 -6
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2174 -60
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2174 -60
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +2174 -60
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2381 -225
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +4452 -228
- 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 +11161 -591
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +11161 -591
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +48433 -1230
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +134 -10
- package/dist-lib/lib/deliveryNote.functions.d.ts +26064 -0
- package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -0
- package/dist-lib/lib/deliveryNote.functions.js +32 -0
- package/dist-lib/lib/engineLayouts.functions.d.ts +26062 -0
- package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -0
- package/dist-lib/lib/engineLayouts.functions.js +42 -0
- package/dist-lib/lib/floriday.functions.d.ts +28367 -186
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +119 -343
- package/dist-lib/lib/logisticsEngine/reschedule.d.ts +17 -0
- package/dist-lib/lib/logisticsEngine/reschedule.d.ts.map +1 -1
- package/dist-lib/lib/logisticsEngine/reschedule.js +63 -0
- package/dist-lib/lib/orderFulfilment.functions.d.ts +26064 -0
- package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -0
- package/dist-lib/lib/orderFulfilment.functions.js +35 -0
- package/dist-lib/lib/orderStatus.d.ts +10 -0
- package/dist-lib/lib/orderStatus.d.ts.map +1 -0
- package/dist-lib/lib/orderStatus.js +81 -0
- package/dist-lib/lib/pickDates.functions.d.ts +26069 -0
- package/dist-lib/lib/pickDates.functions.d.ts.map +1 -0
- package/dist-lib/lib/pickDates.functions.js +56 -0
- package/dist-lib/lib/pickMode.d.ts +14 -0
- package/dist-lib/lib/pickMode.d.ts.map +1 -0
- package/dist-lib/lib/pickMode.js +103 -0
- package/dist-lib/lib/printing.d.ts +3 -1
- package/dist-lib/lib/printing.d.ts.map +1 -1
- package/dist-lib/lib/printing.js +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AA2BD,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,+
|
|
1
|
+
{"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AA2BD,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,+BAwFnE"}
|
|
@@ -20,9 +20,9 @@ function CategoryLine({ items, label, }) {
|
|
|
20
20
|
export function PickAgendaView({ onOrderClick }) {
|
|
21
21
|
const [weekStart, setWeekStart] = useState(() => currentWeekStart());
|
|
22
22
|
const range = useMemo(() => agendaRange(weekStart), [weekStart]);
|
|
23
|
-
const { planned, awaiting,
|
|
23
|
+
const { planned, awaiting, tasksWithoutStart, nearestDeliveryDate, signalError, lastEngineRun, isLoading, error, warning, refetch, } = useOrderPlanning(range);
|
|
24
24
|
const awaitingCount = awaiting.length;
|
|
25
|
-
const anyIssue = awaitingCount +
|
|
25
|
+
const anyIssue = awaitingCount + tasksWithoutStart.length > 0;
|
|
26
26
|
const lastRunLabel = lastEngineRun
|
|
27
27
|
? new Date(lastEngineRun).toLocaleString("nl-NL", {
|
|
28
28
|
timeZone: "Europe/Amsterdam",
|
|
@@ -31,11 +31,11 @@ export function PickAgendaView({ onOrderClick }) {
|
|
|
31
31
|
})
|
|
32
32
|
: null;
|
|
33
33
|
const [dayDetailDate, setDayDetailDate] = useState(null);
|
|
34
|
-
return (_jsxs("div", { className: "
|
|
34
|
+
return (_jsxs("div", { className: "flex h-full flex-col gap-6 overflow-hidden", children: [anyIssue && (_jsxs("div", { className: "shrink-0 space-y-1 rounded-md border border-sky-300 bg-sky-50 p-4 text-sm text-sky-900", children: [_jsx(CategoryLine, { items: awaiting, label: (n) => `${n} order${n === 1 ? "" : "s"} zonder indeling van de Logistics Engine, daarom nog niet in de agenda` }), _jsx(CategoryLine, { items: tasksWithoutStart, label: (n) => `${n} order${n === 1 ? "" : "s"} met een Engine-taak zonder geldige starttijd` }), _jsxs("div", { className: "text-xs", children: [lastRunLabel
|
|
35
35
|
? `Laatste indeling door de Engine: ${lastRunLabel}.`
|
|
36
36
|
: "Er is nog geen indeling van de Engine bekend voor deze periode.", " ", awaitingCount === 0
|
|
37
37
|
? "Orders die buiten deze week zijn ingedeeld staan hier niet bij."
|
|
38
38
|
: signalError
|
|
39
39
|
? `Melding vanuit deze module is mislukt: ${signalError}`
|
|
40
|
-
: "Melding vanuit deze module is verstuurd; het inplannen gebeurt door de Logistics Engine."] })] })), warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx(WeekAgendaView, { planned: planned, mode: "pick", isLoading: isLoading, onOrderClick: onOrderClick, onDayClick: setDayDetailDate, onWeekChange: setWeekStart, jumpToDate: nearestDeliveryDate })), _jsx(DagDetailDialog, { open: !!dayDetailDate, date: dayDetailDate, planned: planned, onOpenChange: (o) => !o && setDayDetailDate(null) })] }));
|
|
40
|
+
: "Melding vanuit deze module is verstuurd; het inplannen gebeurt door de Logistics Engine."] })] })), warning && !error && (_jsxs("div", { className: "shrink-0 rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "shrink-0 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx("div", { className: "min-h-0 flex-1", children: _jsx(WeekAgendaView, { planned: planned, mode: "pick", isLoading: isLoading, onOrderClick: onOrderClick, onDayClick: setDayDetailDate, onWeekChange: setWeekStart, jumpToDate: nearestDeliveryDate }) })), _jsx(DagDetailDialog, { open: !!dayDetailDate, date: dayDetailDate, planned: planned, onOpenChange: (o) => !o && setDayDetailDate(null) })] }));
|
|
41
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PickingPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickingPage.tsx"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,
|
|
1
|
+
{"version":3,"file":"PickingPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickingPage.tsx"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,gCAc1B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PickAgendaView } from "./PickAgendaView";
|
|
3
3
|
export function PickingPage() {
|
|
4
|
-
return (_jsxs("main", { className: "
|
|
4
|
+
return (_jsxs("main", { className: "flex h-full flex-col gap-6 overflow-hidden p-6", children: [_jsxs("div", { className: "shrink-0", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Pick Agenda" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Overzicht van alle orders die gepickt moeten worden." })] }), _jsx("div", { className: "min-h-0 flex-1", children: _jsx(PickAgendaView, {}) })] }));
|
|
5
5
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScanControleView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ScanControleView.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,gBAAgB,gCAqV/B"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { supabase } from "../../integrations/supabase/moduleClient";
|
|
4
|
+
import { Button, Badge, Card, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
|
|
5
|
+
import { AlertTriangle, CheckCircle2, RefreshCw, ScanLine } from "lucide-react";
|
|
6
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
const AFGEROND_STATUS = "geprint";
|
|
8
|
+
function statusVariant(status) {
|
|
9
|
+
if (status === AFGEROND_STATUS)
|
|
10
|
+
return "default";
|
|
11
|
+
if (status === "mislukt")
|
|
12
|
+
return "destructive";
|
|
13
|
+
return "secondary";
|
|
14
|
+
}
|
|
15
|
+
function tijd(iso) {
|
|
16
|
+
if (!iso)
|
|
17
|
+
return "—";
|
|
18
|
+
const d = new Date(iso);
|
|
19
|
+
return Number.isNaN(d.getTime())
|
|
20
|
+
? "—"
|
|
21
|
+
: d.toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" });
|
|
22
|
+
}
|
|
23
|
+
// --------------------------------------------------------------------------
|
|
24
|
+
export function ScanControleView() {
|
|
25
|
+
const qc = useQueryClient();
|
|
26
|
+
const [barcode, setBarcode] = useState("");
|
|
27
|
+
const [lookup, setLookup] = useState(null);
|
|
28
|
+
const inputRef = useRef(null);
|
|
29
|
+
const settingsQ = useQuery({
|
|
30
|
+
queryKey: ["logistics-control-settings"],
|
|
31
|
+
queryFn: async () => {
|
|
32
|
+
const { data, error } = await supabase.rpc("logistics_control_settings");
|
|
33
|
+
if (error)
|
|
34
|
+
throw new Error(error.message);
|
|
35
|
+
return (data ?? {});
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const settings = settingsQ.data;
|
|
39
|
+
const scannerId = settings?.scannerId ?? null;
|
|
40
|
+
const printerId = settings?.printerId ?? null;
|
|
41
|
+
const postKlaar = Boolean(scannerId && printerId);
|
|
42
|
+
const overviewQ = useQuery({
|
|
43
|
+
queryKey: ["logistics-control-scan-overview", scannerId],
|
|
44
|
+
enabled: Boolean(scannerId),
|
|
45
|
+
refetchInterval: 15000,
|
|
46
|
+
queryFn: async () => {
|
|
47
|
+
const { data, error } = await supabase.rpc("logistics_control_scan_overview", {
|
|
48
|
+
_scanner_id: scannerId ?? undefined,
|
|
49
|
+
_date: new Date().toISOString().slice(0, 10),
|
|
50
|
+
});
|
|
51
|
+
if (error)
|
|
52
|
+
throw new Error(error.message);
|
|
53
|
+
return (data ?? []);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
// Ververs zodra de Engine een scan bijwerkt.
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const channel = supabase
|
|
59
|
+
.channel("logistics-control-scan")
|
|
60
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "logistics_control_scan" }, () => {
|
|
61
|
+
void qc.invalidateQueries({ queryKey: ["logistics-control-scan-overview"] });
|
|
62
|
+
})
|
|
63
|
+
.subscribe();
|
|
64
|
+
return () => {
|
|
65
|
+
void supabase.removeChannel(channel);
|
|
66
|
+
};
|
|
67
|
+
}, [qc]);
|
|
68
|
+
const lookupM = useMutation({
|
|
69
|
+
mutationFn: async (code) => {
|
|
70
|
+
const { data, error } = await supabase.rpc("logistics_control_scan_lookup", {
|
|
71
|
+
p_barcode: code,
|
|
72
|
+
});
|
|
73
|
+
if (error)
|
|
74
|
+
throw new Error(error.message);
|
|
75
|
+
return (data ?? { ok: false, error: "Geen antwoord van de controlepost." });
|
|
76
|
+
},
|
|
77
|
+
onSuccess: (res) => {
|
|
78
|
+
setLookup(res);
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
toast.error(res.error ?? "Deze barcode is niet herkend.");
|
|
81
|
+
},
|
|
82
|
+
onError: (err) => toast.error(err.message),
|
|
83
|
+
});
|
|
84
|
+
const approveM = useMutation({
|
|
85
|
+
mutationFn: async (vars) => {
|
|
86
|
+
const { data, error } = await supabase.rpc("logistics_control_scan_approve", {
|
|
87
|
+
p_barcode: vars.code,
|
|
88
|
+
p_scanner_id: scannerId ?? undefined,
|
|
89
|
+
p_printer_id: printerId ?? undefined,
|
|
90
|
+
p_allow_reprint: vars.allowReprint,
|
|
91
|
+
});
|
|
92
|
+
if (error)
|
|
93
|
+
throw new Error(error.message);
|
|
94
|
+
return (data ?? {});
|
|
95
|
+
},
|
|
96
|
+
onSuccess: (res) => {
|
|
97
|
+
void qc.invalidateQueries({ queryKey: ["logistics-control-scan-overview"] });
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
setLookup((prev) => (prev ? { ...prev, ...res } : res));
|
|
100
|
+
toast.error(res.error ?? "Goedkeuren is niet gelukt.");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
toast.success(res.isFloriday
|
|
104
|
+
? "Goedgekeurd — de Floriday-afleverbrief wordt opgehaald en geprint."
|
|
105
|
+
: "Goedgekeurd — de afleverbrief staat klaar op de printer.");
|
|
106
|
+
setLookup(null);
|
|
107
|
+
setBarcode("");
|
|
108
|
+
inputRef.current?.focus();
|
|
109
|
+
},
|
|
110
|
+
onError: (err) => toast.error(err.message),
|
|
111
|
+
});
|
|
112
|
+
const retryM = useMutation({
|
|
113
|
+
mutationFn: async (id) => {
|
|
114
|
+
const { data, error } = await supabase.rpc("logistics_control_scan_retry", { _id: id });
|
|
115
|
+
if (error)
|
|
116
|
+
throw new Error(error.message);
|
|
117
|
+
return data;
|
|
118
|
+
},
|
|
119
|
+
onSuccess: (res) => {
|
|
120
|
+
void qc.invalidateQueries({ queryKey: ["logistics-control-scan-overview"] });
|
|
121
|
+
if (res && res.ok === false)
|
|
122
|
+
toast.error(res.error ?? "Opnieuw proberen is niet gelukt.");
|
|
123
|
+
else
|
|
124
|
+
toast.success("Opnieuw aangeboden aan de controlepost.");
|
|
125
|
+
},
|
|
126
|
+
onError: (err) => toast.error(err.message),
|
|
127
|
+
});
|
|
128
|
+
const bezig = lookupM.isPending || approveM.isPending;
|
|
129
|
+
const rows = overviewQ.data ?? [];
|
|
130
|
+
const stats = useMemo(() => ({
|
|
131
|
+
totaal: rows.length,
|
|
132
|
+
geprint: rows.filter((r) => r.status === AFGEROND_STATUS).length,
|
|
133
|
+
wacht: rows.filter((r) => r.status === "afleverbrief_aangevraagd" || r.status === "goedgekeurd").length,
|
|
134
|
+
mislukt: rows.filter((r) => r.status === "mislukt").length,
|
|
135
|
+
}), [rows]);
|
|
136
|
+
function scan(code) {
|
|
137
|
+
const c = code.trim();
|
|
138
|
+
if (!c || !postKlaar)
|
|
139
|
+
return;
|
|
140
|
+
setLookup(null);
|
|
141
|
+
lookupM.mutate(c);
|
|
142
|
+
}
|
|
143
|
+
if (settingsQ.isLoading) {
|
|
144
|
+
return _jsx("div", { className: "text-sm text-muted-foreground", children: "Controlepost laden\u2026" });
|
|
145
|
+
}
|
|
146
|
+
if (!postKlaar) {
|
|
147
|
+
return (_jsx(Card, { className: "p-4 border-destructive/40 bg-destructive/10", children: _jsxs("div", { className: "flex items-start gap-2 text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "text-sm", children: [_jsx("div", { className: "font-medium", children: "Controlepost nog niet ingesteld" }), _jsxs("p", { children: ["Er is nog geen ", scannerId ? "printer" : printerId ? "scanner" : "scanner en printer", " ", "gekozen bij Logistiek Engine \u2192 stap 6 \u201CControle\u201D \u2192 Scancontrole. Zolang dat ontbreekt kan er niet gescand worden."] })] })] }) }));
|
|
148
|
+
}
|
|
149
|
+
const cart = lookup?.cart ?? null;
|
|
150
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("form", { className: "flex gap-2", onSubmit: (e) => {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
scan(barcode);
|
|
153
|
+
}, children: [_jsx(Input, { ref: inputRef, autoFocus: true, value: barcode, onChange: (e) => setBarcode(e.target.value), placeholder: "Scan de barcode op de pakbon\u2026", disabled: bezig }), _jsxs(Button, { type: "submit", disabled: bezig || !barcode.trim(), children: [_jsx(ScanLine, { className: "h-4 w-4 mr-2" }), lookupM.isPending ? "Zoeken…" : "Opzoeken"] })] }), lookup && !lookup.ok && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: lookup.error ?? "Deze barcode is niet herkend." })), lookup?.ok && (_jsxs("div", { className: "rounded-md border p-3 space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-semibold", children: lookup.order?.orderNumber ?? "—" }), _jsx(Badge, { variant: lookup.isFloriday ? "default" : "outline", children: lookup.isFloriday ? "Floriday-order (FO)" : "Eigen order" }), _jsx(Badge, { variant: "secondary", children: cart?.cartLabel ?? "Kar onbekend" }), lookup.alreadyControlled && (_jsxs(Badge, { variant: "destructive", children: ["Al gecontroleerd \u2014 ", lookup.scan?.status] }))] }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [lookup.order?.customerName ?? "—", cart?.cartNumber ? ` · karnummer ${cart.cartNumber}` : "", typeof cart?.occupancyPct === "number" ? ` · ${Math.round(cart.occupancyPct)}% vol` : ""] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Product" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Aantal" })] }) }), _jsx(TableBody, { children: (cart?.items ?? []).length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, className: "text-sm text-muted-foreground", children: "Geen belading gevonden op deze kar." }) })) : ((cart?.items ?? []).map((it, i) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: it.productName ?? "—" }), _jsx(TableCell, { className: "text-right", children: it.crates ?? "—" }), _jsx(TableCell, { className: "text-right", children: it.quantity ?? "—" })] }, it.itemId ?? i)))) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { disabled: approveM.isPending, onClick: () => approveM.mutate({ code: lookup.barcode ?? barcode, allowReprint: false }), children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), approveM.isPending ? "Bezig…" : "Goedkeuren"] }), lookup.alreadyControlled && (_jsx(Button, { variant: "outline", disabled: approveM.isPending, onClick: () => approveM.mutate({ code: lookup.barcode ?? barcode, allowReprint: true }), children: "Toch opnieuw printen" })), _jsx(Button, { variant: "ghost", disabled: approveM.isPending, onClick: () => {
|
|
154
|
+
setLookup(null);
|
|
155
|
+
setBarcode("");
|
|
156
|
+
inputRef.current?.focus();
|
|
157
|
+
}, children: "Annuleren" })] })] }))] }), _jsxs("div", { className: "flex flex-wrap gap-3", children: [_jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Gescand vandaag" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.totaal })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Geprint" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.geprint })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "In behandeling" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.wacht })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Mislukt" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.mislukt })] })] }), _jsx(Card, { className: "p-0 overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Soort" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Melding" }), _jsx(TableHead, { children: "Bijgewerkt" }), _jsx(TableHead, {})] }) }), _jsx(TableBody, { children: overviewQ.isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "text-sm text-muted-foreground", children: "Laden\u2026" }) })) : rows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "text-sm text-muted-foreground", children: "Vandaag nog niets gescand." }) })) : (rows.map((r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: r.orderNumber ?? "—" }), _jsx(TableCell, { children: r.isFloriday ? "FO" : "Eigen" }), _jsx(TableCell, { children: _jsx(Badge, { variant: statusVariant(r.status), children: r.status }) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: r.error ?? "—" }), _jsx(TableCell, { children: tijd(r.updatedAt) }), _jsx(TableCell, { className: "text-right", children: r.status === "mislukt" && (_jsxs(Button, { size: "sm", variant: "outline", disabled: retryM.isPending, onClick: () => retryM.mutate(r.id), children: [_jsx(RefreshCw, { className: "h-4 w-4 mr-2" }), "Opnieuw proberen"] })) })] }, r.id)))) })] }) })] }));
|
|
158
|
+
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogDescription, AlertDialogTitle, toast, } from "@flowselections/core";
|
|
4
4
|
import { Plus, Pencil, Trash2, Truck, ArrowUp, ArrowDown } from "lucide-react";
|
|
5
|
-
import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "
|
|
5
|
+
import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../../components/ui/responsive-dialog";
|
|
6
6
|
import { Link } from "@tanstack/react-router";
|
|
7
7
|
import { useServerFn } from "@tanstack/react-start";
|
|
8
8
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
3
3
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
5
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Dialog, DialogTitle, DialogDescription, Input, Label, Textarea, toast, } from "@flowselections/core";
|
|
6
|
-
import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "
|
|
6
|
+
import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
|
|
7
7
|
import { applyReschedule, listRescheduleHistory, previewReschedule, getPickMoments, setPickDay, } from "../../lib/logisticsEngine/reschedule";
|
|
8
8
|
import { ENGINE_TASKS_KEY } from "../../hooks/useEngineTasks";
|
|
9
9
|
import { listCarriers } from "../../lib/carriers.functions";
|
|
@@ -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;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;
|
|
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;AAgFD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,YAAY,EACZ,UAAU,GACX,EAAE,mBAAmB,+BAwKrB"}
|
|
@@ -14,6 +14,23 @@ function isOrderReady(o) {
|
|
|
14
14
|
const s = (o.status ?? "").toLowerCase();
|
|
15
15
|
return s.includes("ready") || s.includes("verzendklaar") || s.includes("completed") || s.includes("delivered") || s.includes("afgerond");
|
|
16
16
|
}
|
|
17
|
+
/** Volledig afgehandeld: de order hoeft niets meer te doen op deze dag. */
|
|
18
|
+
function isDoneStatus(status) {
|
|
19
|
+
const s = (status ?? "").toLowerCase();
|
|
20
|
+
if (!s)
|
|
21
|
+
return false;
|
|
22
|
+
return [
|
|
23
|
+
"done",
|
|
24
|
+
"completed",
|
|
25
|
+
"afgerond",
|
|
26
|
+
"afgehandeld",
|
|
27
|
+
"shipped",
|
|
28
|
+
"verzonden",
|
|
29
|
+
"delivered",
|
|
30
|
+
"geleverd",
|
|
31
|
+
"afgeleverd",
|
|
32
|
+
].some((k) => s.includes(k));
|
|
33
|
+
}
|
|
17
34
|
function getISOWeek(d) {
|
|
18
35
|
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
19
36
|
const dayNum = date.getUTCDay() || 7;
|
|
@@ -85,6 +102,9 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
85
102
|
planten: row?.plantCount ?? 0,
|
|
86
103
|
verzendklaar: row?.ready ?? 0,
|
|
87
104
|
voorbereiding: row?.inPreparation ?? 0,
|
|
105
|
+
afgehandeld: row
|
|
106
|
+
? Math.max(row.doneCount, row.orders.filter((o) => o.fulfilled || isDoneStatus(o.status)).length)
|
|
107
|
+
: 0,
|
|
88
108
|
};
|
|
89
109
|
}
|
|
90
110
|
const entries = [];
|
|
@@ -116,6 +136,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
116
136
|
planten,
|
|
117
137
|
verzendklaar,
|
|
118
138
|
voorbereiding,
|
|
139
|
+
afgehandeld: entries.filter((e) => isDoneStatus(e.order.status)).length,
|
|
119
140
|
};
|
|
120
141
|
});
|
|
121
142
|
}, [planned, weekStart, mode, pickAgenda.byDate]);
|
|
@@ -136,7 +157,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
136
157
|
d.setHours(0, 0, 0, 0);
|
|
137
158
|
setCursor(startOfWeek(d));
|
|
138
159
|
};
|
|
139
|
-
return (_jsxs("div", { className: "
|
|
160
|
+
return (_jsxs("div", { className: "flex h-full flex-col gap-4 overflow-hidden", 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 shrink-0 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: "min-h-0 flex-1 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())))) })] }));
|
|
140
161
|
}
|
|
141
162
|
function SummaryCard({ label, value, tone, }) {
|
|
142
163
|
const valueClass = tone === "success" ? "text-green-600" : "text-foreground";
|
|
@@ -147,7 +168,9 @@ function DayRow({ day, onOrderClick, onDayClick, capacity, }) {
|
|
|
147
168
|
const today = new Date();
|
|
148
169
|
today.setHours(0, 0, 0, 0);
|
|
149
170
|
const isToday = isSameDay(day.date, today);
|
|
150
|
-
|
|
171
|
+
// Alles van deze dag is afgehandeld: toon een groene bevestiging.
|
|
172
|
+
const dagAfgesloten = day.bestellingen > 0 && day.afgehandeld >= day.bestellingen;
|
|
173
|
+
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: [dagAfgesloten && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-green-600/30 bg-green-500/15 px-3 py-1.5 text-sm font-medium text-green-700", children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), "Dag is afgesloten"] })), 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" })] }));
|
|
151
174
|
}
|
|
152
175
|
function Stat({ icon, bg, value, label, }) {
|
|
153
176
|
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) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCartIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useCartIndex.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCartIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useCartIndex.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;6BAqBrB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,MAAM,GAAG,IAAI;;;EA4B1F"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
|
+
import { useKnownOrders } from "./useKnownOrders";
|
|
4
5
|
/**
|
|
5
6
|
* Gedeelde karindex van de Engine. Dit is de enige bron voor karrentellingen:
|
|
6
7
|
* taak-payloads (cartCount) en eigen tellingen op order_carts zijn niet meer
|
|
@@ -23,6 +24,7 @@ export function useCartIndex(from, to) {
|
|
|
23
24
|
return Array.isArray(data) ? data : [];
|
|
24
25
|
},
|
|
25
26
|
});
|
|
27
|
+
const { isKnownRef } = useKnownOrders();
|
|
26
28
|
const rows = query.data;
|
|
27
29
|
// Per orderreferentie de unieke karren tellen.
|
|
28
30
|
const cartsForOrder = useCallback((...refs) => {
|
|
@@ -31,6 +33,9 @@ export function useCartIndex(from, to) {
|
|
|
31
33
|
const cart = r.cart_id ?? "";
|
|
32
34
|
if (!cart)
|
|
33
35
|
continue;
|
|
36
|
+
// Karren van orders die de Verkoop-module niet kent tellen niet mee.
|
|
37
|
+
if (!isKnownRef(r.order_number, r.order_uuid))
|
|
38
|
+
continue;
|
|
34
39
|
for (const ref of [r.order_number, r.order_uuid]) {
|
|
35
40
|
if (!ref)
|
|
36
41
|
continue;
|
|
@@ -47,7 +52,7 @@ export function useCartIndex(from, to) {
|
|
|
47
52
|
return set.size;
|
|
48
53
|
}
|
|
49
54
|
return null;
|
|
50
|
-
}, [rows]);
|
|
55
|
+
}, [rows, isKnownRef]);
|
|
51
56
|
return {
|
|
52
57
|
cartsForOrder,
|
|
53
58
|
isLoading: query.isLoading,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Luistert naar `public.logistics_events` en ververst de agenda's, de
|
|
3
|
+
* karrencontrole en alle karrentellingen zodra de Engine meldt dat een buffer
|
|
4
|
+
* is gesloten of dat een order is losgekoppeld.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useEngineBufferEvents(): void;
|
|
7
|
+
//# sourceMappingURL=useEngineBufferEvents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEngineBufferEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineBufferEvents.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAsB5C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
|
+
/** Seintjes van de Engine die de indeling van buffers en karren wijzigen. */
|
|
5
|
+
const BUFFER_EVENTS = new Set(["buffer.closed", "buffer.order_detached"]);
|
|
6
|
+
/**
|
|
7
|
+
* Querykeys die volledig opnieuw bij de Engine opgehaald moeten worden zodra
|
|
8
|
+
* een buffer sluit of een order wordt losgekoppeld. Alles komt van de Engine
|
|
9
|
+
* (logistics_pick_agenda, logistics_delivery_agenda, control_get_todays_carts,
|
|
10
|
+
* logistics_cart_units); deze module telt of cachet niets zelf.
|
|
11
|
+
*/
|
|
12
|
+
const KEYS = [
|
|
13
|
+
["logistics-pick-agenda"],
|
|
14
|
+
["logistics-delivery-agenda"],
|
|
15
|
+
["logistics-cart-index"],
|
|
16
|
+
["control"],
|
|
17
|
+
["order-buffers"],
|
|
18
|
+
["order-pick-dates"],
|
|
19
|
+
["engine"],
|
|
20
|
+
["orders"],
|
|
21
|
+
["known-orders"],
|
|
22
|
+
["floriday"],
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Luistert naar `public.logistics_events` en ververst de agenda's, de
|
|
26
|
+
* karrencontrole en alle karrentellingen zodra de Engine meldt dat een buffer
|
|
27
|
+
* is gesloten of dat een order is losgekoppeld.
|
|
28
|
+
*/
|
|
29
|
+
export function useEngineBufferEvents() {
|
|
30
|
+
const qc = useQueryClient();
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const channel = supabase
|
|
33
|
+
.channel(`engine-buffer-events-${Math.random().toString(36).slice(2)}`)
|
|
34
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "logistics_events" }, (payload) => {
|
|
35
|
+
const row = (payload.new ?? {});
|
|
36
|
+
if (!row.event_type || !BUFFER_EVENTS.has(row.event_type))
|
|
37
|
+
return;
|
|
38
|
+
for (const key of KEYS)
|
|
39
|
+
qc.invalidateQueries({ queryKey: key });
|
|
40
|
+
void qc.refetchQueries({ type: "active" });
|
|
41
|
+
})
|
|
42
|
+
.subscribe();
|
|
43
|
+
return () => {
|
|
44
|
+
supabase.removeChannel(channel);
|
|
45
|
+
};
|
|
46
|
+
}, [qc]);
|
|
47
|
+
}
|
|
@@ -25,7 +25,7 @@ export function useEngineTasks(options) {
|
|
|
25
25
|
const timerRef = useRef(null);
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
const channel = supabase
|
|
28
|
-
.channel(
|
|
28
|
+
.channel(`engine-tasks-${Math.random().toString(36).slice(2)}`)
|
|
29
29
|
.on("postgres_changes", { event: "*", schema: "public", table: "logistics_tasks" }, () => {
|
|
30
30
|
if (timerRef.current)
|
|
31
31
|
return;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alle orderreferenties die de Verkoop-module kent. Alles wat hier niet in
|
|
3
|
+
* staat, hoort in deze module niet zichtbaar te zijn.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useKnownOrders(): {
|
|
6
|
+
refs: Set<string>;
|
|
7
|
+
isKnownRef: (...candidates: (string | null | undefined)[]) => boolean;
|
|
8
|
+
isFulfilledRef: (...candidates: (string | null | undefined)[]) => boolean;
|
|
9
|
+
isReady: boolean;
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=useKnownOrders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useKnownOrders.d.ts","sourceRoot":"","sources":["../../src/hooks/useKnownOrders.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,cAAc;;gCAyCV,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,OAAO;oCAavC,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,OAAO;;;EAW1D"}
|