@flowselections/floriday-afhandeling-module 1.0.44 → 1.0.45
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/ControlePage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControlePage.js +92 -54
- package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
- package/dist-lib/hooks/useCartIndex.js +16 -7
- package/dist-lib/hooks/useEngineBufferEvents.d.ts +5 -0
- package/dist-lib/hooks/useEngineBufferEvents.d.ts.map +1 -1
- package/dist-lib/hooks/useEngineBufferEvents.js +18 -13
- package/dist-lib/hooks/useEngineTasks.d.ts.map +1 -1
- package/dist-lib/hooks/useEngineTasks.js +8 -18
- package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -1
- package/dist-lib/hooks/useKnownOrders.js +2 -1
- package/dist-lib/hooks/useOrderPickDates.d.ts +1 -1
- package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPickDates.js +5 -3
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
- package/dist-lib/hooks/usePickAgenda.js +17 -13
- package/dist-lib/hooks/usePickMode.d.ts +8 -2
- package/dist-lib/hooks/usePickMode.d.ts.map +1 -1
- package/dist-lib/hooks/usePickMode.js +17 -9
- package/dist-lib/index.d.ts +1 -2
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -33
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +3053 -161
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +3053 -161
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +2982 -88
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +3063 -143
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +5798 -14
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/carriers.functions.d.ts +14510 -40
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +14495 -35
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +31999 -104
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +389 -259
- package/dist-lib/lib/deliveryNote.functions.d.ts +3053 -161
- package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -1
- package/dist-lib/lib/engineLayouts.functions.d.ts +3053 -161
- package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.d.ts +5798 -14
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/orderFulfilment.functions.d.ts +3053 -161
- package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -1
- package/dist-lib/lib/pickDates.functions.d.ts +2985 -88
- package/dist-lib/lib/pickDates.functions.d.ts.map +1 -1
- package/dist-lib/lib/pickDates.functions.js +30 -3
- package/dist-lib/lib/pickMode.d.ts +20 -2
- package/dist-lib/lib/pickMode.d.ts.map +1 -1
- package/dist-lib/lib/pickMode.js +65 -16
- package/dist-lib/lib/realtimeBus.d.ts +20 -0
- package/dist-lib/lib/realtimeBus.d.ts.map +1 -0
- package/dist-lib/lib/realtimeBus.js +56 -0
- package/dist-lib/lib/serverFnError.d.ts +12 -0
- package/dist-lib/lib/serverFnError.d.ts.map +1 -1
- package/dist-lib/lib/serverFnError.js +48 -1
- package/dist-lib/moduleDefinition.d.ts +3 -0
- package/dist-lib/moduleDefinition.d.ts.map +1 -0
- package/dist-lib/moduleDefinition.js +36 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AAkJA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+
|
|
1
|
+
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AAkJA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAo+BpE"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { supabase } from "../../integrations/supabase/moduleClient";
|
|
4
3
|
import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
5
4
|
import { Printer, CheckCircle2, AlertTriangle, History, ChevronLeft, ChevronRight, ScanLine } from "lucide-react";
|
|
6
5
|
import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
|
|
@@ -10,6 +9,7 @@ import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSess
|
|
|
10
9
|
import { ScanControleView } from "./ScanControleView";
|
|
11
10
|
import { renderControleLijstHtml } from "./ControleLijstPrint";
|
|
12
11
|
import { printHtmlDocument, printFoutMelding } from "../../lib/printing";
|
|
12
|
+
import { callServerFn } from "../../lib/serverFnError";
|
|
13
13
|
function dayDiffFromToday(iso) {
|
|
14
14
|
const d = new Date(iso);
|
|
15
15
|
if (Number.isNaN(d.getTime()))
|
|
@@ -101,10 +101,15 @@ export function ControlePage({ onExit } = {}) {
|
|
|
101
101
|
const saveDraft = useServerFn(saveControlDraft);
|
|
102
102
|
const cartsQ = useQuery({
|
|
103
103
|
queryKey: ["control", "today"],
|
|
104
|
-
queryFn: () => fetchToday(),
|
|
104
|
+
queryFn: () => callServerFn(() => fetchToday(), "getTodaysControlCarts", { timeoutMs: 8000, maxAttempts: 1 }),
|
|
105
|
+
// Zelfde houdbaarheid als het verversritme: bij elke terugkomst op het
|
|
106
|
+
// tabblad opnieuw laden gaf onnodig zware opvragingen.
|
|
107
|
+
staleTime: 30000,
|
|
105
108
|
refetchInterval: 60000,
|
|
106
109
|
refetchIntervalInBackground: false,
|
|
107
|
-
refetchOnWindowFocus:
|
|
110
|
+
refetchOnWindowFocus: false,
|
|
111
|
+
placeholderData: (prev) => prev,
|
|
112
|
+
retry: false,
|
|
108
113
|
});
|
|
109
114
|
const histQ = useQuery({
|
|
110
115
|
queryKey: ["control", "history"],
|
|
@@ -123,7 +128,7 @@ export function ControlePage({ onExit } = {}) {
|
|
|
123
128
|
const draftQ = useQuery({
|
|
124
129
|
queryKey: controlDraftQueryKey,
|
|
125
130
|
queryFn: async () => (await fetchDraft()),
|
|
126
|
-
staleTime:
|
|
131
|
+
staleTime: 30000,
|
|
127
132
|
refetchOnMount: "always",
|
|
128
133
|
refetchOnWindowFocus: true,
|
|
129
134
|
});
|
|
@@ -228,6 +233,7 @@ export function ControlePage({ onExit } = {}) {
|
|
|
228
233
|
const [savingState, setSavingState] = useState("idle");
|
|
229
234
|
const autosaveTimer = useRef(null);
|
|
230
235
|
const controlFinalizedRef = useRef(false);
|
|
236
|
+
const requestKeyRef = useRef(null);
|
|
231
237
|
useEffect(() => {
|
|
232
238
|
if (controlFinalizedRef.current)
|
|
233
239
|
return;
|
|
@@ -305,9 +311,19 @@ export function ControlePage({ onExit } = {}) {
|
|
|
305
311
|
},
|
|
306
312
|
}));
|
|
307
313
|
};
|
|
314
|
+
// Vinkt alleen de karren aan/uit die op de huidige pagina staan.
|
|
308
315
|
const toggleAll = (on) => {
|
|
309
|
-
const keys =
|
|
310
|
-
setChecked(
|
|
316
|
+
const keys = pagedCarts.map(controlCartKey);
|
|
317
|
+
setChecked((prev) => {
|
|
318
|
+
const next = new Set(prev);
|
|
319
|
+
for (const key of keys) {
|
|
320
|
+
if (on)
|
|
321
|
+
next.add(key);
|
|
322
|
+
else
|
|
323
|
+
next.delete(key);
|
|
324
|
+
}
|
|
325
|
+
return next;
|
|
326
|
+
});
|
|
311
327
|
const nowIso = new Date().toISOString();
|
|
312
328
|
setCartStates((prev) => {
|
|
313
329
|
const next = { ...prev };
|
|
@@ -325,13 +341,18 @@ export function ControlePage({ onExit } = {}) {
|
|
|
325
341
|
mutationFn: (input) => {
|
|
326
342
|
const visibleKeys = new Set(carts.map(controlCartKey));
|
|
327
343
|
const checkedCartKeys = [...checked].filter((k) => visibleKeys.has(k));
|
|
328
|
-
|
|
344
|
+
// Eén aanvraag-kenmerk per controlesessie: opnieuw indienen na een
|
|
345
|
+
// onderbroken verbinding levert nooit een tweede sessie op.
|
|
346
|
+
if (!requestKeyRef.current)
|
|
347
|
+
requestKeyRef.current = crypto.randomUUID();
|
|
348
|
+
return callServerFn(() => submit({
|
|
329
349
|
data: {
|
|
330
350
|
checkedCartKeys,
|
|
331
351
|
confirmedRescheduleCartKeys: input?.confirmedRescheduleCartKeys ?? [],
|
|
332
352
|
notes,
|
|
353
|
+
requestKey: requestKeyRef.current ?? undefined,
|
|
333
354
|
},
|
|
334
|
-
});
|
|
355
|
+
}), "submitControlSession", { timeoutMs: 120000, maxAttempts: 1 });
|
|
335
356
|
},
|
|
336
357
|
onMutate: () => {
|
|
337
358
|
controlFinalizedRef.current = true;
|
|
@@ -340,9 +361,23 @@ export function ControlePage({ onExit } = {}) {
|
|
|
340
361
|
},
|
|
341
362
|
onSuccess: (r) => {
|
|
342
363
|
setResult(r);
|
|
364
|
+
requestKeyRef.current = null;
|
|
365
|
+
const stillOpenCount = r.stillOpen?.length ?? 0;
|
|
343
366
|
toast.success(`Controle opgeslagen — ${r.checkedCount} klaar, ${r.rescheduledCount} doorgeschoven, ${r.blockedCount} geblokkeerd.`);
|
|
344
|
-
|
|
345
|
-
|
|
367
|
+
if (stillOpenCount > 0) {
|
|
368
|
+
const labels = r.stillOpen
|
|
369
|
+
.map((c) => c.cartNumber ?? c.orderNumber ?? "onbekend")
|
|
370
|
+
.join(", ");
|
|
371
|
+
toast.warning(`${stillOpenCount} kar(ren) zijn nog niet afgemeld (${labels}). Ze blijven in de lijst staan; probeer die opnieuw.`);
|
|
372
|
+
}
|
|
373
|
+
qc.invalidateQueries({ queryKey: ["control", "today"] });
|
|
374
|
+
qc.invalidateQueries({ queryKey: ["control", "delivered"] });
|
|
375
|
+
qc.invalidateQueries({ queryKey: ["control", "history"] });
|
|
376
|
+
qc.invalidateQueries({ queryKey: ["floriday"] });
|
|
377
|
+
qc.invalidateQueries({ queryKey: ["orders"] });
|
|
378
|
+
qc.invalidateQueries({ queryKey: ["logistics-cart-index"] });
|
|
379
|
+
qc.invalidateQueries({ queryKey: ["logistics-pick-agenda"] });
|
|
380
|
+
qc.invalidateQueries({ queryKey: ["logistics-delivery-agenda"] });
|
|
346
381
|
qc.invalidateQueries({ queryKey: ["cart-overrides"] });
|
|
347
382
|
setChecked(new Set());
|
|
348
383
|
setCartStates({});
|
|
@@ -359,13 +394,44 @@ export function ControlePage({ onExit } = {}) {
|
|
|
359
394
|
qc.setQueryData(controlDraftQueryKey, buildDraftDto(emptyDraftPayload, {}, null));
|
|
360
395
|
qc.invalidateQueries({ queryKey: controlDraftQueryKey });
|
|
361
396
|
},
|
|
362
|
-
onError: (e) => {
|
|
397
|
+
onError: async (e) => {
|
|
398
|
+
const message = e instanceof Error ? e.message : "Opslaan mislukt";
|
|
399
|
+
// Een time-out betekent alleen dat wij niet langer wachten; de database
|
|
400
|
+
// kan het afhandelen wél hebben afgerond. Eerst de werkelijke stand
|
|
401
|
+
// ophalen voordat we een medewerker een foutmelding voorschotelen.
|
|
402
|
+
if (/time-?out/i.test(message)) {
|
|
403
|
+
const submittedKeys = new Set(checked);
|
|
404
|
+
const fresh = await qc
|
|
405
|
+
.fetchQuery({
|
|
406
|
+
queryKey: ["control", "today"],
|
|
407
|
+
queryFn: () => callServerFn(() => fetchToday(), "getTodaysControlCarts", {
|
|
408
|
+
timeoutMs: 15000,
|
|
409
|
+
maxAttempts: 1,
|
|
410
|
+
}),
|
|
411
|
+
staleTime: 0,
|
|
412
|
+
})
|
|
413
|
+
.catch(() => null);
|
|
414
|
+
if (fresh) {
|
|
415
|
+
const openAgain = fresh.carts.filter((c) => submittedKeys.has(controlCartKey(c))).length;
|
|
416
|
+
qc.invalidateQueries({ queryKey: ["control", "delivered"] });
|
|
417
|
+
qc.invalidateQueries({ queryKey: ["control", "history"] });
|
|
418
|
+
if (openAgain === 0) {
|
|
419
|
+
toast.success("Het afhandelen duurde langer dan verwacht, maar is afgerond: deze karren staan niet meer in de controlelijst.");
|
|
420
|
+
setChecked(new Set());
|
|
421
|
+
setCartStates({});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
controlFinalizedRef.current = false;
|
|
425
|
+
toast.warning(`Het afhandelen duurde te lang. ${openAgain} van de ${submittedKeys.size} karren staan nog in de lijst; probeer die opnieuw.`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
363
429
|
controlFinalizedRef.current = false;
|
|
364
|
-
toast.error(
|
|
430
|
+
toast.error(message);
|
|
365
431
|
},
|
|
366
432
|
});
|
|
367
433
|
const proposalM = useMutation({
|
|
368
|
-
mutationFn: () => fetchProposals({ data: { checkedCartKeys: [...checked] } }),
|
|
434
|
+
mutationFn: () => callServerFn(() => fetchProposals({ data: { checkedCartKeys: [...checked] } }), "getControlRescheduleProposals", { timeoutMs: 8000, maxAttempts: 1 }),
|
|
369
435
|
onSuccess: (r) => {
|
|
370
436
|
if (r.proposals.length > 0 || r.blocked.length > 0) {
|
|
371
437
|
setPendingProposal(r);
|
|
@@ -447,42 +513,7 @@ export function ControlePage({ onExit } = {}) {
|
|
|
447
513
|
const pendingNotReadyCount = pendingProposals.length + pendingBlocked.length;
|
|
448
514
|
const criticalTomorrowCount = pendingProposals.filter((p) => isTomorrowIso(p.pickupDate)).length +
|
|
449
515
|
pendingBlocked.filter((b) => isTomorrowIso(b.pickupDate)).length;
|
|
450
|
-
|
|
451
|
-
const invalidateTimer = useRef(null);
|
|
452
|
-
useEffect(() => {
|
|
453
|
-
const scheduleInvalidate = () => {
|
|
454
|
-
if (invalidateTimer.current)
|
|
455
|
-
clearTimeout(invalidateTimer.current);
|
|
456
|
-
invalidateTimer.current = setTimeout(() => {
|
|
457
|
-
qc.invalidateQueries({ queryKey: ["control"] });
|
|
458
|
-
}, 300);
|
|
459
|
-
};
|
|
460
|
-
const tables = [
|
|
461
|
-
"cart_numbers",
|
|
462
|
-
"cart_control_sessions",
|
|
463
|
-
"cart_control_items",
|
|
464
|
-
"cart_pick_overrides",
|
|
465
|
-
"orders",
|
|
466
|
-
"floriday_salesorders_cache",
|
|
467
|
-
"dashboard_critical_alerts",
|
|
468
|
-
"logistics_tasks",
|
|
469
|
-
"pick_queue",
|
|
470
|
-
"print_jobs",
|
|
471
|
-
"order_carts",
|
|
472
|
-
"order_cart_items",
|
|
473
|
-
];
|
|
474
|
-
const channel = supabase.channel("controle-page-realtime");
|
|
475
|
-
for (const table of tables) {
|
|
476
|
-
channel.on("postgres_changes", { event: "*", schema: "public", table }, scheduleInvalidate);
|
|
477
|
-
}
|
|
478
|
-
channel.subscribe();
|
|
479
|
-
return () => {
|
|
480
|
-
if (invalidateTimer.current)
|
|
481
|
-
clearTimeout(invalidateTimer.current);
|
|
482
|
-
supabase.removeChannel(channel);
|
|
483
|
-
};
|
|
484
|
-
}, [qc]);
|
|
485
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Controle karren" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Dagelijkse eindcontrole \u2014 vandaag ", new Date().toLocaleDateString("nl-NL")] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", disabled: printBezig || carts.length === 0, onClick: async () => {
|
|
516
|
+
return (_jsxs("main", { className: "h-full overflow-y-auto p-6 space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Controle karren" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Dagelijkse eindcontrole \u2014 vandaag ", new Date().toLocaleDateString("nl-NL")] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", disabled: printBezig || carts.length === 0, onClick: async () => {
|
|
486
517
|
setPrintBezig(true);
|
|
487
518
|
try {
|
|
488
519
|
await printHtmlDocument({
|
|
@@ -499,14 +530,21 @@ export function ControlePage({ onExit } = {}) {
|
|
|
499
530
|
finally {
|
|
500
531
|
setPrintBezig(false);
|
|
501
532
|
}
|
|
502
|
-
}, children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), printBezig ? "Bezig met printen…" : "Controlelijst afdrukken"] }), _jsxs(Button, { onClick: runControl, disabled: submitM.isPending || proposalM.isPending || carts.length === 0 || tab !== "controle", children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), proposalM.isPending ? "Controleren…" : "Controle uitvoeren"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: tab === "controle" ? "default" : "outline", onClick: () => setTab("controle"), children: "Controle" }), _jsxs(Button, { variant: tab === "scan" ? "default" : "outline", onClick: () => setTab("scan"), children: [_jsx(ScanLine, { className: "h-4 w-4 mr-2" }), "Scancontrole"] }), _jsxs(Button, { variant: tab === "afgeleverd" ? "default" : "outline", onClick: () => setTab("afgeleverd"), children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), "Afgeleverd"] }), _jsxs(Button, { variant: tab === "historie" ? "default" : "outline", onClick: () => setTab("historie"), children: [_jsx(History, { className: "h-4 w-4 mr-2" }), "Historie"] })] }), tab === "controle" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-3 items-center", children: [_jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Totaal karren vandaag" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.total })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Geprint" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.printed })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Aangevinkt" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.checked })] })] }),
|
|
533
|
+
}, children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), printBezig ? "Bezig met printen…" : "Controlelijst afdrukken"] }), _jsxs(Button, { onClick: runControl, disabled: submitM.isPending || proposalM.isPending || carts.length === 0 || tab !== "controle", children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), proposalM.isPending ? "Controleren…" : "Controle uitvoeren"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: tab === "controle" ? "default" : "outline", onClick: () => setTab("controle"), children: "Controle" }), _jsxs(Button, { variant: tab === "scan" ? "default" : "outline", onClick: () => setTab("scan"), children: [_jsx(ScanLine, { className: "h-4 w-4 mr-2" }), "Scancontrole"] }), _jsxs(Button, { variant: tab === "afgeleverd" ? "default" : "outline", onClick: () => setTab("afgeleverd"), children: [_jsx(CheckCircle2, { className: "h-4 w-4 mr-2" }), "Afgeleverd"] }), _jsxs(Button, { variant: tab === "historie" ? "default" : "outline", onClick: () => setTab("historie"), children: [_jsx(History, { className: "h-4 w-4 mr-2" }), "Historie"] })] }), tab === "controle" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-3 items-center", children: [_jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Totaal karren vandaag" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.total })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Geprint" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.printed })] }), _jsxs(Card, { className: "p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Aangevinkt" }), _jsx("div", { className: "text-2xl font-semibold", children: stats.checked })] })] }), cartsQ.isError && (_jsx(Card, { className: "border-destructive/40 bg-destructive/10 p-4", 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: "space-y-2 text-sm", children: [_jsx("div", { className: "font-medium", children: "De karren konden niet worden opgehaald" }), _jsx("p", { children: cartsQ.error instanceof Error
|
|
534
|
+
? cartsQ.error.message
|
|
535
|
+
: "Onbekende fout bij het ophalen van de karren." }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void cartsQ.refetch(), children: "Opnieuw proberen" })] })] }) })), !cartsQ.isError && cartsQ.data && (cartsQ.data.warnings ?? []).length > 0 && (_jsx(Card, { className: "border-amber-500/40 bg-amber-500/10 p-4", children: _jsxs("div", { className: "flex items-start gap-2 text-amber-700", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "space-y-1 text-sm", children: [_jsx("div", { className: "font-medium", children: "Niet alle gegevens konden worden opgehaald" }), (cartsQ.data.warnings ?? []).map((w) => (_jsx("p", { children: w }, w)))] })] }) })), _jsxs(Card, { className: "p-0 overflow-hidden", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: _jsx(Checkbox, { checked: pagedCarts.length > 0 &&
|
|
536
|
+
pagedCarts.every((c) => checked.has(controlCartKey(c)))
|
|
503
537
|
? true
|
|
504
|
-
:
|
|
538
|
+
: pagedCarts.some((c) => checked.has(controlCartKey(c)))
|
|
505
539
|
? "indeterminate"
|
|
506
|
-
: false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Karnummer" }), _jsx(TableHead, { children: "Uitgeprint" })] }) }), _jsxs(TableBody, { children: [cartsQ.isLoading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan:
|
|
540
|
+
: false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Karnummer" }), _jsx(TableHead, { children: "Oorspronkelijk gepland" }), _jsx(TableHead, { children: "Uitgeprint" })] }) }), _jsxs(TableBody, { children: [cartsQ.isLoading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })), !cartsQ.isLoading && !cartsQ.isError && carts.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-center text-muted-foreground py-8", children: (cartsQ.data?.rpcCount ?? 0) > 0
|
|
541
|
+
? `De planning gaf ${cartsQ.data?.rpcCount} karren voor vandaag, maar geen daarvan hoort nog bij een openstaande bestelling in deze module.`
|
|
542
|
+
: "De planning heeft voor vandaag geen karren om te controleren." }) })), pagedCarts.map((c) => {
|
|
507
543
|
const k = controlCartKey(c);
|
|
508
|
-
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsxs(TableCell, { className: "font-mono font-semibold", children: [c.orderNumber || "—", _jsxs("span", { className: "ml-2 text-muted-foreground font-normal", children: ["[", c.cartSeq, " - ", c.cartTotal ?? c.cartSeq, "]"] })] }), _jsx(TableCell, { className: "font-mono font-semibold", children: c.cartNumber ?? "—" }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
|
|
509
|
-
})] })] }), carts.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [carts.length, " resultaten"
|
|
544
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsxs(TableCell, { className: "font-mono font-semibold", children: [c.orderNumber || "—", _jsxs("span", { className: "ml-2 text-muted-foreground font-normal", children: ["[", c.cartSeq, " - ", c.cartTotal ?? c.cartSeq, "]"] })] }), _jsx(TableCell, { className: "font-mono font-semibold", children: c.cartNumber ?? "—" }), _jsxs(TableCell, { className: "whitespace-nowrap", children: [c.originalPickDate ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { children: c.originalPickDate }), c.originalPickDate < c.pickDate ? (_jsx(Badge, { variant: "destructive", children: "Achterstand" })) : null] })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })), c.pickupTime ? (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Ophalen ", c.pickupTime] })) : null] }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
|
|
545
|
+
})] })] }), carts.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [carts.length, " resultaten", typeof cartsQ.data?.rpcCount === "number" && (_jsxs(_Fragment, { children: [" · planning gaf ", cartsQ.data.rpcCount, (cartsQ.data.filteredOut ?? 0) > 0
|
|
546
|
+
? `, ${cartsQ.data.filteredOut} niet gekoppeld aan een bestelling`
|
|
547
|
+
: ""] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: "Resultaten per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => {
|
|
510
548
|
setPageSize(Number(v));
|
|
511
549
|
setPage(1);
|
|
512
550
|
}, children: [_jsx(SelectTrigger, { className: "h-8 w-[80px]", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: [10, 20, 50, 100].map((n) => (_jsx(SelectItem, { value: String(n), children: n }, n))) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Vorige pagina", disabled: currentPage <= 1, onClick: () => setPage((p) => Math.max(1, p - 1)), children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsxs("span", { className: "tabular-nums", children: [currentPage, " / ", pageCount] }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Volgende pagina", disabled: currentPage >= pageCount, onClick: () => setPage((p) => Math.min(pageCount, p + 1)), children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] })] }))] }), _jsxs("div", { className: "max-w-xl", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { className: "text-sm font-medium", children: "Opmerkingen bij deze controle" }), _jsx("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: savingState === "saving"
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;6BA2CrB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,MAAM,GAAG,IAAI;;;EAc1F"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
4
|
import { useKnownOrders } from "./useKnownOrders";
|
|
@@ -26,8 +26,10 @@ export function useCartIndex(from, to) {
|
|
|
26
26
|
});
|
|
27
27
|
const { isKnownRef } = useKnownOrders();
|
|
28
28
|
const rows = query.data;
|
|
29
|
-
//
|
|
30
|
-
|
|
29
|
+
// Eén keer tellen zodra de gegevens binnenkomen. Deze telling stond eerder in
|
|
30
|
+
// `cartsForOrder` zelf en werd dus per orderregel opnieuw uitgevoerd — bij
|
|
31
|
+
// honderden orders liep de browser daarop vast.
|
|
32
|
+
const countsByRef = useMemo(() => {
|
|
31
33
|
const perOrder = new Map();
|
|
32
34
|
for (const r of rows ?? []) {
|
|
33
35
|
const cart = r.cart_id ?? "";
|
|
@@ -44,15 +46,22 @@ export function useCartIndex(from, to) {
|
|
|
44
46
|
perOrder.set(ref, set);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
for (const [ref, set] of perOrder)
|
|
51
|
+
counts.set(ref, set.size);
|
|
52
|
+
return counts;
|
|
53
|
+
}, [rows, isKnownRef]);
|
|
54
|
+
/** Alleen opzoeken, niet meer rekenen. */
|
|
55
|
+
const cartsForOrder = useCallback((...refs) => {
|
|
47
56
|
for (const ref of refs) {
|
|
48
57
|
if (!ref)
|
|
49
58
|
continue;
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
return
|
|
59
|
+
const count = countsByRef.get(ref);
|
|
60
|
+
if (count !== undefined)
|
|
61
|
+
return count;
|
|
53
62
|
}
|
|
54
63
|
return null;
|
|
55
|
-
}, [
|
|
64
|
+
}, [countsByRef]);
|
|
56
65
|
return {
|
|
57
66
|
cartsForOrder,
|
|
58
67
|
isLoading: query.isLoading,
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Luistert naar `public.logistics_events` en ververst de agenda's, de
|
|
3
3
|
* karrencontrole en alle karrentellingen zodra de Engine meldt dat een buffer
|
|
4
4
|
* is gesloten of dat een order is losgekoppeld.
|
|
5
|
+
*
|
|
6
|
+
* Seintjes worden samengevoegd en we invalideren alleen: React Query haalt de
|
|
7
|
+
* schermen die daadwerkelijk in beeld staan zelf opnieuw op. Een directe
|
|
8
|
+
* `refetchQueries({type:"active"})` per seintje veroorzaakte een stortvloed aan
|
|
9
|
+
* gelijktijdige opvragingen en liet het scherm blokkeren.
|
|
5
10
|
*/
|
|
6
11
|
export declare function useEngineBufferEvents(): void;
|
|
7
12
|
//# sourceMappingURL=useEngineBufferEvents.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEngineBufferEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineBufferEvents.ts"],"names":[],"mappings":"AA0BA
|
|
1
|
+
{"version":3,"file":"useEngineBufferEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineBufferEvents.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAmB5C"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import { useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import {
|
|
3
|
+
import { debounce, subscribeTable } from "../lib/realtimeBus";
|
|
4
4
|
/** Seintjes van de Engine die de indeling van buffers en karren wijzigen. */
|
|
5
5
|
const BUFFER_EVENTS = new Set(["buffer.closed", "buffer.order_detached"]);
|
|
6
6
|
/**
|
|
7
|
-
* Querykeys die
|
|
8
|
-
*
|
|
7
|
+
* Querykeys die opnieuw bij de Engine opgehaald moeten worden zodra een buffer
|
|
8
|
+
* sluit of een order wordt losgekoppeld. Alles komt van de Engine
|
|
9
9
|
* (logistics_pick_agenda, logistics_delivery_agenda, control_get_todays_carts,
|
|
10
10
|
* logistics_cart_units); deze module telt of cachet niets zelf.
|
|
11
11
|
*/
|
|
@@ -25,23 +25,28 @@ const KEYS = [
|
|
|
25
25
|
* Luistert naar `public.logistics_events` en ververst de agenda's, de
|
|
26
26
|
* karrencontrole en alle karrentellingen zodra de Engine meldt dat een buffer
|
|
27
27
|
* is gesloten of dat een order is losgekoppeld.
|
|
28
|
+
*
|
|
29
|
+
* Seintjes worden samengevoegd en we invalideren alleen: React Query haalt de
|
|
30
|
+
* schermen die daadwerkelijk in beeld staan zelf opnieuw op. Een directe
|
|
31
|
+
* `refetchQueries({type:"active"})` per seintje veroorzaakte een stortvloed aan
|
|
32
|
+
* gelijktijdige opvragingen en liet het scherm blokkeren.
|
|
28
33
|
*/
|
|
29
34
|
export function useEngineBufferEvents() {
|
|
30
35
|
const qc = useQueryClient();
|
|
31
36
|
useEffect(() => {
|
|
32
|
-
const
|
|
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;
|
|
37
|
+
const refresh = debounce(() => {
|
|
38
38
|
for (const key of KEYS)
|
|
39
39
|
qc.invalidateQueries({ queryKey: key });
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
40
|
+
}, 1000);
|
|
41
|
+
const off = subscribeTable("logistics_events", (row) => {
|
|
42
|
+
const eventType = row.event_type;
|
|
43
|
+
if (typeof eventType !== "string" || !BUFFER_EVENTS.has(eventType))
|
|
44
|
+
return;
|
|
45
|
+
refresh();
|
|
46
|
+
});
|
|
43
47
|
return () => {
|
|
44
|
-
|
|
48
|
+
refresh.cancel();
|
|
49
|
+
off();
|
|
45
50
|
};
|
|
46
51
|
}, [qc]);
|
|
47
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEngineTasks.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineTasks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useEngineTasks.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineTasks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAGjF,eAAO,MAAM,gBAAgB,UAAsB,CAAC;AAIpD,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE;IACvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;;;;;EAuCA;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAShF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useEffect, useMemo
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
3
|
import { listEngineTasks } from "../lib/logisticsEngine/engine";
|
|
4
|
+
import { debounce, subscribeTable } from "../lib/realtimeBus";
|
|
5
5
|
export const ENGINE_TASKS_KEY = ["engine", "tasks"];
|
|
6
6
|
const OPEN_STATUSES = ["planned", "ready", "in_progress"];
|
|
7
7
|
export function useEngineTasks(options) {
|
|
@@ -22,24 +22,14 @@ export function useEngineTasks(options) {
|
|
|
22
22
|
});
|
|
23
23
|
// Realtime-updates komen in bursts binnen als de Engine een batch plant;
|
|
24
24
|
// daarom gebundeld invalideren in plaats van per rij.
|
|
25
|
-
const timerRef = useRef(null);
|
|
26
25
|
useEffect(() => {
|
|
27
|
-
const
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return;
|
|
32
|
-
timerRef.current = setTimeout(() => {
|
|
33
|
-
timerRef.current = null;
|
|
34
|
-
void qc.invalidateQueries({ queryKey: ENGINE_TASKS_KEY });
|
|
35
|
-
}, 2000);
|
|
36
|
-
})
|
|
37
|
-
.subscribe();
|
|
26
|
+
const refresh = debounce(() => {
|
|
27
|
+
void qc.invalidateQueries({ queryKey: ENGINE_TASKS_KEY });
|
|
28
|
+
}, 2000);
|
|
29
|
+
const off = subscribeTable("logistics_tasks", refresh);
|
|
38
30
|
return () => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
timerRef.current = null;
|
|
42
|
-
void supabase.removeChannel(channel);
|
|
31
|
+
refresh.cancel();
|
|
32
|
+
off();
|
|
43
33
|
};
|
|
44
34
|
}, [qc]);
|
|
45
35
|
const tasks = useMemo(() => query.data ?? [], [query.data]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKnownOrders.d.ts","sourceRoot":"","sources":["../../src/hooks/useKnownOrders.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useKnownOrders.d.ts","sourceRoot":"","sources":["../../src/hooks/useKnownOrders.ts"],"names":[],"mappings":"AAgBA;;;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"}
|
|
@@ -4,6 +4,7 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
4
4
|
import { listKnownOrders } from "../lib/floriday.functions";
|
|
5
5
|
import { normalizeOrderNumber } from "../lib/orderRefs";
|
|
6
6
|
import { isFulfilledOrderStatus } from "../lib/orderStatus";
|
|
7
|
+
import { callServerFn } from "../lib/serverFnError";
|
|
7
8
|
function keyOf(value) {
|
|
8
9
|
if (!value)
|
|
9
10
|
return null;
|
|
@@ -20,7 +21,7 @@ export function useKnownOrders() {
|
|
|
20
21
|
const fetchKnown = useServerFn(listKnownOrders);
|
|
21
22
|
const query = useQuery({
|
|
22
23
|
queryKey: ["known-orders"],
|
|
23
|
-
queryFn: () => fetchKnown(),
|
|
24
|
+
queryFn: () => callServerFn(() => fetchKnown(), "listKnownOrders"),
|
|
24
25
|
retry: 1,
|
|
25
26
|
staleTime: 30000,
|
|
26
27
|
refetchInterval: 60000,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* door naar vandaag; een order hoort te staan op de dag waarop hij ooit
|
|
4
4
|
* gepland is en nooit ná zijn leverdatum.
|
|
5
5
|
*/
|
|
6
|
-
export declare function useOrderPickDates(): {
|
|
6
|
+
export declare function useOrderPickDates(from?: string | null, to?: string | null): {
|
|
7
7
|
correctPickDate: (orderRef: string, enginePickDate: string) => string;
|
|
8
8
|
isLoading: boolean;
|
|
9
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrderPickDates.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPickDates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useOrderPickDates.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPickDates.ts"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;gCAoC3D,MAAM,kBAAkB,MAAM,KAAG,MAAM;;EAerD"}
|
|
@@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
3
3
|
import { useServerFn } from "@tanstack/react-start";
|
|
4
4
|
import { listOrderPickDates } from "../lib/pickDates.functions";
|
|
5
5
|
import { normalizeOrderNumber } from "../lib/orderRefs";
|
|
6
|
+
import { callServerFn } from "../lib/serverFnError";
|
|
6
7
|
function keyOf(value) {
|
|
7
8
|
if (!value)
|
|
8
9
|
return null;
|
|
@@ -16,13 +17,14 @@ function keyOf(value) {
|
|
|
16
17
|
* door naar vandaag; een order hoort te staan op de dag waarop hij ooit
|
|
17
18
|
* gepland is en nooit ná zijn leverdatum.
|
|
18
19
|
*/
|
|
19
|
-
export function useOrderPickDates() {
|
|
20
|
+
export function useOrderPickDates(from, to) {
|
|
20
21
|
const fetchDates = useServerFn(listOrderPickDates);
|
|
21
22
|
const query = useQuery({
|
|
22
|
-
queryKey: ["order-pick-dates"],
|
|
23
|
-
queryFn: () => fetchDates(),
|
|
23
|
+
queryKey: ["order-pick-dates", from ?? null, to ?? null],
|
|
24
|
+
queryFn: () => callServerFn(() => fetchDates({ data: { from: from ?? undefined, to: to ?? undefined } }), "listOrderPickDates"),
|
|
24
25
|
staleTime: 60000,
|
|
25
26
|
retry: 1,
|
|
27
|
+
placeholderData: (prev) => prev,
|
|
26
28
|
});
|
|
27
29
|
const map = useMemo(() => {
|
|
28
30
|
const m = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePickAgenda.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickAgenda.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePickAgenda.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickAgenda.ts"],"names":[],"mappings":"AAQA,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAwCD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;;;;;EA6GnE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo } from "react";
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { supabase } from "../integrations/supabase/moduleClient";
|
|
4
|
+
import { debounce, subscribeTable } from "../lib/realtimeBus";
|
|
4
5
|
import { useKnownOrders } from "./useKnownOrders";
|
|
5
6
|
import { useOrderPickDates } from "./useOrderPickDates";
|
|
6
7
|
function toOrders(value) {
|
|
@@ -30,9 +31,10 @@ function shiftDay(day, delta) {
|
|
|
30
31
|
}
|
|
31
32
|
export function usePickAgenda(from, to) {
|
|
32
33
|
const queryClient = useQueryClient();
|
|
33
|
-
//
|
|
34
|
-
// zetten ze terug op hun oorspronkelijke dag
|
|
35
|
-
|
|
34
|
+
// Beperkte marge rond de zichtbare week: de Engine schuift oude taken naar
|
|
35
|
+
// vandaag en wij zetten ze terug op hun oorspronkelijke dag. Een venster van
|
|
36
|
+
// een half jaar haalde honderden dagen op die daarna toch werden weggefilterd.
|
|
37
|
+
const wideFrom = from ? shiftDay(from, -21) : null;
|
|
36
38
|
const wideTo = to ? shiftDay(to, 30) : null;
|
|
37
39
|
const queryKey = ["logistics-pick-agenda", wideFrom, wideTo];
|
|
38
40
|
const query = useQuery({
|
|
@@ -41,7 +43,10 @@ export function usePickAgenda(from, to) {
|
|
|
41
43
|
staleTime: 30000,
|
|
42
44
|
retry: false,
|
|
43
45
|
refetchInterval: 60000,
|
|
46
|
+
refetchIntervalInBackground: false,
|
|
44
47
|
refetchOnWindowFocus: true,
|
|
48
|
+
// Vorige week blijft zichtbaar tijdens het laden van een nieuwe week.
|
|
49
|
+
placeholderData: (prev) => prev,
|
|
45
50
|
queryFn: async () => {
|
|
46
51
|
const { data, error } = await supabase.rpc("logistics_pick_agenda", {
|
|
47
52
|
p_from: wideFrom,
|
|
@@ -63,25 +68,24 @@ export function usePickAgenda(from, to) {
|
|
|
63
68
|
}));
|
|
64
69
|
},
|
|
65
70
|
});
|
|
66
|
-
// Ververs zodra de Engine taken wijzigt
|
|
67
|
-
//
|
|
68
|
-
//
|
|
71
|
+
// Ververs zodra de Engine taken wijzigt, via één gedeelde verbinding en met
|
|
72
|
+
// samenvoegen van seintjes: de Engine schrijft tientallen taken achter elkaar
|
|
73
|
+
// en elke wijziging afzonderlijk opnieuw ophalen liet het scherm blokkeren.
|
|
69
74
|
useEffect(() => {
|
|
70
|
-
const
|
|
71
|
-
.channel(`pick-agenda-logistics-tasks-${Math.random().toString(36).slice(2)}`)
|
|
72
|
-
.on("postgres_changes", { event: "*", schema: "public", table: "logistics_tasks" }, () => {
|
|
75
|
+
const refresh = debounce(() => {
|
|
73
76
|
queryClient.invalidateQueries({ queryKey: ["logistics-pick-agenda"] });
|
|
74
|
-
})
|
|
75
|
-
|
|
77
|
+
}, 750);
|
|
78
|
+
const off = subscribeTable("logistics_tasks", refresh);
|
|
76
79
|
return () => {
|
|
77
|
-
|
|
80
|
+
refresh.cancel();
|
|
81
|
+
off();
|
|
78
82
|
};
|
|
79
83
|
}, [queryClient]);
|
|
80
84
|
// Orders die de Verkoop-module niet kent horen hier niet te staan, en orders
|
|
81
85
|
// die de Engine ná hun leverdatum plant gaan terug naar hun eigen dag. De
|
|
82
86
|
// dagtotalen worden opnieuw opgeteld uit wat overblijft.
|
|
83
87
|
const { isKnownRef, isFulfilledRef } = useKnownOrders();
|
|
84
|
-
const { correctPickDate } = useOrderPickDates();
|
|
88
|
+
const { correctPickDate } = useOrderPickDates(wideFrom, wideTo);
|
|
85
89
|
const days = useMemo(() => {
|
|
86
90
|
const perDay = new Map();
|
|
87
91
|
for (const d of query.data ?? []) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { type PickModeInfo } from "../lib/pickMode";
|
|
1
2
|
/**
|
|
2
3
|
* De Engine bepaalt de actieve pickmethode. Alleen bij "picken per kar"
|
|
3
|
-
* bestaat er een karcontrolestap.
|
|
4
|
+
* bestaat er een karcontrolestap. Mislukt het ophalen, dan blijft de laatst
|
|
5
|
+
* bekende werkwijze gelden en melden we dat als fout — nooit stil uitzetten.
|
|
4
6
|
*/
|
|
5
7
|
export declare function usePickMode(): {
|
|
6
|
-
pickMode: "
|
|
8
|
+
pickMode: import("..").PickMode;
|
|
7
9
|
showCartControl: boolean;
|
|
8
10
|
isLoading: boolean;
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isUnknown: boolean;
|
|
13
|
+
error: string | undefined;
|
|
14
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<NoInfer<PickModeInfo>, Error>>;
|
|
9
15
|
};
|
|
10
16
|
//# sourceMappingURL=usePickMode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePickMode.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickMode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePickMode.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickMode.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,wBAAgB,WAAW;;;;;;;;EAkC1B"}
|