@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.
Files changed (63) hide show
  1. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
  2. package/dist-lib/components/afhandeling/ControlePage.js +92 -54
  3. package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
  4. package/dist-lib/hooks/useCartIndex.js +16 -7
  5. package/dist-lib/hooks/useEngineBufferEvents.d.ts +5 -0
  6. package/dist-lib/hooks/useEngineBufferEvents.d.ts.map +1 -1
  7. package/dist-lib/hooks/useEngineBufferEvents.js +18 -13
  8. package/dist-lib/hooks/useEngineTasks.d.ts.map +1 -1
  9. package/dist-lib/hooks/useEngineTasks.js +8 -18
  10. package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -1
  11. package/dist-lib/hooks/useKnownOrders.js +2 -1
  12. package/dist-lib/hooks/useOrderPickDates.d.ts +1 -1
  13. package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -1
  14. package/dist-lib/hooks/useOrderPickDates.js +5 -3
  15. package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
  16. package/dist-lib/hooks/usePickAgenda.js +17 -13
  17. package/dist-lib/hooks/usePickMode.d.ts +8 -2
  18. package/dist-lib/hooks/usePickMode.d.ts.map +1 -1
  19. package/dist-lib/hooks/usePickMode.js +17 -9
  20. package/dist-lib/index.d.ts +1 -2
  21. package/dist-lib/index.d.ts.map +1 -1
  22. package/dist-lib/index.js +1 -33
  23. package/dist-lib/integrations/supabase/auth-middleware.d.ts +3053 -161
  24. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/client.server.d.ts +3053 -161
  26. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/moduleClient.d.ts +2982 -88
  28. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/types.d.ts +3063 -143
  30. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  31. package/dist-lib/lib/afhandelingSettings.functions.d.ts +5798 -14
  32. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/carriers.functions.d.ts +14510 -40
  34. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/cartAssignments.functions.d.ts +14495 -35
  36. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/cartControl.functions.d.ts +31999 -104
  38. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/cartControl.functions.js +389 -259
  40. package/dist-lib/lib/deliveryNote.functions.d.ts +3053 -161
  41. package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/engineLayouts.functions.d.ts +3053 -161
  43. package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday.functions.d.ts +5798 -14
  45. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/orderFulfilment.functions.d.ts +3053 -161
  47. package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -1
  48. package/dist-lib/lib/pickDates.functions.d.ts +2985 -88
  49. package/dist-lib/lib/pickDates.functions.d.ts.map +1 -1
  50. package/dist-lib/lib/pickDates.functions.js +30 -3
  51. package/dist-lib/lib/pickMode.d.ts +20 -2
  52. package/dist-lib/lib/pickMode.d.ts.map +1 -1
  53. package/dist-lib/lib/pickMode.js +65 -16
  54. package/dist-lib/lib/realtimeBus.d.ts +20 -0
  55. package/dist-lib/lib/realtimeBus.d.ts.map +1 -0
  56. package/dist-lib/lib/realtimeBus.js +56 -0
  57. package/dist-lib/lib/serverFnError.d.ts +12 -0
  58. package/dist-lib/lib/serverFnError.d.ts.map +1 -1
  59. package/dist-lib/lib/serverFnError.js +48 -1
  60. package/dist-lib/moduleDefinition.d.ts +3 -0
  61. package/dist-lib/moduleDefinition.d.ts.map +1 -0
  62. package/dist-lib/moduleDefinition.js +36 -0
  63. 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,+BAu3BpE"}
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: true,
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: 0,
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 = carts.map(controlCartKey);
310
- setChecked(on ? new Set(keys) : new Set());
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
- return submit({
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
- qc.invalidateQueries({ queryKey: ["control"] });
345
- qc.invalidateQueries({ queryKey: ["floriday", "orders"] });
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(e instanceof Error ? e.message : "Opslaan mislukt");
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
- // Realtime sync: invalidate control queries on any change to relevant tables.
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 })] })] }), _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: carts.length > 0 && carts.every((c) => checked.has(controlCartKey(c)))
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
- : carts.some((c) => checked.has(controlCartKey(c)))
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: 4, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })), !cartsQ.isLoading && carts.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Geen karren om vandaag te controleren." }) })), pagedCarts.map((c) => {
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"] }), _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) => {
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;6BAqBrB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,MAAM,GAAG,IAAI;;;EA4B1F"}
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
- // Per orderreferentie de unieke karren tellen.
30
- const cartsForOrder = useCallback((...refs) => {
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 set = perOrder.get(ref);
51
- if (set)
52
- return set.size;
59
+ const count = countsByRef.get(ref);
60
+ if (count !== undefined)
61
+ return count;
53
62
  }
54
63
  return null;
55
- }, [rows, isKnownRef]);
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;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAsB5C"}
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 { supabase } from "../integrations/supabase/moduleClient";
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 volledig opnieuw bij de Engine opgehaald moeten worden zodra
8
- * een buffer sluit of een order wordt losgekoppeld. Alles komt van de Engine
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 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;
37
+ const refresh = debounce(() => {
38
38
  for (const key of KEYS)
39
39
  qc.invalidateQueries({ queryKey: key });
40
- void qc.refetchQueries({ type: "active" });
41
- })
42
- .subscribe();
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
- supabase.removeChannel(channel);
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":"AAGA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEjF,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;;;;;EAmDA;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAShF"}
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, useRef } from "react";
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 channel = supabase
28
- .channel(`engine-tasks-${Math.random().toString(36).slice(2)}`)
29
- .on("postgres_changes", { event: "*", schema: "public", table: "logistics_tasks" }, () => {
30
- if (timerRef.current)
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
- if (timerRef.current)
40
- clearTimeout(timerRef.current);
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":"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"}
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":"AAaA;;;;GAIG;AACH,wBAAgB,iBAAiB;gCA+BlB,MAAM,kBAAkB,MAAM,KAAG,MAAM;;EAerD"}
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":"AAOA,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;;;;;EA8GnE"}
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
- // Ruimer ophalen dan gevraagd: de Engine schuift oude taken naar vandaag, wij
34
- // zetten ze terug op hun oorspronkelijke dag en dat kan buiten de week vallen.
35
- const wideFrom = from ? shiftDay(from, -180) : null;
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. Elke hook-instantie krijgt een eigen
67
- // kanaalnaam; hergebruik van dezelfde naam laat Supabase een al-geabonneerd
68
- // kanaal terugvinden en gooit "cannot add postgres_changes after subscribe()".
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 channel = supabase
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
- .subscribe();
77
+ }, 750);
78
+ const off = subscribeTable("logistics_tasks", refresh);
76
79
  return () => {
77
- supabase.removeChannel(channel);
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. Zonder antwoord blijft die verborgen.
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: "order" | "buffer" | "cart" | "smallest" | null;
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":"AASA;;;GAGG;AACH,wBAAgB,WAAW;;;;EA0B1B"}
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"}