@flowselections/floriday-afhandeling-module 1.0.9 → 1.0.11

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 (96) hide show
  1. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts +7 -0
  2. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts.map +1 -0
  3. package/dist-lib/components/afhandeling/ControleLijstPrint.js +67 -0
  4. package/dist-lib/components/afhandeling/ControlePage.d.ts +2 -0
  5. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -0
  6. package/dist-lib/components/afhandeling/ControlePage.js +456 -0
  7. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +9 -0
  8. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
  9. package/dist-lib/components/afhandeling/DagDetailDialog.js +149 -0
  10. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  11. package/dist-lib/components/afhandeling/LeveringenPage.js +11 -5
  12. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  13. package/dist-lib/components/afhandeling/PickAgendaView.js +13 -4
  14. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -1
  15. package/dist-lib/components/afhandeling/PlanningAgendaPage.js +2 -2
  16. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
  17. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
  18. package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
  19. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  20. package/dist-lib/components/afhandeling/VervoerderForm.js +62 -6
  21. package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
  22. package/dist-lib/components/afhandeling/VervoerdersPage.js +25 -6
  23. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +2 -1
  24. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  25. package/dist-lib/components/afhandeling/WeekAgendaView.js +63 -18
  26. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts +12 -0
  27. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts.map +1 -0
  28. package/dist-lib/components/afhandeling/WeekdayPicker.js +30 -0
  29. package/dist-lib/components/pickstation/PakbonTemplate.d.ts +6 -2
  30. package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
  31. package/dist-lib/components/pickstation/PakbonTemplate.js +37 -12
  32. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
  33. package/dist-lib/components/settings/AfhandelingSettingsCard.js +25 -6
  34. package/dist-lib/components/ui/responsive-dialog.d.ts +20 -0
  35. package/dist-lib/components/ui/responsive-dialog.d.ts.map +1 -0
  36. package/dist-lib/components/ui/responsive-dialog.js +44 -0
  37. package/dist-lib/hooks/useOrderPlanning.d.ts +3 -0
  38. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  39. package/dist-lib/hooks/useOrderPlanning.js +64 -12
  40. package/dist-lib/index.d.ts +1 -0
  41. package/dist-lib/index.d.ts.map +1 -1
  42. package/dist-lib/index.js +4 -2
  43. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2898 -147
  44. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  45. package/dist-lib/integrations/supabase/client.d.ts +2898 -147
  46. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.server.d.ts +2898 -147
  48. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/types.d.ts +2986 -155
  50. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  51. package/dist-lib/integrations/supabase/types.js +1 -0
  52. package/dist-lib/lib/afhandelingSettings.functions.d.ts +10666 -5151
  53. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/afhandelingSettings.functions.js +42 -2
  55. package/dist-lib/lib/carrierMatch.server.d.ts +27 -0
  56. package/dist-lib/lib/carrierMatch.server.d.ts.map +1 -0
  57. package/dist-lib/lib/carrierMatch.server.js +75 -0
  58. package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
  59. package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
  60. package/dist-lib/lib/carrierSignupMail.server.js +229 -0
  61. package/dist-lib/lib/carriers.functions.d.ts +36404 -15470
  62. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/carriers.functions.js +43 -2
  64. package/dist-lib/lib/cartAssignments.functions.d.ts +49609 -0
  65. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
  66. package/dist-lib/lib/cartAssignments.functions.js +130 -0
  67. package/dist-lib/lib/cartControl.functions.d.ts +99259 -0
  68. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -0
  69. package/dist-lib/lib/cartControl.functions.js +489 -0
  70. package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
  71. package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
  72. package/dist-lib/lib/cartMoveImpact.js +26 -0
  73. package/dist-lib/lib/cartRescheduling.d.ts +30 -0
  74. package/dist-lib/lib/cartRescheduling.d.ts.map +1 -0
  75. package/dist-lib/lib/cartRescheduling.js +71 -0
  76. package/dist-lib/lib/floriday.functions.d.ts +2902 -147
  77. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday.functions.js +178 -98
  79. package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
  80. package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
  81. package/dist-lib/lib/geo/euServiceAreas.js +277 -0
  82. package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
  83. package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
  84. package/dist-lib/lib/invalidatePlanning.js +25 -0
  85. package/dist-lib/lib/pickstationI18n.d.ts +12 -0
  86. package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
  87. package/dist-lib/lib/pickstationI18n.js +72 -0
  88. package/dist-lib/lib/pickstationOrders.server.d.ts +2 -1
  89. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
  90. package/dist-lib/lib/pickstationOrders.server.js +393 -178
  91. package/dist-lib/lib/pickstationTypes.d.ts +23 -2
  92. package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
  93. package/dist-lib/lib/planning.d.ts +41 -2
  94. package/dist-lib/lib/planning.d.ts.map +1 -1
  95. package/dist-lib/lib/planning.js +159 -36
  96. package/package.json +2 -2
