@flowselections/floriday-afhandeling-module 1.0.30 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControlePage.js +23 -10
- package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/LeveringenPage.js +7 -24
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickAgendaView.js +22 -24
- package/dist-lib/components/afhandeling/VervoerdersPage.d.ts +8 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.js +17 -2
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +5 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +19 -29
- package/dist-lib/hooks/useEngineTasks.d.ts +2 -0
- package/dist-lib/hooks/useEngineTasks.d.ts.map +1 -1
- package/dist-lib/hooks/useEngineTasks.js +19 -4
- package/dist-lib/hooks/useOrderPlanning.d.ts +12 -1
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +65 -26
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1351 -164
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1351 -164
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1351 -164
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1360 -168
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +12 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +2614 -240
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/agendaRange.d.ts +13 -0
- package/dist-lib/lib/agendaRange.d.ts.map +1 -0
- package/dist-lib/lib/agendaRange.js +25 -0
- package/dist-lib/lib/carriers.functions.d.ts +6535 -600
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +6535 -600
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +13070 -1200
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.d.ts +1367 -166
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +86 -20
- package/dist-lib/lib/logisticsEngine/engine.d.ts +16 -3
- package/dist-lib/lib/logisticsEngine/engine.d.ts.map +1 -1
- package/dist-lib/lib/logisticsEngine/engine.js +13 -6
- package/package.json +2 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AAkHA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAgzBpE"}
|
|
@@ -1,8 +1,8 @@
|
|
|
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
3
|
import { supabase } from "../../integrations/supabase/client";
|
|
4
|
-
import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, } from "@flowselections/core";
|
|
5
|
-
import { Printer, CheckCircle2, AlertTriangle, History } from "lucide-react";
|
|
4
|
+
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
|
+
import { Printer, CheckCircle2, AlertTriangle, History, ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
6
|
import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
|
|
7
7
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
8
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -368,6 +368,16 @@ export function ControlePage({ onExit } = {}) {
|
|
|
368
368
|
const checkedCount = carts.filter((c) => checked.has(controlCartKey(c))).length;
|
|
369
369
|
return { total, printed, checked: checkedCount };
|
|
370
370
|
}, [carts, checked]);
|
|
371
|
+
// Paginering
|
|
372
|
+
const [pageSize, setPageSize] = useState(20);
|
|
373
|
+
const [page, setPage] = useState(1);
|
|
374
|
+
const pageCount = Math.max(1, Math.ceil(carts.length / pageSize));
|
|
375
|
+
const currentPage = Math.min(page, pageCount);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
if (page > pageCount)
|
|
378
|
+
setPage(pageCount);
|
|
379
|
+
}, [pageCount, page]);
|
|
380
|
+
const pagedCarts = useMemo(() => carts.slice((currentPage - 1) * pageSize, currentPage * pageSize), [carts, currentPage, pageSize]);
|
|
371
381
|
const pendingProposals = pendingProposal?.proposals ?? [];
|
|
372
382
|
const pendingBlocked = pendingProposal?.blocked ?? [];
|
|
373
383
|
const pendingNotReadyCount = pendingProposals.length + pendingBlocked.length;
|
|
@@ -403,14 +413,17 @@ export function ControlePage({ onExit } = {}) {
|
|
|
403
413
|
supabase.removeChannel(channel);
|
|
404
414
|
};
|
|
405
415
|
}, [qc]);
|
|
406
|
-
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", onClick: () => setShowPrint(true), children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), "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 === "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 })] })] }),
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
416
|
+
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", onClick: () => setShowPrint(true), children: [_jsx(Printer, { className: "h-4 w-4 mr-2" }), "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 === "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)))
|
|
417
|
+
? true
|
|
418
|
+
: carts.some((c) => checked.has(controlCartKey(c)))
|
|
419
|
+
? "indeterminate"
|
|
420
|
+
: false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Ophaaldatum" }), _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) => {
|
|
421
|
+
const k = controlCartKey(c);
|
|
422
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: (c.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtDate(c.pickupDate) }), _jsx(TableCell, { children: (c.printedCount ?? 0) > 0 ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
|
|
423
|
+
})] })] }), 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) => {
|
|
424
|
+
setPageSize(Number(v));
|
|
425
|
+
setPage(1);
|
|
426
|
+
}, 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"
|
|
414
427
|
? "Opslaan…"
|
|
415
428
|
: savingState === "saved"
|
|
416
429
|
? "Voortgang automatisch opgeslagen"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LeveringenPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/LeveringenPage.tsx"],"names":[],"mappings":"
|
|
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 {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from "react";
|
|
3
|
-
import { Button
|
|
3
|
+
import { Button } from "@flowselections/core";
|
|
4
4
|
import { WeekAgendaView } from "./WeekAgendaView";
|
|
5
5
|
import { useOrderPlanning } from "../../hooks/useOrderPlanning";
|
|
6
|
-
|
|
6
|
+
import { agendaRange, currentWeekStart } from "../../lib/agendaRange";
|
|
7
7
|
export function LeveringenPage() {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
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":"
|
|
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 {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from "react";
|
|
3
|
-
import { Button
|
|
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
|
-
|
|
7
|
+
import { agendaRange, currentWeekStart } from "../../lib/agendaRange";
|
|
8
8
|
export function PickAgendaView({ onOrderClick }) {
|
|
9
|
-
const
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
}
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Bewerken gebeurt via een searchparam (?id=<uuid>) i.p.v. een dynamisch
|
|
3
|
+
* URL-segment, zodat het ook werkt in hosts/shells die alleen statische
|
|
4
|
+
* module-routes registreren.
|
|
5
|
+
*/
|
|
6
|
+
export declare function VervoerdersPage({ carrierId }?: {
|
|
7
|
+
carrierId?: string | null;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
2
9
|
//# sourceMappingURL=VervoerdersPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"AA+BA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,+BAShF"}
|
|
@@ -9,7 +9,22 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
9
9
|
import { toServerFnError } from "../../lib/serverFnError";
|
|
10
10
|
import { listCarriers, deleteCarrier, reorderCarriers } from "../../lib/carriers.functions";
|
|
11
11
|
import { carrierLeadDaysFromMoments } from "../../lib/carrierPlanning";
|
|
12
|
-
|
|
12
|
+
import { VervoerderForm } from "./VervoerderForm";
|
|
13
|
+
/**
|
|
14
|
+
* Bewerken gebeurt via een searchparam (?id=<uuid>) i.p.v. een dynamisch
|
|
15
|
+
* URL-segment, zodat het ook werkt in hosts/shells die alleen statische
|
|
16
|
+
* module-routes registreren.
|
|
17
|
+
*/
|
|
18
|
+
export function VervoerdersPage({ carrierId } = {}) {
|
|
19
|
+
const idFromUrl = carrierId ??
|
|
20
|
+
(typeof window !== "undefined"
|
|
21
|
+
? new URLSearchParams(window.location.search).get("id")
|
|
22
|
+
: null);
|
|
23
|
+
if (idFromUrl)
|
|
24
|
+
return _jsx(VervoerderForm, { carrierId: idFromUrl });
|
|
25
|
+
return _jsx(VervoerdersLijst, {});
|
|
26
|
+
}
|
|
27
|
+
function VervoerdersLijst() {
|
|
13
28
|
const queryClient = useQueryClient();
|
|
14
29
|
const fetchList = useServerFn(listCarriers);
|
|
15
30
|
const removeCarrier = useServerFn(deleteCarrier);
|
|
@@ -54,5 +69,5 @@ export function VervoerdersPage() {
|
|
|
54
69
|
[ids[index], ids[target]] = [ids[target], ids[index]];
|
|
55
70
|
reorderMutation.mutate(ids);
|
|
56
71
|
};
|
|
57
|
-
return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Vervoerders" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer vervoerders en hun verzendtarieven per land." })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] }), _jsx("div", { className: "border rounded-md bg-card", children: isLoading ? (_jsx("div", { className: "p-12 text-center text-muted-foreground", children: "Laden..." })) : error ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsxs("div", { className: "text-destructive", children: ["Fout bij laden: ", error.message] }), _jsx(Button, { variant: "outline", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : carriers.length === 0 ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsx(Truck, { className: "w-10 h-10 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen vervoerders" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een vervoerder toe om verzendtarieven in te stellen." }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-32", children: "Prioriteit" }), _jsx(TableHead, { children: "Bedrijfsnaam" }), _jsx(TableHead, { children: "Contactpersoon" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Telefoon" }), _jsx(TableHead, { children: "Levertijd" }), _jsx(TableHead, { children: "Landen" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: carriers.map((c, index) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "w-6 text-sm font-medium tabular-nums text-muted-foreground", children: index + 1 }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, -1), disabled: index === 0 || reorderMutation.isPending, "aria-label": "Hoger", children: _jsx(ArrowUp, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, 1), disabled: index === carriers.length - 1 || reorderMutation.isPending, "aria-label": "Lager", children: _jsx(ArrowDown, { className: "w-4 h-4" }) })] }) }), _jsx(TableCell, { className: "font-medium", children: c.name }), _jsx(TableCell, { children: c.contact_person ?? "—" }), _jsx(TableCell, { children: c.email ?? "—" }), _jsx(TableCell, { children: c.phone ?? "—" }), _jsxs(TableCell, { children: [carrierLeadDaysFromMoments(c.pickup_moments), " werkdagen"] }), _jsx(TableCell, { children: c.rate_count }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { to: "/afhandeling/vervoerders
|
|
72
|
+
return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Vervoerders" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer vervoerders en hun verzendtarieven per land." })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] }), _jsx("div", { className: "border rounded-md bg-card", children: isLoading ? (_jsx("div", { className: "p-12 text-center text-muted-foreground", children: "Laden..." })) : error ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsxs("div", { className: "text-destructive", children: ["Fout bij laden: ", error.message] }), _jsx(Button, { variant: "outline", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : carriers.length === 0 ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsx(Truck, { className: "w-10 h-10 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen vervoerders" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een vervoerder toe om verzendtarieven in te stellen." }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-32", children: "Prioriteit" }), _jsx(TableHead, { children: "Bedrijfsnaam" }), _jsx(TableHead, { children: "Contactpersoon" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Telefoon" }), _jsx(TableHead, { children: "Levertijd" }), _jsx(TableHead, { children: "Landen" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: carriers.map((c, index) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "w-6 text-sm font-medium tabular-nums text-muted-foreground", children: index + 1 }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, -1), disabled: index === 0 || reorderMutation.isPending, "aria-label": "Hoger", children: _jsx(ArrowUp, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, 1), disabled: index === carriers.length - 1 || reorderMutation.isPending, "aria-label": "Lager", children: _jsx(ArrowDown, { className: "w-4 h-4" }) })] }) }), _jsx(TableCell, { className: "font-medium", children: c.name }), _jsx(TableCell, { children: c.contact_person ?? "—" }), _jsx(TableCell, { children: c.email ?? "—" }), _jsx(TableCell, { children: c.phone ?? "—" }), _jsxs(TableCell, { children: [carrierLeadDaysFromMoments(c.pickup_moments), " werkdagen"] }), _jsx(TableCell, { children: c.rate_count }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { to: "/afhandeling/vervoerders", search: { id: c.id }, children: _jsx(Pencil, { className: "w-4 h-4" }) }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDeletingId(c.id), className: "text-destructive hover:text-destructive", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }) })] }, c.id))) })] })) }), _jsx(AlertDialog, { open: !!deletingId, onOpenChange: (o) => !o && setDeletingId(null), children: _jsxs(ResponsiveAlertDialogContent, { size: "sm", children: [_jsx(ResponsiveAlertDialogHeader, { children: _jsx(AlertDialogTitle, { children: "Vervoerder verwijderen?" }) }), _jsx(ResponsiveAlertDialogBody, { children: _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan gemaakt worden. Alle verzendtarieven worden ook verwijderd." }) }), _jsxs(ResponsiveAlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => deletingId && deleteMutation.mutate(deletingId), className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Verwijderen" })] })] }) })] }));
|
|
58
73
|
}
|
|
@@ -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;
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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 (
|
|
64
|
+
if (jumpedToRef.current === jumpToDate)
|
|
58
65
|
return;
|
|
59
|
-
const
|
|
60
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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);
|
|
@@ -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;
|
|
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
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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,
|