@flowselections/floriday-afhandeling-module 1.0.29 → 1.0.31

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 (40) hide show
  1. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  2. package/dist-lib/components/afhandeling/LeveringenPage.js +7 -24
  3. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  4. package/dist-lib/components/afhandeling/PickAgendaView.js +22 -24
  5. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +5 -1
  6. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  7. package/dist-lib/components/afhandeling/WeekAgendaView.js +19 -29
  8. package/dist-lib/hooks/useEngineTasks.d.ts +2 -0
  9. package/dist-lib/hooks/useEngineTasks.d.ts.map +1 -1
  10. package/dist-lib/hooks/useEngineTasks.js +19 -4
  11. package/dist-lib/hooks/useOrderPlanning.d.ts +12 -1
  12. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  13. package/dist-lib/hooks/useOrderPlanning.js +65 -26
  14. package/dist-lib/integrations/supabase/auth-middleware.d.ts +629 -4
  15. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/client.d.ts +629 -4
  17. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/client.server.d.ts +629 -4
  19. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/types.d.ts +630 -5
  21. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.js +12 -1
  23. package/dist-lib/lib/afhandelingSettings.functions.d.ts +1378 -128
  24. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  25. package/dist-lib/lib/agendaRange.d.ts +13 -0
  26. package/dist-lib/lib/agendaRange.d.ts.map +1 -0
  27. package/dist-lib/lib/agendaRange.js +25 -0
  28. package/dist-lib/lib/carriers.functions.d.ts +3265 -140
  29. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/cartAssignments.functions.d.ts +3265 -140
  31. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  32. package/dist-lib/lib/cartControl.functions.d.ts +6650 -400
  33. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  34. package/dist-lib/lib/floriday.functions.d.ts +647 -8
  35. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  36. package/dist-lib/lib/floriday.functions.js +86 -20
  37. package/dist-lib/lib/logisticsEngine/engine.d.ts +16 -3
  38. package/dist-lib/lib/logisticsEngine/engine.d.ts.map +1 -1
  39. package/dist-lib/lib/logisticsEngine/engine.js +13 -6
  40. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"LeveringenPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/LeveringenPage.tsx"],"names":[],"mappings":"AAeA,wBAAgB,cAAc,gCA+E7B"}
1
+ {"version":3,"file":"LeveringenPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/LeveringenPage.tsx"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,gCA8B7B"}
@@ -1,29 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
- import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
3
+ import { Button } from "@flowselections/core";
4
4
  import { WeekAgendaView } from "./WeekAgendaView";
5
5
  import { useOrderPlanning } from "../../hooks/useOrderPlanning";
6
- const ALL = "__all__";
6
+ import { agendaRange, currentWeekStart } from "../../lib/agendaRange";
7
7
  export function LeveringenPage() {
8
- const { planned, isLoading, error, warning, refetch } = useOrderPlanning();
9
- const [customer, setCustomer] = useState(ALL);
10
- const [deliveryType, setDeliveryType] = useState(ALL);
11
- const [dateFrom, setDateFrom] = useState("");
12
- const [dateTo, setDateTo] = useState("");
13
- const customers = useMemo(() => Array.from(new Set(planned.map((p) => p.order.customerName).filter(Boolean))).sort(), [planned]);
14
- const deliveryTypes = useMemo(() => Array.from(new Set(planned.map((p) => p.order.deliveryType).filter(Boolean))).sort(), [planned]);
15
- const filtered = useMemo(() => {
16
- return planned.filter(({ order: o }) => {
17
- if (customer !== ALL && o.customerName !== customer)
18
- return false;
19
- if (deliveryType !== ALL && o.deliveryType !== deliveryType)
20
- return false;
21
- if (dateFrom && (!o.deliveryDate || o.deliveryDate < dateFrom))
22
- return false;
23
- if (dateTo && (!o.deliveryDate || o.deliveryDate > dateTo + "T23:59:59"))
24
- return false;
25
- return true;
26
- });
27
- }, [planned, customer, deliveryType, dateFrom, dateTo]);
28
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsx("div", { className: "flex flex-wrap items-end justify-between gap-3", children: _jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Datum vanaf" }), _jsx(Input, { type: "date", value: dateFrom, onChange: (e) => setDateFrom(e.target.value), className: "w-40" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Datum t/m" }), _jsx(Input, { type: "date", value: dateTo, onChange: (e) => setDateTo(e.target.value), className: "w-40" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Klant" }), _jsxs(Select, { value: customer, onValueChange: setCustomer, children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Alle klanten" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: "Alle klanten" }), customers.map((c) => _jsx(SelectItem, { value: c, children: c }, c))] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Type levering" }), _jsxs(Select, { value: deliveryType, onValueChange: setDeliveryType, children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Alle types" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: "Alle types" }), deliveryTypes.map((t) => _jsx(SelectItem, { value: t, children: t }, t))] })] })] })] }) }), warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx(WeekAgendaView, { planned: filtered, mode: "delivery", isLoading: isLoading, showPrintButtons: true }))] }));
8
+ const [weekStart, setWeekStart] = useState(() => currentWeekStart());
9
+ const range = useMemo(() => agendaRange(weekStart), [weekStart]);
10
+ const { planned, nearestDeliveryDate, isLoading, error, warning, refetch } = useOrderPlanning(range);
11
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx(WeekAgendaView, { planned: planned, mode: "delivery", isLoading: isLoading, showPrintButtons: true, onWeekChange: setWeekStart, jumpToDate: nearestDeliveryDate }))] }));
29
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAIlE,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,+BAgGnE"}
1
+ {"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,+BAwEnE"}
@@ -1,32 +1,30 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
- import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
3
+ import { Button } from "@flowselections/core";
4
4
  import { WeekAgendaView } from "./WeekAgendaView";