@@ -0,0 +1,149 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Badge, Button, Dialog, DialogTitle, DialogDescription, Input, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
+ import { ChevronDown, ChevronRight, MoveRight, Truck } from "lucide-react";
7
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
8
+ import { isSameDay } from "../../lib/pickDate";
9
+ import { getOrderLines, moveCart as moveCartFn, } from "../../lib/cartAssignments.functions";
10
+ import { evaluateCartMove } from "../../lib/cartMoveImpact";
11
+ const NL_DATE = new Intl.DateTimeFormat("nl-NL", { dateStyle: "full" });
12
+ export function DagDetailDialog({ open, date, planned, onOpenChange }) {
13
+ if (!date)
14
+ return null;
15
+ const ordersOnDay = planned.filter((p) => {
16
+ if (!p.planning)
17
+ return false;
18
+ const assigns = p.planning.pickAssignments ?? [];
19
+ return assigns.some((a) => isSameDay(a.date, date));
20
+ });
21
+ // Synthetiseer karren — één rij per kar die op deze dag valt
22
+ const cartRows = [];
23
+ for (const p of ordersOnDay) {
24
+ const assigns = p.planning.pickAssignments ?? [];
25
+ const totalCarts = p.order.cartCount ?? 0;
26
+ if (totalCarts <= 0)
27
+ continue;
28
+ let runningIdx = 0;
29
+ for (const a of assigns) {
30
+ const onThisDay = isSameDay(a.date, date);
31
+ const cartIndexes = a.cartIndexes && a.cartIndexes.length > 0
32
+ ? a.cartIndexes
33
+ : Array.from({ length: a.cartCount }, (_, i) => runningIdx + i);
34
+ for (const idx of cartIndexes) {
35
+ if (onThisDay) {
36
+ const plantsPerCart = totalCarts > 0 ? Math.round((p.order.plantCount ?? 0) / totalCarts) : 0;
37
+ cartRows.push({
38
+ key: `${p.order.id}-${idx}`,
39
+ orderId: p.order.id,
40
+ orderSource: p.order.source,
41
+ cartIndex: idx,
42
+ cartNumber: `${p.order.orderNumber}-${String(idx + 1).padStart(2, "0")}`,
43
+ order: p,
44
+ layers: 4,
45
+ crates: 0,
46
+ plants: plantsPerCart,
47
+ occupancy: 100,
48
+ });
49
+ }
50
+ }
51
+ runningIdx += a.cartCount;
52
+ }
53
+ }
54
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(ResponsiveDialogContent, { size: "xl", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsxs(DialogTitle, { children: ["Dagdetails \u2014 ", NL_DATE.format(date)] }), _jsxs(DialogDescription, { children: [ordersOnDay.length, " bestellingen \u00B7 ", cartRows.length, " karren"] })] }), _jsx(ResponsiveDialogBody, { children: _jsxs(Tabs, { defaultValue: "bestellingen", children: [_jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "bestellingen", children: ["Bestellingen (", ordersOnDay.length, ")"] }), _jsxs(TabsTrigger, { value: "karren", children: ["Karren (", cartRows.length, ")"] })] }), _jsx(TabsContent, { value: "bestellingen", className: "mt-4", children: _jsx(BestellingenTab, { orders: ordersOnDay, date: date }) }), _jsx(TabsContent, { value: "karren", className: "mt-4", children: _jsx(KarrenTab, { carts: cartRows, currentDate: date }) })] }) }), _jsx(ResponsiveDialogFooter, { children: _jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Sluiten" }) })] }) }));
55
+ }
56
+ function BestellingenTab({ orders, date }) {
57
+ if (orders.length === 0) {
58
+ return _jsx("p", { className: "text-sm text-muted-foreground", children: "Geen bestellingen op deze dag." });
59
+ }
60
+ return (_jsx("div", { className: "space-y-3", children: orders.map((p) => {
61
+ const cartsToday = (p.planning?.pickAssignments ?? []).find((a) => isSameDay(a.date, date))?.cartCount ?? 0;
62
+ return (_jsx(OrderRow, { planned: p, cartsToday: cartsToday }, p.order.id));
63
+ }) }));
64
+ }
65
+ function OrderRow({ planned: p, cartsToday }) {
66
+ const [open, setOpen] = useState(false);
67
+ const source = p.order.source;
68
+ const fetchLines = useServerFn(getOrderLines);
69
+ const linesQuery = useQuery({
70
+ queryKey: ["order-lines", source, p.order.id],
71
+ queryFn: () => fetchLines({ data: { orderId: p.order.id, orderSource: source } }),
72
+ enabled: open,
73
+ });
74
+ return (_jsxs("div", { className: "rounded-lg border bg-card", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), className: "flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-muted/50", children: [open ? _jsx(ChevronDown, { className: "h-4 w-4" }) : _jsx(ChevronRight, { className: "h-4 w-4" }), _jsxs("div", { className: "flex-1 grid grid-cols-2 md:grid-cols-4 gap-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: p.order.orderNumber }), _jsx("div", { className: "text-xs text-muted-foreground", children: p.order.customerName })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Type" }), _jsx("div", { children: p.order.deliveryType })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Leverdatum" }), _jsx("div", { children: p.order.deliveryDate
75
+ ? new Date(p.order.deliveryDate).toLocaleDateString("nl-NL")
76
+ : "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", children: [cartsToday, " karren vandaag"] }), _jsx(Badge, { variant: "outline", children: p.order.status })] })] })] }), open && (_jsxs("div", { className: "border-t px-4 py-3 space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3 text-sm", children: [_jsx(Field, { label: "Afleverlocatie", value: "\u2014" }), _jsx(Field, { label: "Ophaallocatie (tuin)", value: "\u2014" }), _jsx(Field, { label: "Vervoerder", value: "\u2014" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Orderregels" }), linesQuery.isLoading && _jsx("p", { className: "text-xs", children: "Regels laden\u2026" }), linesQuery.data && _jsx(OrderLinesTable, { lines: linesQuery.data.lines }), linesQuery.error && _jsx("p", { className: "text-xs text-destructive", children: "Kon regels niet laden" })] })] }))] }));
77
+ }
78
+ function Field({ label, value }) {
79
+ return (_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { children: value })] }));
80
+ }
81
+ function OrderLinesTable({ lines }) {
82
+ if (lines.length === 0) {
83
+ return _jsx("p", { className: "text-xs text-muted-foreground", children: "Geen orderregels gevonden." });
84
+ }
85
+ return (_jsx("div", { className: "grid gap-2", children: lines.map((l) => (_jsxs("div", { className: "grid gap-2 rounded-md border p-2 text-sm md:grid-cols-[minmax(0,1fr)_auto_auto_auto] md:items-center", children: [_jsx("div", { className: "min-w-0 truncate font-medium", title: l.article, children: l.article }), _jsx(Metric, { label: "Planten", value: l.plants }), _jsx(Metric, { label: "Fusten", value: l.crates ?? "—" }), _jsx(Metric, { label: "Overig", value: l.otherUnits ?? "—" })] }, l.id))) }));
86
+ }
87
+ function KarrenTab({ carts, currentDate }) {
88
+ const [moveCartRow, setMoveCartRow] = useState(null);
89
+ if (carts.length === 0) {
90
+ return _jsx("p", { className: "text-sm text-muted-foreground", children: "Geen karren ingepland op deze dag." });
91
+ }
92
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid gap-3", children: carts.map((c) => (_jsx("div", { className: "rounded-md border p-3", children: _jsxs("div", { className: "grid gap-3 lg:grid-cols-[1fr_1.4fr_1fr_auto_auto] lg:items-center", children: [_jsx(Field, { label: "Karnummer", value: c.cartNumber }), _jsx(Field, { label: "Klant", value: c.order.order.customerName }), _jsx(Field, { label: "Bestelling", value: c.order.order.orderNumber }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Badge, { variant: "secondary", children: "Nog te picken" }), _jsxs(Badge, { variant: "outline", children: [c.occupancy, "%"] }), _jsxs(Badge, { variant: "outline", children: [c.layers, " lagen"] }), _jsxs(Badge, { variant: "outline", children: [c.plants, " planten"] })] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setMoveCartRow(c), children: [_jsx(MoveRight, { className: "mr-1 h-3.5 w-3.5" }), " Verplaatsen"] })] }) }, c.key))) }), _jsx(VerplaatsKarDialog, { cart: moveCartRow, currentDate: currentDate, onClose: () => setMoveCartRow(null) })] }));
93
+ }
94
+ function Metric({ label, value }) {
95
+ return (_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2 md:block md:text-right", children: [_jsx("span", { className: "text-xs text-muted-foreground md:block", children: label }), _jsx("span", { className: "font-medium", children: value })] }));
96
+ }
97
+ function VerplaatsKarDialog({ cart, currentDate, onClose, }) {
98
+ const [newDate, setNewDate] = useState("");
99
+ const move = useServerFn(moveCartFn);
100
+ const qc = useQueryClient();
101
+ const mutation = useMutation({
102
+ mutationFn: async () => {
103
+ if (!cart)
104
+ throw new Error("Geen kar geselecteerd");
105
+ if (!newDate)
106
+ throw new Error("Kies eerst een nieuwe pickdatum");
107
+ return move({
108
+ data: {
109
+ orderId: cart.orderId,
110
+ orderSource: cart.orderSource,
111
+ cartIndex: cart.cartIndex,
112
+ newDate,
113
+ fromDate: currentDate,
114
+ },
115
+ });
116
+ },
117
+ onSuccess: async (res) => {
118
+ const target = new Date(newDate).toLocaleDateString("nl-NL", {
119
+ weekday: "long", day: "numeric", month: "long",
120
+ });
121
+ toast.success(`Kar ${cart?.cartNumber ?? ""} verplaatst naar ${target}`);
122
+ qc.setQueryData(["cart-overrides"], (current) => {
123
+ const overrides = current?.overrides ?? [];
124
+ const next = overrides.filter((o) => !(o.order_id === res.override.order_id &&
125
+ o.order_source === res.override.order_source &&
126
+ o.cart_index === res.override.cart_index));
127
+ return { overrides: [...next, res.override] };
128
+ });
129
+ await Promise.all([
130
+ qc.invalidateQueries({ queryKey: ["floriday", "orders"] }),
131
+ qc.invalidateQueries({ queryKey: ["cart-overrides"] }),
132
+ ]);
133
+ await qc.refetchQueries({ queryKey: ["cart-overrides"], type: "active" });
134
+ setNewDate("");
135
+ onClose();
136
+ return res;
137
+ },
138
+ onError: (e) => {
139
+ const msg = e instanceof Error ? e.message : "Verplaatsen mislukt";
140
+ toast.error(msg);
141
+ console.error("[moveCart]", e);
142
+ },
143
+ });
144
+ if (!cart)
145
+ return null;
146
+ const parsed = newDate ? new Date(newDate) : null;
147
+ const impact = parsed ? evaluateCartMove(parsed, currentDate, cart.order.planning) : null;
148
+ return (_jsx(Dialog, { open: !!cart, onOpenChange: (o) => !o && onClose(), children: _jsxs(ResponsiveDialogContent, { size: "sm", children: [_jsx(ResponsiveDialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(Truck, { className: "h-4 w-4" }), " Kar verplaatsen"] }) }), _jsxs(ResponsiveDialogBody, { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "rounded-md border bg-muted/40 p-3", children: [_jsx("div", { className: "font-medium", children: cart.cartNumber }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [cart.order.order.customerName, " \u00B7 ", cart.order.order.orderNumber] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-xs text-muted-foreground", children: "Nieuwe pickdatum" }), _jsx(Input, { type: "date", value: newDate, onChange: (e) => setNewDate(e.target.value) })] }), impact && (_jsxs("div", { className: "space-y-1", children: [impact.blockers.map((b, i) => (_jsxs("p", { className: "text-xs text-destructive", children: ["\u26A0 ", b] }, `b${i}`))), impact.warnings.map((w, i) => (_jsxs("p", { className: "text-xs text-amber-600", children: ["! ", w] }, `w${i}`))), impact.ok && impact.warnings.length === 0 && (_jsx("p", { className: "text-xs text-green-600", children: "\u2713 Verplaatsing is logistiek haalbaar." }))] }))] }), _jsxs(ResponsiveDialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: onClose, children: "Annuleren" }), _jsx(Button, { onClick: () => mutation.mutate(), disabled: !newDate || (impact ? !impact.ok : false) || mutation.isPending, children: mutation.isPending ? "Bezig…" : "Bevestig verplaatsing" })] })] }) }));
149
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"LeveringenPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/LeveringenPage.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,cAAc,gCA+F7B"}
1
+ {"version":3,"file":"LeveringenPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/LeveringenPage.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,cAAc,gCA2G7B"}
@@ -1,13 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Link } from "@tanstack/react-router";
3
2
  import { useMemo, useState } from "react";
4
- import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsContent, TabsList, TabsTrigger, } from "@flowselections/core";
5
- import { Plus } from "lucide-react";
3
+ import { useQueryClient } from "@tanstack/react-query";
4
+ import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
5
+ import { RefreshCw } from "lucide-react";
6
6
  import { WeekAgendaView } from "./WeekAgendaView";
7
7
  import { useOrderPlanning } from "../../hooks/useOrderPlanning";
8
+ import { invalidatePlanning } from "../../lib/invalidatePlanning";
8
9
  const ALL = "__all__";
9
10
  export function LeveringenPage() {
10
- const { planned, isLoading, error, refetch } = useOrderPlanning();
11
+ const { planned, isLoading, error, warning, refetch } = useOrderPlanning();
12
+ const qc = useQueryClient();
13
+ const handleRecalc = async () => {
14
+ await invalidatePlanning(qc);
15
+ toast.success("Planning herberekend met actuele instellingen");
16
+ };
11
17
  const [customer, setCustomer] = useState(ALL);
12
18
  const [deliveryType, setDeliveryType] = useState(ALL);
13
19
  const [dateFrom, setDateFrom] = useState("");
@@ -27,5 +33,5 @@ export function LeveringenPage() {
27
33
  return true;
28
34
  });
29
35
  }, [planned, customer, deliveryType, dateFrom, dateTo]);
30
- return (_jsx("main", { className: "p-6 space-y-6", children: _jsxs(Tabs, { defaultValue: "overzicht", className: "space-y-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "overzicht", children: "Overzicht" }), _jsx(TabsTrigger, { value: "veilbrieven", children: "Veilbrieven" })] }), _jsxs("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))] })] })] })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/leveringen/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Levering aanmaken"] }) })] }), _jsx(TabsContent, { value: "overzicht", className: "space-y-3", children: 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 })) }), _jsx(TabsContent, { value: "veilbrieven", children: _jsx("div", { className: "rounded-md border border-dashed p-10 text-center text-sm text-muted-foreground", children: "Veilbrieven volgen in een volgende iteratie." }) })] }) }));
36
+ return (_jsx("main", { className: "p-6 space-y-6", children: _jsxs(Tabs, { defaultValue: "overzicht", className: "space-y-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "overzicht", children: "Overzicht" }), _jsx(TabsTrigger, { value: "veilbrieven", children: "Veilbrieven" })] }), _jsxs("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))] })] })] })] }), _jsx("div", { className: "flex items-end gap-2", children: _jsxs(Button, { variant: "outline", onClick: handleRecalc, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Herbereken planning"] }) })] }), _jsxs(TabsContent, { value: "overzicht", className: "space-y-3", 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: filtered, mode: "delivery", isLoading: isLoading }))] }), _jsx(TabsContent, { value: "veilbrieven", children: _jsx("div", { className: "rounded-md border border-dashed p-10 text-center text-sm text-muted-foreground", children: "Veilbrieven volgen in een volgende iteratie." }) })] }) }));
31
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAkBA,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,+BA6FnE"}
1
+ {"version":3,"file":"PickAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickAgendaView.tsx"],"names":[],"mappings":"AAsBA,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,+BAkHnE"}
@@ -1,13 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Link } from "@tanstack/react-router";
3
2
  import { useMemo, useState } from "react";
3
+ import { useQueryClient } from "@tanstack/react-query";
4
4
  import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsContent, TabsList, TabsTrigger, } from "@flowselections/core";
5
- import { Plus } from "lucide-react";
5
+ import { RefreshCw } from "lucide-react";
6
+ import { toast } from "@flowselections/core";
6
7
  import { WeekAgendaView } from "./WeekAgendaView";
8
+ import { DagDetailDialog } from "./DagDetailDialog";
7
9
  import { useOrderPlanning } from "../../hooks/useOrderPlanning";
10
+ import { invalidatePlanning } from "../../lib/invalidatePlanning";
8
11
  const ALL = "__all__";
9
12
  export function PickAgendaView({ onOrderClick }) {
10
- const { planned, isLoading, error, refetch } = useOrderPlanning();
13
+ const { planned, isLoading, error, warning, refetch } = useOrderPlanning();
14
+ const qc = useQueryClient();
15
+ const [dayDetailDate, setDayDetailDate] = useState(null);
16
+ const handleRecalc = async () => {
17
+ await invalidatePlanning(qc);
18
+ toast.success("Planning herberekend met actuele instellingen");
19
+ };
11
20
  const [customer, setCustomer] = useState(ALL);
12
21
  const [deliveryType, setDeliveryType] = useState(ALL);
13
22
  const [dateFrom, setDateFrom] = useState("");
@@ -27,5 +36,5 @@ export function PickAgendaView({ onOrderClick }) {
27
36
  return true;
28
37
  });
29
38
  }, [planned, customer, deliveryType, dateFrom, dateTo]);
