@flowselections/floriday-afhandeling-module 1.0.44 → 1.0.46

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 (78) hide show
  1. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
  2. package/dist-lib/components/afhandeling/ControlePage.js +93 -55
  3. package/dist-lib/components/afhandeling/DagDetailDialog.js +1 -1
  4. package/dist-lib/components/afhandeling/ScanControleView.js +1 -1
  5. package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
  6. package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
  7. package/dist-lib/components/afhandeling/WerkplekWaarschuwing.d.ts +9 -9
  8. package/dist-lib/components/afhandeling/WerkplekWaarschuwing.js +28 -28
  9. package/dist-lib/components/ui/responsive-dialog.js +1 -1
  10. package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
  11. package/dist-lib/hooks/useCartIndex.js +16 -7
  12. package/dist-lib/hooks/useEngineBufferEvents.d.ts +5 -0
  13. package/dist-lib/hooks/useEngineBufferEvents.d.ts.map +1 -1
  14. package/dist-lib/hooks/useEngineBufferEvents.js +18 -13
  15. package/dist-lib/hooks/useEngineTasks.d.ts.map +1 -1
  16. package/dist-lib/hooks/useEngineTasks.js +8 -18
  17. package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -1
  18. package/dist-lib/hooks/useKnownOrders.js +2 -1
  19. package/dist-lib/hooks/useOrderPickDates.d.ts +1 -1
  20. package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -1
  21. package/dist-lib/hooks/useOrderPickDates.js +5 -3
  22. package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
  23. package/dist-lib/hooks/usePickAgenda.js +17 -13
  24. package/dist-lib/hooks/usePickMode.d.ts +8 -2
  25. package/dist-lib/hooks/usePickMode.d.ts.map +1 -1
  26. package/dist-lib/hooks/usePickMode.js +17 -9
  27. package/dist-lib/index.d.ts +1 -2
  28. package/dist-lib/index.d.ts.map +1 -1
  29. package/dist-lib/index.js +1 -33
  30. package/dist-lib/integrations/supabase/auth-middleware.d.ts +5145 -373
  31. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/client.server.d.ts +5145 -373
  33. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  34. package/dist-lib/integrations/supabase/client.server.js +1 -1
  35. package/dist-lib/integrations/supabase/moduleClient.d.ts +5160 -386
  36. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  37. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +1 -1
  38. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
  39. package/dist-lib/integrations/supabase/previewAuthStorage.js +8 -1
  40. package/dist-lib/integrations/supabase/types.d.ts +5173 -345
  41. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  42. package/dist-lib/lib/afhandelingSettings.functions.d.ts +9558 -14
  43. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/carriers.functions.d.ts +23910 -40
  45. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/cartAssignments.functions.d.ts +23895 -35
  47. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  48. package/dist-lib/lib/cartControl.functions.d.ts +52679 -104
  49. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  50. package/dist-lib/lib/cartControl.functions.js +389 -259
  51. package/dist-lib/lib/deliveryNote.functions.d.ts +5145 -373
  52. package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -1
  53. package/dist-lib/lib/deliveryNotePrint.d.ts +33 -33
  54. package/dist-lib/lib/deliveryNotePrint.js +219 -219
  55. package/dist-lib/lib/engineLayouts.functions.d.ts +5145 -373
  56. package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/floriday.functions.d.ts +9558 -14
  58. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/logisticsEngine/capacity.js +1 -1
  60. package/dist-lib/lib/logisticsEngine/staffPlanning.js +1 -1
  61. package/dist-lib/lib/orderFulfilment.functions.d.ts +5145 -373
  62. package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/pickDates.functions.d.ts +5163 -386
  64. package/dist-lib/lib/pickDates.functions.d.ts.map +1 -1
  65. package/dist-lib/lib/pickDates.functions.js +30 -3
  66. package/dist-lib/lib/pickMode.d.ts +20 -2
  67. package/dist-lib/lib/pickMode.d.ts.map +1 -1
  68. package/dist-lib/lib/pickMode.js +65 -16
  69. package/dist-lib/lib/realtimeBus.d.ts +20 -0
  70. package/dist-lib/lib/realtimeBus.d.ts.map +1 -0
  71. package/dist-lib/lib/realtimeBus.js +56 -0
  72. package/dist-lib/lib/serverFnError.d.ts +12 -0
  73. package/dist-lib/lib/serverFnError.d.ts.map +1 -1
  74. package/dist-lib/lib/serverFnError.js +48 -1
  75. package/dist-lib/moduleDefinition.d.ts +3 -0
  76. package/dist-lib/moduleDefinition.d.ts.map +1 -0
  77. package/dist-lib/moduleDefinition.js +36 -0
  78. package/package.json +3 -3
@@ -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,15 +1,15 @@
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
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
5
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog.js";
7
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
7
  import { useServerFn } from "@tanstack/react-start";