5
5
  import { DagDetailDialog } from "./DagDetailDialog";
6
6
  import { useOrderPlanning } from "../../hooks/useOrderPlanning";
7
- const ALL = "__all__";
7
+ import { agendaRange, currentWeekStart } from "../../lib/agendaRange";
8
8
  export function PickAgendaView({ onOrderClick }) {
9
- const { planned, awaiting, isLoading, error, warning, refetch } = useOrderPlanning();
9
+ const [weekStart, setWeekStart] = useState(() => currentWeekStart());
10
+ const range = useMemo(() => agendaRange(weekStart), [weekStart]);
11
+ const { planned, awaiting, nearestDeliveryDate, signalError, lastEngineRun, isLoading, error, warning, refetch } = useOrderPlanning(range);
10
12
  const awaitingCount = awaiting.length;
13
+ const awaitingRefs = awaiting
14
+ .map((p) => p.order.orderNumber || p.order.id)
15
+ .filter(Boolean)
16
+ .slice(0, 8);
17
+ const lastRunLabel = lastEngineRun
18
+ ? new Date(lastEngineRun).toLocaleString("nl-NL", {
19
+ timeZone: "Europe/Amsterdam",
20
+ dateStyle: "short",
21
+ timeStyle: "short",
22
+ })
23
+ : null;
11
24
  const [dayDetailDate, setDayDetailDate] = useState(null);
12
- const [customer, setCustomer] = useState(ALL);
13
- const [deliveryType, setDeliveryType] = useState(ALL);
14
- const [dateFrom, setDateFrom] = useState("");
15
- const [dateTo, setDateTo] = useState("");
16
- const customers = useMemo(() => Array.from(new Set(planned.map((p) => p.order.customerName).filter(Boolean))).sort(), [planned]);
17
- const deliveryTypes = useMemo(() => Array.from(new Set(planned.map((p) => p.order.deliveryType).filter(Boolean))).sort(), [planned]);
18
- const filtered = useMemo(() => {
19
- return planned.filter(({ order: o }) => {
20
- if (customer !== ALL && o.customerName !== customer)
21
- return false;
22
- if (deliveryType !== ALL && o.deliveryType !== deliveryType)
23
- return false;
24
- if (dateFrom && (!o.deliveryDate || o.deliveryDate < dateFrom))
25
- return false;
26
- if (dateTo && (!o.deliveryDate || o.deliveryDate > dateTo + "T23:59:59"))
27
- return false;
28
- return true;
29
- });
30
- }, [planned, customer, deliveryType, dateFrom, dateTo]);
31
- return (_jsxs("div", { className: "space-y-6", children: [_jsx("div", { className: "flex flex-wrap items-end justify-between gap-3", children: _jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Datum vanaf" }), _jsx(Input, { type: "date", value: dateFrom, onChange: (e) => setDateFrom(e.target.value), className: "w-40" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Datum t/m" }), _jsx(Input, { type: "date", value: dateTo, onChange: (e) => setDateTo(e.target.value), className: "w-40" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Klant" }), _jsxs(Select, { value: customer, onValueChange: setCustomer, children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Alle klanten" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: "Alle klanten" }), customers.map((c) => _jsx(SelectItem, { value: c, children: c }, c))] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Type levering" }), _jsxs(Select, { value: deliveryType, onValueChange: setDeliveryType, children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Alle types" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: "Alle types" }), deliveryTypes.map((t) => _jsx(SelectItem, { value: t, children: t }, t))] })] })] })] }) }), awaitingCount > 0 && (_jsxs("div", { className: "rounded-md border border-sky-300 bg-sky-50 p-4 text-sm text-sky-900", children: [awaitingCount, " order", awaitingCount === 1 ? "" : "s", " wacht", awaitingCount === 1 ? "" : "en", " op een indeling van de Logistics Engine en worden daarom nog niet in de agenda gepland."] })), warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx(WeekAgendaView, { planned: filtered, mode: "pick", isLoading: isLoading, onOrderClick: onOrderClick, onDayClick: setDayDetailDate })), _jsx(DagDetailDialog, { open: !!dayDetailDate, date: dayDetailDate, planned: filtered, onOpenChange: (o) => !o && setDayDetailDate(null) })] }));
25
+ return (_jsxs("div", { className: "space-y-6", children: [awaitingCount > 0 && (_jsxs("div", { className: "space-y-1 rounded-md border border-sky-300 bg-sky-50 p-4 text-sm text-sky-900", children: [_jsxs("div", { children: [awaitingCount, " order", awaitingCount === 1 ? "" : "s", " wacht", awaitingCount === 1 ? "" : "en", " op een indeling van de Logistics Engine en ", awaitingCount === 1 ? "wordt" : "worden", " daarom nog niet in de agenda gepland: ", awaitingRefs.join(", "), awaitingCount > awaitingRefs.length ? ` en ${awaitingCount - awaitingRefs.length} meer` : "", "."] }), _jsxs("div", { className: "text-xs", children: [lastRunLabel
26
+ ? `Laatste indeling door de Engine: ${lastRunLabel}.`
27
+ : "Er is nog geen indeling van de Engine bekend voor deze periode.", " ", signalError
28
+ ? `Melding vanuit deze module is mislukt: ${signalError}`
29
+ : "Melding vanuit deze module is verstuurd; het inplannen gebeurt door de Logistics Engine."] })] })), warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), error ? (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [error, _jsx(Button, { size: "sm", variant: "outline", className: "ml-3", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : (_jsx(WeekAgendaView, { planned: planned, mode: "pick", isLoading: isLoading, onOrderClick: onOrderClick, onDayClick: setDayDetailDate, onWeekChange: setWeekStart, jumpToDate: nearestDeliveryDate })), _jsx(DagDetailDialog, { open: !!dayDetailDate, date: dayDetailDate, planned: planned, onOpenChange: (o) => !o && setDayDetailDate(null) })] }));
32
30
  }
@@ -10,6 +10,10 @@ export interface WeekAgendaViewProps {
10
10
  title?: string;
11
11
  /** Toon "Afleverbrief" print-knoppen (per order + per dag). */
12
12
  showPrintButtons?: boolean;
13
+ /** Meld de zichtbare week zodat de pagina alleen die periode ophaalt. */
14
+ onWeekChange?: (weekStart: Date, weekEnd: Date) => void;
15
+ /** Dichtstbijzijnde datum met data buiten de zichtbare week (auto-jump). */
16
+ jumpToDate?: string | null;
13
17
  }
14
- export declare function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, showPrintButtons, }: WeekAgendaViewProps): import("react").JSX.Element;
18
+ export declare function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, showPrintButtons, onWeekChange, jumpToDate, }: WeekAgendaViewProps): import("react").JSX.Element;
15
19
  //# sourceMappingURL=WeekAgendaView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAUjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,gBAAgB,GACjB,EAAE,mBAAmB,+BAiKrB"}