30
- return (_jsxs(Tabs, { defaultValue: "overzicht", className: "space-y-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "overzicht", children: "Overzicht" }), _jsx(TabsTrigger, { value: "veilbrieven", children: "Veilbrieven" })] }), _jsxs("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))] })] })] })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/leveringen/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Levering aanmaken"] }) })] }), _jsx(TabsContent, { value: "overzicht", className: "space-y-3", children: 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 })) }), _jsx(TabsContent, { value: "veilbrieven", children: _jsx("div", { className: "rounded-md border border-dashed p-10 text-center text-sm text-muted-foreground", children: "Veilbrieven volgen in een volgende iteratie." }) })] }));
39
+ return (_jsxs(Tabs, { defaultValue: "overzicht", className: "space-y-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "overzicht", children: "Overzicht" }), _jsx(TabsTrigger, { value: "veilbrieven", children: "Veilbrieven" })] }), _jsxs("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))] })] })] })] }), _jsx("div", { className: "flex items-end gap-2", children: _jsxs(Button, { variant: "outline", onClick: handleRecalc, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Herbereken planning"] }) })] }), _jsxs(TabsContent, { value: "overzicht", className: "space-y-3", 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: filtered, mode: "pick", isLoading: isLoading, onOrderClick: onOrderClick, onDayClick: setDayDetailDate }))] }), _jsx(TabsContent, { value: "veilbrieven", children: _jsx("div", { className: "rounded-md border border-dashed p-10 text-center text-sm text-muted-foreground", children: "Veilbrieven volgen in een volgende iteratie." }) }), _jsx(DagDetailDialog, { open: !!dayDetailDate, date: dayDetailDate, planned: filtered, onOpenChange: (o) => !o && setDayDetailDate(null) })] }));
31
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlanningAgendaPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PlanningAgendaPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvE,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,+BAgBrE"}
1
+ {"version":3,"file":"PlanningAgendaPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PlanningAgendaPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvE,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,+BAqBrE"}
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { WeekAgendaView } from "./WeekAgendaView";
3
3
  import { useOrderPlanning } from "../../hooks/useOrderPlanning";
4
4
  export function PlanningAgendaPage({ title, description, mode }) {
5
- const { planned, isLoading, error } = useOrderPlanning();
6
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), error && (_jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive", children: error })), _jsx(WeekAgendaView, { planned: planned, mode: mode, isLoading: isLoading })] }));
5
+ const { planned, isLoading, error, warning } = useOrderPlanning();
6
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), error && (_jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive", children: error })), warning && !error && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-sm text-amber-900", children: [warning, " De beschikbare orders blijven zichtbaar."] })), _jsx(WeekAgendaView, { planned: planned, mode: mode, isLoading: isLoading })] }));
7
7
  }