9
8
  import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSession, listControlHistory, getControlSessionDetail, getControlDraft, listDeliveredToday, saveControlDraft, } from "../../lib/cartControl.functions";
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"
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { Badge, Checkbox, Button, Dialog, DialogTitle, DialogDescription, Input, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
6
  import { CalendarClock, CheckCircle2, ChevronDown, ChevronRight, Printer, Users } from "lucide-react";
7
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
7
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog.js";
8
8
  import { usePickAgenda } from "../../hooks/usePickAgenda";
9
9
  import { useOrderBuffers } from "../../hooks/useOrderBuffers";
10
10
  import { isSameDay } from "../../lib/pickDate";
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
- import { supabase } from "../../integrations/supabase/moduleClient";
3
+ import { supabase } from "../../integrations/supabase/moduleClient.js";
4
4
  import { Button, Badge, Card, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
5
5
  import { AlertTriangle, CheckCircle2, RefreshCw, ScanLine } from "lucide-react";
6
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -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 "../../components/ui/responsive-dialog";
5
+ import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../ui/responsive-dialog.js";
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 "../../components/ui/responsive-dialog";
6
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog.js";
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,10 +1,10 @@
1
- /** Leest de ingestelde werkplek client-side (SSR-veilig). */
2
- export declare function useWerkplek(): string | null;
3
- /**
4
- * Eén melding bovenaan een printscherm zolang er geen werkplek is ingesteld.
5
- * Printknoppen zijn dan uitgeschakeld, zodat er geen fout per printactie komt.
6
- */
7
- export declare function WerkplekWaarschuwing({ werkplek }: {
8
- werkplek: string | null;
9
- }): import("react").JSX.Element | null;
1
+ /** Leest de ingestelde werkplek client-side (SSR-veilig). */
2
+ export declare function useWerkplek(): string | null;
3
+ /**
4
+ * Eén melding bovenaan een printscherm zolang er geen werkplek is ingesteld.
5
+ * Printknoppen zijn dan uitgeschakeld, zodat er geen fout per printactie komt.
6
+ */
7
+ export declare function WerkplekWaarschuwing({ werkplek }: {
8
+ werkplek: string | null;
9
+ }): import("react").JSX.Element | null;
10
10
  //# sourceMappingURL=WerkplekWaarschuwing.d.ts.map
@@ -1,28 +1,28 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { AlertTriangle } from "lucide-react";
4
- import { getWerkplek } from "../../lib/printing";
5
- /** Leest de ingestelde werkplek client-side (SSR-veilig). */
6
- export function useWerkplek() {
7
- const [werkplek, setWerkplekState] = useState(null);
8
- useEffect(() => {
9
- setWerkplekState(getWerkplek());
10
- const onStorage = () => setWerkplekState(getWerkplek());
11
- window.addEventListener("storage", onStorage);
12
- window.addEventListener("focus", onStorage);
13
- return () => {
14
- window.removeEventListener("storage", onStorage);
15
- window.removeEventListener("focus", onStorage);
16
- };
17
- }, []);
18
- return werkplek;
19
- }
20
- /**
21
- * Eén melding bovenaan een printscherm zolang er geen werkplek is ingesteld.
22
- * Printknoppen zijn dan uitgeschakeld, zodat er geen fout per printactie komt.
23
- */
24
- export function WerkplekWaarschuwing({ werkplek }) {
25
- if (werkplek)
26
- return null;
27
- return (_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Geen werkplek ingesteld." }), " Printen is uitgeschakeld. Stel de werkplek (pickstation- of computernaam) in bij ", _jsx("strong", { children: "Instellingen" }), "."] })] }));
28
- }
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { AlertTriangle } from "lucide-react";
4
+ import { getWerkplek } from "../../lib/printing";
5
+ /** Leest de ingestelde werkplek client-side (SSR-veilig). */
6
+ export function useWerkplek() {
7
+ const [werkplek, setWerkplekState] = useState(null);
8
+ useEffect(() => {
9
+ setWerkplekState(getWerkplek());
10
+ const onStorage = () => setWerkplekState(getWerkplek());
11
+ window.addEventListener("storage", onStorage);
12
+ window.addEventListener("focus", onStorage);
13
+ return () => {
14
+ window.removeEventListener("storage", onStorage);
15
+ window.removeEventListener("focus", onStorage);
16
+ };
17
+ }, []);
18
+ return werkplek;
19
+ }
20
+ /**
21
+ * Eén melding bovenaan een printscherm zolang er geen werkplek is ingesteld.
22
+ * Printknoppen zijn dan uitgeschakeld, zodat er geen fout per printactie komt.
23
+ */
24
+ export function WerkplekWaarschuwing({ werkplek }) {
25
+ if (werkplek)
26
+ return null;
27
+ return (_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Geen werkplek ingesteld." }), " Printen is uitgeschakeld. Stel de werkplek (pickstation- of computernaam) in bij ", _jsx("strong", { children: "Instellingen" }), "."] })] }));
28
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { AlertDialogContent, AlertDialogFooter, AlertDialogHeader, DialogContent, DialogFooter, DialogHeader, } from "@flowselections/core";
3
- import { cn } from "../../lib/utils";
3
+ import { cn } from "../../lib/utils.js";
4
4
  const WIDTH_BY_SIZE = {
5
5
  sm: "min(40rem, calc(100vw - 2rem))",
6
6
  md: "min(48rem, calc(100vw - 2rem))",
@@ -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"}