1
+ {"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAUjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IACxD,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AA+DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,UAAU,GACX,EAAE,mBAAmB,+BAyJrB"}
@@ -41,44 +41,34 @@ const MODE_LABELS = {
41
41
  "transport-register": { summary: "Aanmeldingen deze week" },
42
42
  pick: { summary: "Te picken deze week" },
43
43
  };
44
- export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, showPrintButtons, }) {
44
+ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, showPrintButtons, onWeekChange, jumpToDate, }) {
45
45
  const [cursor, setCursor] = useState(() => {
46
46
  const d = new Date();
47
47
  d.setHours(0, 0, 0, 0);
48
48
  return startOfWeek(d);
49
49
  });
50
- // Als er in de huidige week niks staat maar er zijn wél orders in de
51
- // toekomst, spring dan éénmalig naar de eerstvolgende week met data zodat
52
- // gebruikers hun orders direct zien in plaats van een lege week.
53
- const autoJumpedRef = useRef(false);
50
+ const weekStart = useMemo(() => startOfWeek(cursor), [cursor]);
51
+ const weekEnd = useMemo(() => addDays(weekStart, 6), [weekStart]);
52
+ const weekNumber = useMemo(() => getISOWeek(weekStart), [weekStart]);
53
+ // Meld de zichtbare week zodat de pagina alleen die periode ophaalt.
54
54
  useEffect(() => {
55
- if (autoJumpedRef.current)
55
+ onWeekChange?.(weekStart, weekEnd);
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [weekStart.getTime()]);
58
+ // Is de week leeg maar bestaat er wél data elders, spring dan éénmalig naar
59
+ // de dichtstbijzijnde week met orders.
60
+ const jumpedToRef = useRef(null);
61
+ useEffect(() => {
62
+ if (isLoading || planned.length > 0 || !jumpToDate)
56
63
  return;
57
- if (!planned.length)
64
+ if (jumpedToRef.current === jumpToDate)
58
65
  return;
59
- const today = new Date();
60
- today.setHours(0, 0, 0, 0);
61
- const currentWeekStart = startOfWeek(today);
62
- const currentWeekEnd = addDays(currentWeekStart, 6);
63
- const dates = planned
64
- .map((p) => dateForPlanned(p, mode))
65
- .filter((d) => !!d);
66
- const hasCurrent = dates.some((d) => d >= currentWeekStart && d <= new Date(currentWeekEnd.getTime() + 86399999));
67
- if (hasCurrent) {
68
- autoJumpedRef.current = true;
66
+ const target = new Date(jumpToDate);
67
+ if (Number.isNaN(target.getTime()))
69
68
  return;
70
- }
71
- const future = dates
72
- .filter((d) => d > currentWeekEnd)
73
- .sort((a, b) => a.getTime() - b.getTime());
74
- if (future.length > 0) {
75
- setCursor(startOfWeek(future[0]));
76
- autoJumpedRef.current = true;
77
- }
78
- }, [planned, mode]);
79
- const weekStart = useMemo(() => startOfWeek(cursor), [cursor]);
80
- const weekEnd = useMemo(() => addDays(weekStart, 6), [weekStart]);
81
- const weekNumber = useMemo(() => getISOWeek(weekStart), [weekStart]);
69
+ jumpedToRef.current = jumpToDate;
70
+ setCursor(startOfWeek(target));
71
+ }, [jumpToDate, planned.length, isLoading]);
82
72
  const days = useMemo(() => {
83
73
  return Array.from({ length: 7 }, (_, i) => {
84
74
  const date = addDays(weekStart, i);
@@ -3,6 +3,8 @@ export declare const ENGINE_TASKS_KEY: string[];
3
3
  export declare function useEngineTasks(options?: {
4
4
  taskTypes?: string[];
5
5
  statuses?: string[];
6
+ plannedFrom?: string | null;
7
+ plannedTo?: string | null;
6
8
  }): {
7
9
  tasks: EngineTask[];
8
10
  isLoading: boolean;
@@ -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;IAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE;;;;;EAsCrF;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":"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,4 +1,4 @@
1
- import { useEffect, useMemo } from "react";
1
+ import { useEffect, useMemo, useRef } from "react";
2
2
  import { useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { supabase } from "../integrations/supabase/client";
4
4
  import { listEngineTasks } from "../lib/logisticsEngine/engine";
@@ -8,22 +8,37 @@ export function useEngineTasks(options) {
8
8
  const qc = useQueryClient();
9
9
  const taskTypes = options?.taskTypes;
10
10
  const statuses = options?.statuses ?? OPEN_STATUSES;
11
+ const plannedFrom = options?.plannedFrom ?? null;
12
+ const plannedTo = options?.plannedTo ?? null;
11
13
  const query = useQuery({
12
- queryKey: [...ENGINE_TASKS_KEY, taskTypes ?? "all", statuses],
13
- queryFn: () => listEngineTasks({ taskTypes, statuses }),
14
+ queryKey: [...ENGINE_TASKS_KEY, taskTypes ?? "all", statuses, plannedFrom, plannedTo],
15
+ queryFn: () => listEngineTasks({ taskTypes, statuses, plannedFrom, plannedTo }),
14
16
  retry: false,
17
+ staleTime: 30000,
18
+ placeholderData: (prev) => prev,
15
19
  refetchInterval: 60000,
16
20
  refetchIntervalInBackground: false,
17
21
  refetchOnWindowFocus: true,
18
22
  });
23
+ // Realtime-updates komen in bursts binnen als de Engine een batch plant;
24
+ // daarom gebundeld invalideren in plaats van per rij.
25
+ const timerRef = useRef(null);
19
26
  useEffect(() => {
20
27
  const channel = supabase
21
28
  .channel("engine-tasks")
22
29
  .on("postgres_changes", { event: "*", schema: "public", table: "logistics_tasks" }, () => {
23
- void qc.invalidateQueries({ queryKey: ENGINE_TASKS_KEY });
30
+ if (timerRef.current)
31
+ return;
32
+ timerRef.current = setTimeout(() => {
33
+ timerRef.current = null;
34
+ void qc.invalidateQueries({ queryKey: ENGINE_TASKS_KEY });
35
+ }, 2000);
24
36
  })
25
37
  .subscribe();
26
38
  return () => {
39
+ if (timerRef.current)
40
+ clearTimeout(timerRef.current);
41
+ timerRef.current = null;
27
42
  void supabase.removeChannel(channel);
28
43
  };
29
44
  }, [qc]);
@@ -12,12 +12,23 @@ export interface PlannedOrder {
12
12
  }
13
13
  export interface UseOrderPlanningResult {
14
14
  planned: PlannedOrder[];
15
+ /** Dichtstbijzijnde leverdatum buiten het venster (leeg venster → auto-jump). */
16
+ nearestDeliveryDate?: string | null;
15
17
  /** Orders die op een Engine-taak wachten en dus niet zijn ingedeeld. */
16
18
  awaiting: PlannedOrder[];
19
+ /** Foutmelding als het signaal naar de Engine niet verstuurd kon worden. */
20
+ signalError?: string | null;
21
+ /** Tijdstip van de laatst bekende Engine-ronde (nieuwste taak). */
22
+ lastEngineRun?: string | null;
17
23
  isLoading: boolean;
18
24
  error?: string;
19
25
  warning?: string;
20
26
  refetch: () => void;
21
27
  }
22
- export declare function useOrderPlanning(): UseOrderPlanningResult;
28
+ export interface UseOrderPlanningOptions {
29
+ /** Leverdatumvenster (YYYY-MM-DD). Zonder venster laadt de module te veel. */
30
+ from?: string;
31
+ to?: string;
32
+ }
33
+ export declare function useOrderPlanning(range?: UseOrderPlanningOptions): UseOrderPlanningResult;
23
34
  //# sourceMappingURL=useOrderPlanning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEjF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,yEAAyE;IACzE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,wEAAwE;IACxE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,wBAAgB,gBAAgB,IAAI,sBAAsB,CA6LzD"}
1
+ {"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEjF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,yEAAyE;IACzE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,MAAM,WAAW,uBAAuB;IACtC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,uBAAuB,GAAG,sBAAsB,CAwOxF"}
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useRef } from "react";
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { useServerFn } from "@tanstack/react-start";
4
4
  import { getFloridayOrders } from "../lib/floriday.functions";
@@ -12,15 +12,17 @@ import { emitEngineEvent } from "../lib/logisticsEngine/engine";
12
12
  function orderRefsFor(order) {
13
13
  return [order.orderNumber, order.id].filter((v) => Boolean(v));
14
14
  }
15
- export function useOrderPlanning() {
15
+ export function useOrderPlanning(range) {
16
+ const from = range?.from ?? null;
17
+ const to = range?.to ?? null;
16
18
  const fetchOrders = useServerFn(getFloridayOrders);
17
19
  const fetchSettings = useServerFn(getAfhandelingSettings);
18
20
  const fetchCarriers = useServerFn(listCarriers);
19
21
  const ordersQuery = useQuery({
20
- queryKey: ["floriday", "orders"],
22
+ queryKey: ["floriday", "orders", from, to],
21
23
  queryFn: async () => {
22
24
  try {
23
- const result = await fetchOrders();
25
+ const result = await fetchOrders({ data: { from: from ?? undefined, to: to ?? undefined } });
24
26
  if (result.error) {
25
27
  console.warn("[useOrderPlanning] orders loaded with warning", {
26
28
  message: result.error,
@@ -48,7 +50,9 @@ export function useOrderPlanning() {
48
50
  return { orders: [], error: message };
49
51
  }
50
52
  },
51
- retry: false,
53
+ retry: 1,
54
+ staleTime: 30000,
55
+ placeholderData: (prev) => prev,
52
56
  refetchInterval: 60000,
53
57
  refetchIntervalInBackground: false,
54
58
  refetchOnWindowFocus: true,
@@ -77,6 +81,8 @@ export function useOrderPlanning() {
77
81
  });
78
82
  const { tasks: engineTasks, isLoading: tasksLoading, refetch: refetchTasks } = useEngineTasks({
79
83
  taskTypes: ["load.compose", "pick.execute"],
84
+ plannedFrom: from,
85
+ plannedTo: to,
80
86
  });
81
87
  const { composeByRef, pickByRef } = useMemo(() => {
82
88
  const compose = new Map();
@@ -155,33 +161,66 @@ export function useOrderPlanning() {
155
161
  });
156
162
  }, [ordersQuery.data, settingsQuery.data, carriersQuery.data, composeByRef, pickByRef]);
157
163
  // Orders zonder taak melden we bij de Engine; zelf indelen mag niet.
164
+ // Alleen bij succes markeren als gemeld, zodat een mislukte melding
165
+ // bij de volgende ronde opnieuw geprobeerd wordt.
158
166
  const announced = useRef(new Set());
167
+ const inFlight = useRef(new Set());
168
+ const [signalError, setSignalError] = useState(null);
159
169
  useEffect(() => {
160
- for (const item of planned) {
161
- if (!item.awaitingEngine || item.engineTask)
162
- continue;
163
- const ref = item.order.orderNumber || item.order.id;
164
- if (!ref || announced.current.has(ref))
165
- continue;
166
- announced.current.add(ref);
167
- void emitEngineEvent({
168
- eventType: "order.received",
169
- orderRef: ref,
170
- shadow: true,
171
- payload: {
172
- source: item.order.source,
173
- customerRef: item.order.customerName,
174
- deliveryDate: item.order.deliveryDate,
175
- deliveryType: item.order.deliveryType,
176
- lineCount: item.order.lineCount,
177
- hasStickers: Boolean(item.order.hasStickers),
178
- },
179
- });
180
- }
170
+ let cancelled = false;
171
+ void (async () => {
172
+ for (const item of planned) {
173
+ if (!item.awaitingEngine || item.engineTask)
174
+ continue;
175
+ const ref = item.order.orderNumber || item.order.id;
176
+ if (!ref || announced.current.has(ref) || inFlight.current.has(ref))
177
+ continue;
178
+ inFlight.current.add(ref);
179
+ const result = await emitEngineEvent({
180
+ eventType: "order.received",
181
+ orderRef: ref,
182
+ shadow: true,
183
+ payload: {
184
+ source: item.order.source,
185
+ customerRef: item.order.customerName,
186
+ deliveryDate: item.order.deliveryDate,
187
+ deliveryType: item.order.deliveryType,
188
+ lineCount: item.order.lineCount,
189
+ hasStickers: Boolean(item.order.hasStickers),
190
+ },
191
+ });
192
+ inFlight.current.delete(ref);
193
+ if (cancelled)
194
+ return;
195
+ if (result.ok) {
196
+ announced.current.add(ref);
197
+ setSignalError(null);
198
+ }
199
+ else {
200
+ setSignalError(result.error ?? "Melding aan de Logistics Engine is mislukt.");
201
+ }
202
+ }
203
+ })();
204
+ return () => {
205
+ cancelled = true;
206
+ };
181
207
  }, [planned]);
208
+ // Tijdstip van de laatste Engine-ronde: nieuwste taak die we kennen.
209
+ const lastEngineRun = useMemo(() => {
210
+ let latest = null;
211
+ for (const t of engineTasks) {
212
+ const ts = t.created_at ?? null;
213
+ if (ts && (!latest || ts > latest))
214
+ latest = ts;
215
+ }
216
+ return latest;
217
+ }, [engineTasks]);
182
218
  return {
183
219
  planned,
220
+ nearestDeliveryDate: ordersQuery.data?.nearestDeliveryDate ?? null,
184
221
  awaiting: planned.filter((p) => p.awaitingEngine),
222
+ signalError,
223
+ lastEngineRun,
185
224
  isLoading: ordersQuery.isLoading || settingsQuery.isLoading || carriersQuery.isLoading || tasksLoading,
186
225
  error: ordersQuery.data?.orders?.length ? undefined : ordersQuery.data?.error,
187
226
  warning: ordersQuery.data?.orders?.length ? ordersQuery.data?.error : undefined,