@@ -0,0 +1,12 @@
1
+ export interface ServiceAreaValue {
2
+ countries: string[];
3
+ provinces: string[];
4
+ cities: string[];
5
+ }
6
+ interface Props {
7
+ value: ServiceAreaValue;
8
+ onChange: (next: ServiceAreaValue) => void;
9
+ }
10
+ export declare function ServiceAreaSelect({ value, onChange }: Props): import("react").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=ServiceAreaSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServiceAreaSelect.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ServiceAreaSelect.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,UAAU,KAAK;IACb,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC5C;AAiCD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAwH3D"}
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Popover, PopoverContent, PopoverTrigger, Badge, cn } from "@flowselections/core";
4
+ import { ChevronDown, Search, X, Check, Globe, MapPin, Building2 } from "lucide-react";
5
+ import { GEO_ITEMS, getGeoItem } from "../../lib/geo/euServiceAreas";
6
+ const LEVEL_META = {
7
+ country: { label: "Land", icon: Globe, tone: "bg-blue-100 text-blue-700" },
8
+ province: { label: "Provincie", icon: MapPin, tone: "bg-emerald-100 text-emerald-700" },
9
+ city: { label: "Plaats", icon: Building2, tone: "bg-amber-100 text-amber-700" },
10
+ };
11
+ function isSelected(value, item) {
12
+ if (item.level === "country")
13
+ return value.countries.includes(item.code);
14
+ if (item.level === "province")
15
+ return value.provinces.includes(item.code);
16
+ return value.cities.includes(item.code);
17
+ }
18
+ function toggle(value, item) {
19
+ const has = isSelected(value, item);
20
+ if (item.level === "country") {
21
+ return { ...value, countries: has ? value.countries.filter((c) => c !== item.code) : [...value.countries, item.code] };
22
+ }
23
+ if (item.level === "province") {
24
+ return { ...value, provinces: has ? value.provinces.filter((c) => c !== item.code) : [...value.provinces, item.code] };
25
+ }
26
+ return { ...value, cities: has ? value.cities.filter((c) => c !== item.code) : [...value.cities, item.code] };
27
+ }
28
+ function removeCode(value, code) {
29
+ return {
30
+ countries: value.countries.filter((c) => c !== code),
31
+ provinces: value.provinces.filter((c) => c !== code),
32
+ cities: value.cities.filter((c) => c !== code),
33
+ };
34
+ }
35
+ export function ServiceAreaSelect({ value, onChange }) {
36
+ const [open, setOpen] = useState(false);
37
+ const [query, setQuery] = useState("");
38
+ const filtered = useMemo(() => {
39
+ const q = query.trim().toLowerCase();
40
+ const items = q
41
+ ? GEO_ITEMS.filter((i) => i.label.toLowerCase().includes(q) || i.code.toLowerCase().includes(q))
42
+ : GEO_ITEMS;
43
+ const byLevel = { country: [], province: [], city: [] };
44
+ for (const i of items)
45
+ byLevel[i.level].push(i);
46
+ for (const k of Object.keys(byLevel)) {
47
+ byLevel[k].sort((a, b) => a.label.localeCompare(b.label, "nl"));
48
+ }
49
+ // cap city list to keep the popover snappy when no query
50
+ if (!q)
51
+ byLevel.city = byLevel.city.slice(0, 60);
52
+ return byLevel;
53
+ }, [query]);
54
+ const selectedItems = useMemo(() => {
55
+ const all = [...value.countries, ...value.provinces, ...value.cities];
56
+ return all
57
+ .map((c) => getGeoItem(c) ?? { code: c, label: c, level: "country", country: c })
58
+ .sort((a, b) => {
59
+ const order = { country: 0, province: 1, city: 2 };
60
+ return order[a.level] - order[b.level] || a.label.localeCompare(b.label, "nl");
61
+ });
62
+ }, [value]);
63
+ const total = selectedItems.length;
64
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center justify-between w-full min-h-10 px-3 py-2 rounded-md border bg-background text-sm", children: [_jsx("span", { className: total === 0 ? "text-muted-foreground" : "", children: total === 0 ? "Selecteer landen, provincies of plaatsen…" : `${total} gebied${total === 1 ? "" : "en"} geselecteerd` }), _jsx(ChevronDown, { className: "w-4 h-4 text-muted-foreground" })] }) }), _jsxs(PopoverContent, { className: "p-0 w-[360px]", align: "start", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [_jsx(Search, { className: "w-4 h-4 text-muted-foreground" }), _jsx("input", { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Zoek land, provincie of plaats\u2026", className: "flex-1 bg-transparent text-sm outline-none" })] }), _jsxs("div", { className: "max-h-[340px] overflow-y-auto py-1", children: [Object.keys(filtered).map((level) => {
65
+ const items = filtered[level];
66
+ if (items.length === 0)
67
+ return null;
68
+ const Meta = LEVEL_META[level];
69
+ return (_jsxs("div", { children: [_jsxs("div", { className: "px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [Meta.label, "en"] }), items.map((item) => {
70
+ const sel = isSelected(value, item);
71
+ const Icon = LEVEL_META[item.level].icon;
72
+ return (_jsxs("button", { type: "button", onClick: () => onChange(toggle(value, item)), className: cn("flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted", sel && "bg-muted/60"), children: [_jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }), _jsx("span", { className: "flex-1 truncate", children: item.label }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: item.code }), sel ? _jsx(Check, { className: "h-4 w-4 text-primary" }) : null] }, item.code));
73
+ })] }, level));
74
+ }), filtered.country.length === 0 && filtered.province.length === 0 && filtered.city.length === 0 && (_jsx("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: "Geen resultaten" }))] })] })] }), selectedItems.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedItems.map((item) => {
75
+ const meta = LEVEL_META[item.level];
76
+ const Icon = meta.icon;
77
+ return (_jsxs(Badge, { variant: "secondary", className: cn("gap-1 pr-1", meta.tone), children: [_jsx(Icon, { className: "h-3 w-3" }), _jsx("span", { children: item.label }), _jsx("button", { type: "button", onClick: () => onChange(removeCode(value, item.code)), className: "ml-1 rounded-sm hover:bg-black/10 p-0.5", "aria-label": `Verwijder ${item.label}`, children: _jsx(X, { className: "h-3 w-3" }) })] }, item.code));
78
+ }) }))] }));
79
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VervoerderForm.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerderForm.tsx"],"names":[],"mappings":"AA6CA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAiND,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BA0VlD"}
1
+ {"version":3,"file":"VervoerderForm.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerderForm.tsx"],"names":[],"mappings":"AA+CA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA2PD,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BAyalD"}
@@ -8,6 +8,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
8
  import { getCarrier, upsertCarrier, } from "../../lib/carriers.functions";
9
9
  import { DEFAULT_PICKUP_MOMENT } from "../../lib/planning";
10
10
  import { COUNTRIES, countryName } from "../../lib/countries";
11
+ import { ServiceAreaSelect } from "./ServiceAreaSelect";
12
+ import { WeekdayPicker } from "./WeekdayPicker";
11
13
  const EMPTY = {
12
14
  name: "",
13
15
  contact_person: "",
@@ -17,6 +19,11 @@ const EMPTY = {
17
19
  notes: "",
18
20
  is_default: false,
19
21
  pickup_moments: [DEFAULT_PICKUP_MOMENT],
22
+ service_countries: [],
23
+ service_provinces: [],
24
+ service_cities: [],
25
+ pickup_weekdays: [1, 2, 3, 4, 5],
26
+ delivery_weekdays: [1, 2, 3, 4, 5],
20
27
  };
21
28
  function FieldIcon({ icon: Icon }) {
22
29
  return (_jsx("div", { className: "flex items-center justify-center w-10 h-10 rounded-md bg-primary/10 text-primary shrink-0", children: _jsx(Icon, { className: "w-4 h-4" }) }));
@@ -38,11 +45,41 @@ function CountryPicker({ value, onChange, excluded, }) {
38
45
  setQuery("");
39
46
  }, className: "flex items-center justify-between w-full px-3 py-2 text-sm text-left hover:bg-muted", children: [_jsx("span", { children: c.name }), _jsx("span", { className: "text-xs text-muted-foreground", children: c.code })] }, c.code)))) })] })] }));
40
47
  }
41
- function PickupMomentRow({ label, days, time, onDaysChange, onTimeChange, }) {
42
- return (_jsxs("div", { className: "grid grid-cols-[110px,1fr,1fr] items-center gap-3", children: [_jsx("div", { className: "text-sm font-medium", children: label }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Werkdagen v\u00F3\u00F3r levering" }), _jsx(IconInput, { icon: Calendar, type: "number", min: 0, value: days, onChange: (e) => onDaysChange(Number(e.target.value) || 0) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Tijdstip" }), _jsx(IconInput, { icon: Clock, type: "time", value: time, onChange: (e) => onTimeChange(e.target.value) })] })] }));
48
+ function PickupMomentRow({ label, days, time, maxDays, onDaysChange, onTimeChange, }) {
49
+ return (_jsxs("div", { className: "grid grid-cols-[110px,1fr,1fr] items-center gap-3", children: [_jsx("div", { className: "text-sm font-medium", children: label }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Werkdagen v\u00F3\u00F3r levering" }), _jsx(IconInput, { icon: Calendar, type: "number", min: 0, max: maxDays, value: days, onChange: (e) => onDaysChange(Number(e.target.value) || 0) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Tijdstip" }), _jsx(IconInput, { icon: Clock, type: "time", value: time, onChange: (e) => onTimeChange(e.target.value) })] })] }));
50
+ }
51
+ // Normaliseer een ophaalmoment: aanmelden moet altijd vóór (of op) ophalen
52
+ // liggen, ophalen vóór (of op) bezorgen. Werkdagen-waardes zijn afstanden tot
53
+ // de bezorgdag, dus geldt: signup_days >= pickup_days >= delivery_days.
54
+ function normalizeMoment(next, prev) {
55
+ const m = { ...next };
56
+ if (next.signup_days !== prev.signup_days) {
57
+ // Aanmelden verlaagd → ophalen mag niet eerder zijn dan aanmelden
58
+ if (m.pickup_days > m.signup_days)
59
+ m.pickup_days = m.signup_days;
60
+ if (m.delivery_days > m.pickup_days)
61
+ m.delivery_days = m.pickup_days;
62
+ }
63
+ if (next.pickup_days !== prev.pickup_days) {
64
+ // Ophalen mag nooit vóór aanmelden plaatsvinden
65
+ if (m.pickup_days > m.signup_days)
66
+ m.pickup_days = m.signup_days;
67
+ if (m.delivery_days > m.pickup_days)
68
+ m.delivery_days = m.pickup_days;
69
+ }
70
+ if (next.delivery_days !== prev.delivery_days) {
71
+ if (m.delivery_days > m.pickup_days)
72
+ m.delivery_days = m.pickup_days;
73
+ }
74
+ return m;
43
75
  }
44
76
  function PickupMomentEditor({ index, value, canDelete, onChange, onDelete, }) {
45
- return (_jsxs("div", { className: "rounded-md border bg-background p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm font-semibold", children: ["Ophaalmoment ", index + 1] }), canDelete ? (_jsxs("button", { type: "button", onClick: onDelete, className: "text-xs text-muted-foreground hover:text-destructive inline-flex items-center gap-1", "aria-label": "Ophaalmoment verwijderen", children: [_jsx(Trash2, { className: "w-3.5 h-3.5" }), "Verwijderen"] })) : null] }), _jsxs("div", { className: "space-y-3", children: [_jsx(PickupMomentRow, { label: "Aanmelden", days: value.signup_days, time: value.signup_time, onDaysChange: (n) => onChange({ ...value, signup_days: n }), onTimeChange: (v) => onChange({ ...value, signup_time: v }) }), _jsx(PickupMomentRow, { label: "Ophalen", days: value.pickup_days, time: value.pickup_time, onDaysChange: (n) => onChange({ ...value, pickup_days: n }), onTimeChange: (v) => onChange({ ...value, pickup_time: v }) }), _jsx(PickupMomentRow, { label: "Bezorgen", days: value.delivery_days, time: value.delivery_time, onDaysChange: (n) => onChange({ ...value, delivery_days: n }), onTimeChange: (v) => onChange({ ...value, delivery_time: v }) })] })] }));
77
+ const update = (patch) => {
78
+ const next = { ...value, ...patch };
79
+ onChange(normalizeMoment(next, value));
80
+ };
81
+ const pickupInvalid = value.pickup_days > value.signup_days;
82
+ return (_jsxs("div", { className: "rounded-md border bg-background p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm font-semibold", children: ["Ophaalmoment ", index + 1] }), canDelete ? (_jsxs("button", { type: "button", onClick: onDelete, className: "text-xs text-muted-foreground hover:text-destructive inline-flex items-center gap-1", "aria-label": "Ophaalmoment verwijderen", children: [_jsx(Trash2, { className: "w-3.5 h-3.5" }), "Verwijderen"] })) : null] }), _jsxs("div", { className: "space-y-3", children: [_jsx(PickupMomentRow, { label: "Aanmelden", days: value.signup_days, time: value.signup_time, onDaysChange: (n) => update({ signup_days: n }), onTimeChange: (v) => update({ signup_time: v }) }), _jsx(PickupMomentRow, { label: "Ophalen", days: value.pickup_days, time: value.pickup_time, maxDays: value.signup_days, onDaysChange: (n) => update({ pickup_days: Math.min(n, value.signup_days) }), onTimeChange: (v) => update({ pickup_time: v }) }), pickupInvalid ? (_jsx("p", { className: "text-xs text-destructive -mt-1 pl-[122px]", children: "Ophalen kan niet v\u00F3\u00F3r aanmelden plaatsvinden." })) : null, _jsx(PickupMomentRow, { label: "Bezorgen", days: value.delivery_days, time: value.delivery_time, maxDays: value.pickup_days, onDaysChange: (n) => update({ delivery_days: Math.min(n, value.pickup_days) }), onTimeChange: (v) => update({ delivery_time: v }) })] })] }));
46
83
  }
47
84
  export function VervoerderForm({ carrierId }) {
48
85
  const isEdit = !!carrierId;
@@ -74,6 +111,15 @@ export function VervoerderForm({ carrierId }) {
74
111
  pickup_moments: c.pickup_moments && c.pickup_moments.length > 0
75
112
  ? c.pickup_moments
76
113
  : [DEFAULT_PICKUP_MOMENT],
114
+ service_countries: c.service_countries ?? [],
115
+ service_provinces: c.service_provinces ?? [],
116
+ service_cities: c.service_cities ?? [],
117
+ pickup_weekdays: Array.isArray(c.pickup_weekdays) && c.pickup_weekdays.length > 0
118
+ ? c.pickup_weekdays
119
+ : [1, 2, 3, 4, 5],
120
+ delivery_weekdays: Array.isArray(c.delivery_weekdays) && c.delivery_weekdays.length > 0
121
+ ? c.delivery_weekdays
122
+ : [1, 2, 3, 4, 5],
77
123
  });
78
124
  setRates(c.rates);
79
125
  }
@@ -92,9 +138,10 @@ export function VervoerderForm({ carrierId }) {
92
138
  throw e;
93
139
  }
94
140
  },
95
- onSuccess: () => {
141
+ onSuccess: async () => {
96
142
  toast.success(isEdit ? "Vervoerder bijgewerkt" : "Vervoerder aangemaakt");
97
- queryClient.invalidateQueries({ queryKey: ["carriers"] });
143
+ const { invalidatePlanning } = await import("../../lib/invalidatePlanning");
144
+ await invalidatePlanning(queryClient);
98
145
  if (carrierId)
99
146
  queryClient.invalidateQueries({ queryKey: ["carrier", carrierId] });
100
147
  goBack();
@@ -143,7 +190,16 @@ export function VervoerderForm({ carrierId }) {
143
190
  } }, idx))) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setForm({
144
191
  ...form,
145
192
  pickup_moments: [...form.pickup_moments, { ...DEFAULT_PICKUP_MOMENT }],
146
- }), children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Ophaalmoment toevoegen"] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: form.is_default ?? false, onChange: (e) => setForm({ ...form, is_default: e.target.checked }), className: "h-4 w-4 rounded border" }), _jsxs("span", { children: ["Gebruik als ", _jsx("strong", { children: "standaard vervoerder" }), " voor de automatische planning (voor orders zonder eigen vervoerder)."] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Opmerkingen" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(FieldIcon, { icon: FileText }), _jsx(Textarea, { value: form.notes ?? "", onChange: (e) => setForm({ ...form, notes: e.target.value }), placeholder: "Extra informatie over deze vervoerder...", rows: 3, className: "flex-1" })] })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Verzendtarieven" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stel de verzendkosten ", _jsx("strong", { children: "per kwekerskar" }), " in voor elk land waar deze vervoerder naartoe verzendt."] })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-[1fr,160px,auto] gap-3", children: [_jsx(CountryPicker, { value: pendingCountry, onChange: setPendingCountry, excluded: rates.map((r) => r.country_code) }), _jsxs("div", { className: "flex items-stretch gap-2 rounded-md border bg-background h-10", children: [_jsx("div", { className: "flex items-center justify-center w-10 bg-primary/10 text-primary rounded-l-md text-sm font-medium", children: "\u20AC" }), _jsx("input", { type: "number", step: "0.01", min: 0, value: pendingPrice, onChange: (e) => setPendingPrice(e.target.value), placeholder: "Prijs", className: "flex-1 bg-transparent text-sm outline-none pr-3" })] }), _jsxs(Button, { type: "button", onClick: addRate, children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Toevoegen"] })] }), rates.length === 0 ? (_jsxs("div", { className: "border rounded-md py-10 text-center space-y-2", children: [_jsx(Globe, { className: "w-8 h-8 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen landen toegevoegd" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een land toe om verzendtarieven in te stellen." })] })) : (_jsx("div", { className: "border rounded-md divide-y", children: rates
193
+ }), children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Ophaalmoment toevoegen"] })] }), _jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "w-4 h-4 text-primary" }), _jsx(Label, { className: "font-semibold", children: "Ophaaldagen" })] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Dagen waarop deze vervoerder karren bij ons komt ophalen." }), _jsx("div", { className: "mt-3", children: _jsx(WeekdayPicker, { value: form.pickup_weekdays ?? [1, 2, 3, 4, 5], onChange: (next) => setForm({ ...form, pickup_weekdays: next }), idPrefix: "carrier-pu-wd" }) })] }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Truck, { className: "w-4 h-4 text-primary" }), _jsx(Label, { className: "font-semibold", children: "Bezorgdagen" })] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Dagen waarop deze vervoerder bij de klant aflevert." }), _jsx("div", { className: "mt-3", children: _jsx(WeekdayPicker, { value: form.delivery_weekdays ?? [1, 2, 3, 4, 5], onChange: (next) => setForm({ ...form, delivery_weekdays: next }), idPrefix: "carrier-dl-wd" }) })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: form.is_default ?? false, onChange: (e) => setForm({ ...form, is_default: e.target.checked }), className: "h-4 w-4 rounded border" }), _jsxs("span", { children: ["Gebruik als ", _jsx("strong", { children: "standaard vervoerder" }), " voor de automatische planning (voor orders zonder eigen vervoerder)."] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Opmerkingen" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(FieldIcon, { icon: FileText }), _jsx(Textarea, { value: form.notes ?? "", onChange: (e) => setForm({ ...form, notes: e.target.value }), placeholder: "Extra informatie over deze vervoerder...", rows: 3, className: "flex-1" })] })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Bedieningsgebied" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Selecteer de landen, provincies en/of plaatsen waar deze vervoerder actief is. Een land dekt automatisch al zijn provincies en plaatsen." })] }), _jsx(CardContent, { children: _jsx(ServiceAreaSelect, { value: {
194
+ countries: form.service_countries ?? [],
195
+ provinces: form.service_provinces ?? [],
196
+ cities: form.service_cities ?? [],
197
+ }, onChange: (next) => setForm({
198
+ ...form,
199
+ service_countries: next.countries,
200
+ service_provinces: next.provinces,
201
+ service_cities: next.cities,
202
+ }) }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Verzendtarieven" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stel de verzendkosten ", _jsx("strong", { children: "per kwekerskar" }), " in voor elk land waar deze vervoerder naartoe verzendt."] })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-[1fr,160px,auto] gap-3", children: [_jsx(CountryPicker, { value: pendingCountry, onChange: setPendingCountry, excluded: rates.map((r) => r.country_code) }), _jsxs("div", { className: "flex items-stretch gap-2 rounded-md border bg-background h-10", children: [_jsx("div", { className: "flex items-center justify-center w-10 bg-primary/10 text-primary rounded-l-md text-sm font-medium", children: "\u20AC" }), _jsx("input", { type: "number", step: "0.01", min: 0, value: pendingPrice, onChange: (e) => setPendingPrice(e.target.value), placeholder: "Prijs", className: "flex-1 bg-transparent text-sm outline-none pr-3" })] }), _jsxs(Button, { type: "button", onClick: addRate, children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Toevoegen"] })] }), rates.length === 0 ? (_jsxs("div", { className: "border rounded-md py-10 text-center space-y-2", children: [_jsx(Globe, { className: "w-8 h-8 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen landen toegevoegd" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een land toe om verzendtarieven in te stellen." })] })) : (_jsx("div", { className: "border rounded-md divide-y", children: rates
147
203
  .slice()
148
204
  .sort((a, b) => countryName(a.country_code).localeCompare(countryName(b.country_code), "nl"))
149
205
  .map((r) => (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx(Globe, { className: "w-4 h-4 text-muted-foreground" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: countryName(r.country_code) }), _jsx("div", { className: "text-xs text-muted-foreground", children: r.country_code })] }), _jsxs("div", { className: "text-sm font-medium", children: ["\u20AC ", r.price_per_trolley.toFixed(2), " ", _jsx("span", { className: "text-muted-foreground font-normal", children: "/ kar" })] }), _jsx("button", { type: "button", onClick: () => removeRate(r.country_code), className: "text-muted-foreground hover:text-destructive", "aria-label": "Verwijderen", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }, r.country_code))) })), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["De prijs per kwekerskar wordt automatisch berekend", " ", _jsx("strong", { children: "bij het aanmaken van een bestelling" }), "."] })] })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", onClick: goBack, children: [_jsx(X, { className: "w-4 h-4 mr-1" }), "Annuleren"] }), _jsx(Button, { type: "submit", disabled: mutation.isPending, children: mutation.isPending ? "Opslaan..." : "Opslaan" })] })] })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,eAAe,gCAgJ9B"}
1
+ {"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,eAAe,gCA+L9B"}