@flowselections/floriday-afhandeling-module 1.0.8 → 1.0.10
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/DagDetailDialog.d.ts +9 -0
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/DagDetailDialog.js +145 -0
- package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/LeveringenPage.js +17 -69
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts +6 -0
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/PickAgendaView.js +40 -0
- package/dist-lib/components/afhandeling/PickingPage.d.ts +2 -0
- package/dist-lib/components/afhandeling/PickingPage.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/PickingPage.js +5 -0
- package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts +9 -0
- package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/PlanningAgendaPage.js +7 -0
- package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
- package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
- package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerderForm.js +73 -12
- package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/VervoerdersPage.js +23 -5
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +13 -0
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/WeekAgendaView.js +142 -0
- package/dist-lib/components/pickstation/LanguagePicker.d.ts +5 -0
- package/dist-lib/components/pickstation/LanguagePicker.d.ts.map +1 -0
- package/dist-lib/components/pickstation/LanguagePicker.js +5 -0
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts +20 -0
- package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -0
- package/dist-lib/components/pickstation/PakbonTemplate.js +76 -0
- package/dist-lib/components/pickstation/printDocuments.d.ts +3 -0
- package/dist-lib/components/pickstation/printDocuments.d.ts.map +1 -0
- package/dist-lib/components/pickstation/printDocuments.js +53 -0
- package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/AfhandelingSettingsCard.js +72 -0
- package/dist-lib/components/settings/VervoerdersSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/VervoerdersSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/VervoerdersSettingsCard.js +30 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts +17 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderPlanning.js +131 -0
- package/dist-lib/index.d.ts +5 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +16 -6
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2793 -137
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +2793 -137
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2793 -137
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2870 -131
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +2 -0
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +18597 -0
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -0
- package/dist-lib/lib/afhandelingSettings.functions.js +98 -0
- package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
- package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
- package/dist-lib/lib/carrierSignupMail.server.js +229 -0
- package/dist-lib/lib/carriers.functions.d.ts +34091 -14161
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/carriers.functions.js +69 -6
- package/dist-lib/lib/cartAssignments.functions.d.ts +46479 -0
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
- package/dist-lib/lib/cartAssignments.functions.js +130 -0
- package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
- package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
- package/dist-lib/lib/cartMoveImpact.js +26 -0
- package/dist-lib/lib/floriday.functions.d.ts +2798 -137
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +178 -93
- package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
- package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
- package/dist-lib/lib/geo/euServiceAreas.js +277 -0
- package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
- package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
- package/dist-lib/lib/invalidatePlanning.js +25 -0
- package/dist-lib/lib/pickDate.d.ts +21 -0
- package/dist-lib/lib/pickDate.d.ts.map +1 -0
- package/dist-lib/lib/pickDate.js +71 -0
- package/dist-lib/lib/pickstationI18n.d.ts +35 -0
- package/dist-lib/lib/pickstationI18n.d.ts.map +1 -0
- package/dist-lib/lib/pickstationI18n.js +171 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts +9 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -0
- package/dist-lib/lib/pickstationOrders.server.js +372 -0
- package/dist-lib/lib/pickstationTypes.d.ts +26 -0
- package/dist-lib/lib/pickstationTypes.d.ts.map +1 -0
- package/dist-lib/lib/pickstationTypes.js +1 -0
- package/dist-lib/lib/planning.d.ts +82 -0
- package/dist-lib/lib/planning.d.ts.map +1 -0
- package/dist-lib/lib/planning.js +243 -0
- package/package.json +12 -6
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlannedOrder } from "../../hooks/useOrderPlanning";
|
|
2
|
+
export interface DagDetailDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
date: Date | null;
|
|
5
|
+
planned: PlannedOrder[];
|
|
6
|
+
onOpenChange: (open: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function DagDetailDialog({ open, date, planned, onOpenChange }: DagDetailDialogProps): import("react").JSX.Element | null;
|
|
9
|
+
//# sourceMappingURL=DagDetailDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAyBjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAID,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,oBAAoB,sCAyE1F"}
|
|
@@ -0,0 +1,145 @@
|
|
|
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, DialogContent, DialogHeader, DialogTitle, Input, Tabs, TabsContent, TabsList, TabsTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
|
|
6
|
+
import { ChevronDown, ChevronRight, MoveRight, Truck } from "lucide-react";
|
|
7
|
+
import { isSameDay } from "../../lib/pickDate";
|
|
8
|
+
import { getOrderLines, moveCart as moveCartFn, } from "../../lib/cartAssignments.functions";
|
|
9
|
+
import { evaluateCartMove } from "../../lib/cartMoveImpact";
|
|
10
|
+
const NL_DATE = new Intl.DateTimeFormat("nl-NL", { dateStyle: "full" });
|
|
11
|
+
export function DagDetailDialog({ open, date, planned, onOpenChange }) {
|
|
12
|
+
if (!date)
|
|
13
|
+
return null;
|
|
14
|
+
const ordersOnDay = planned.filter((p) => {
|
|
15
|
+
if (!p.planning)
|
|
16
|
+
return false;
|
|
17
|
+
const assigns = p.planning.pickAssignments ?? [];
|
|
18
|
+
return assigns.some((a) => isSameDay(a.date, date));
|
|
19
|
+
});
|
|
20
|
+
// Synthetiseer karren — één rij per kar die op deze dag valt
|
|
21
|
+
const cartRows = [];
|
|
22
|
+
for (const p of ordersOnDay) {
|
|
23
|
+
const assigns = p.planning.pickAssignments ?? [];
|
|
24
|
+
const totalCarts = p.order.cartCount ?? 0;
|
|
25
|
+
if (totalCarts <= 0)
|
|
26
|
+
continue;
|
|
27
|
+
let runningIdx = 0;
|
|
28
|
+
for (const a of assigns) {
|
|
29
|
+
const onThisDay = isSameDay(a.date, date);
|
|
30
|
+
const cartIndexes = a.cartIndexes && a.cartIndexes.length > 0
|
|
31
|
+
? a.cartIndexes
|
|
32
|
+
: Array.from({ length: a.cartCount }, (_, i) => runningIdx + i);
|
|
33
|
+
for (const idx of cartIndexes) {
|
|
34
|
+
if (onThisDay) {
|
|
35
|
+
const plantsPerCart = totalCarts > 0 ? Math.round((p.order.plantCount ?? 0) / totalCarts) : 0;
|
|
36
|
+
cartRows.push({
|
|
37
|
+
key: `${p.order.id}-${idx}`,
|
|
38
|
+
orderId: p.order.id,
|
|
39
|
+
orderSource: p.order.source,
|
|
40
|
+
cartIndex: idx,
|
|
41
|
+
cartNumber: `${p.order.orderNumber}-${String(idx + 1).padStart(2, "0")}`,
|
|
42
|
+
order: p,
|
|
43
|
+
layers: 4,
|
|
44
|
+
crates: 0,
|
|
45
|
+
plants: plantsPerCart,
|
|
46
|
+
occupancy: 100,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
runningIdx += a.cartCount;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-5xl max-h-[90vh] overflow-y-auto", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { children: ["Dagdetails \u2014 ", NL_DATE.format(date)] }) }), _jsxs("div", { className: "flex flex-wrap gap-3 text-sm text-muted-foreground", children: [_jsxs("span", { children: [ordersOnDay.length, " bestellingen"] }), _jsx("span", { children: "\u00B7" }), _jsxs("span", { children: [cartRows.length, " karren"] })] }), _jsxs(Tabs, { defaultValue: "bestellingen", className: "mt-4", 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 }) })] })] }) }));
|
|
54
|
+
}
|
|
55
|
+
function BestellingenTab({ orders, date }) {
|
|
56
|
+
if (orders.length === 0) {
|
|
57
|
+
return _jsx("p", { className: "text-sm text-muted-foreground", children: "Geen bestellingen op deze dag." });
|
|
58
|
+
}
|
|
59
|
+
return (_jsx("div", { className: "space-y-3", children: orders.map((p) => {
|
|
60
|
+
const cartsToday = (p.planning?.pickAssignments ?? []).find((a) => isSameDay(a.date, date))?.cartCount ?? 0;
|
|
61
|
+
return (_jsx(OrderRow, { planned: p, cartsToday: cartsToday }, p.order.id));
|
|
62
|
+
}) }));
|
|
63
|
+
}
|
|
64
|
+
function OrderRow({ planned: p, cartsToday }) {
|
|
65
|
+
const [open, setOpen] = useState(false);
|
|
66
|
+
const source = p.order.source;
|
|
67
|
+
const fetchLines = useServerFn(getOrderLines);
|
|
68
|
+
const linesQuery = useQuery({
|
|
69
|
+
queryKey: ["order-lines", source, p.order.id],
|
|
70
|
+
queryFn: () => fetchLines({ data: { orderId: p.order.id, orderSource: source } }),
|
|
71
|
+
enabled: open,
|
|
72
|
+
});
|
|
73
|
+
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
|
|
74
|
+
? new Date(p.order.deliveryDate).toLocaleDateString("nl-NL")
|
|
75
|
+
: "—" })] }), _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" })] })] }))] }));
|
|
76
|
+
}
|
|
77
|
+
function Field({ label, value }) {
|
|
78
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { children: value })] }));
|
|
79
|
+
}
|
|
80
|
+
function OrderLinesTable({ lines }) {
|
|
81
|
+
if (lines.length === 0) {
|
|
82
|
+
return _jsx("p", { className: "text-xs text-muted-foreground", children: "Geen orderregels gevonden." });
|
|
83
|
+
}
|
|
84
|
+
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Planten" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Overig" })] }) }), _jsx(TableBody, { children: lines.map((l) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: l.article }), _jsx(TableCell, { className: "text-right", children: l.plants }), _jsx(TableCell, { className: "text-right", children: l.crates ?? "—" }), _jsx(TableCell, { className: "text-right", children: l.otherUnits ?? "—" })] }, l.id))) })] }));
|
|
85
|
+
}
|
|
86
|
+
function KarrenTab({ carts, currentDate }) {
|
|
87
|
+
const [moveCartRow, setMoveCartRow] = useState(null);
|
|
88
|
+
if (carts.length === 0) {
|
|
89
|
+
return _jsx("p", { className: "text-sm text-muted-foreground", children: "Geen karren ingepland op deze dag." });
|
|
90
|
+
}
|
|
91
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Karnummer" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Bestelling" }), _jsx(TableHead, { children: "Tuin" }), _jsx(TableHead, { children: "Vervoerder" }), _jsx(TableHead, { className: "text-right", children: "Bezetting" }), _jsx(TableHead, { className: "text-right", children: "Lagen" }), _jsx(TableHead, { className: "text-right", children: "Planten" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Actie" })] }) }), _jsx(TableBody, { children: carts.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: c.cartNumber }), _jsx(TableCell, { children: c.order.order.customerName }), _jsx(TableCell, { children: c.order.order.orderNumber }), _jsx(TableCell, { children: "\u2014" }), _jsx(TableCell, { children: "\u2014" }), _jsxs(TableCell, { className: "text-right", children: [c.occupancy, "%"] }), _jsx(TableCell, { className: "text-right", children: c.layers }), _jsx(TableCell, { className: "text-right", children: c.plants }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: "Nog te picken" }) }), _jsx(TableCell, { className: "text-right", children: _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) })] }));
|
|
92
|
+
}
|
|
93
|
+
function VerplaatsKarDialog({ cart, currentDate, onClose, }) {
|
|
94
|
+
const [newDate, setNewDate] = useState("");
|
|
95
|
+
const move = useServerFn(moveCartFn);
|
|
96
|
+
const qc = useQueryClient();
|
|
97
|
+
const mutation = useMutation({
|
|
98
|
+
mutationFn: async () => {
|
|
99
|
+
if (!cart)
|
|
100
|
+
throw new Error("Geen kar geselecteerd");
|
|
101
|
+
if (!newDate)
|
|
102
|
+
throw new Error("Kies eerst een nieuwe pickdatum");
|
|
103
|
+
return move({
|
|
104
|
+
data: {
|
|
105
|
+
orderId: cart.orderId,
|
|
106
|
+
orderSource: cart.orderSource,
|
|
107
|
+
cartIndex: cart.cartIndex,
|
|
108
|
+
newDate,
|
|
109
|
+
fromDate: currentDate,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
onSuccess: async (res) => {
|
|
114
|
+
const target = new Date(newDate).toLocaleDateString("nl-NL", {
|
|
115
|
+
weekday: "long", day: "numeric", month: "long",
|
|
116
|
+
});
|
|
117
|
+
toast.success(`Kar ${cart?.cartNumber ?? ""} verplaatst naar ${target}`);
|
|
118
|
+
qc.setQueryData(["cart-overrides"], (current) => {
|
|
119
|
+
const overrides = current?.overrides ?? [];
|
|
120
|
+
const next = overrides.filter((o) => !(o.order_id === res.override.order_id &&
|
|
121
|
+
o.order_source === res.override.order_source &&
|
|
122
|
+
o.cart_index === res.override.cart_index));
|
|
123
|
+
return { overrides: [...next, res.override] };
|
|
124
|
+
});
|
|
125
|
+
await Promise.all([
|
|
126
|
+
qc.invalidateQueries({ queryKey: ["floriday", "orders"] }),
|
|
127
|
+
qc.invalidateQueries({ queryKey: ["cart-overrides"] }),
|
|
128
|
+
]);
|
|
129
|
+
await qc.refetchQueries({ queryKey: ["cart-overrides"], type: "active" });
|
|
130
|
+
setNewDate("");
|
|
131
|
+
onClose();
|
|
132
|
+
return res;
|
|
133
|
+
},
|
|
134
|
+
onError: (e) => {
|
|
135
|
+
const msg = e instanceof Error ? e.message : "Verplaatsen mislukt";
|
|
136
|
+
toast.error(msg);
|
|
137
|
+
console.error("[moveCart]", e);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
if (!cart)
|
|
141
|
+
return null;
|
|
142
|
+
const parsed = newDate ? new Date(newDate) : null;
|
|
143
|
+
const impact = parsed ? evaluateCartMove(parsed, currentDate, cart.order.planning) : null;
|
|
144
|
+
return (_jsx(Dialog, { open: !!cart, onOpenChange: (o) => !o && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(Truck, { className: "h-4 w-4" }), " Kar verplaatsen"] }) }), _jsxs("div", { 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("div", { className: "flex justify-end gap-2", 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" })] })] })] }) }));
|
|
145
|
+
}
|
|
@@ -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":"AAwBA,wBAAgB,cAAc,gCA2G7B"}
|
|
@@ -1,79 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Link } from "@tanstack/react-router";
|
|
3
|
-
import { useServerFn } from "@tanstack/react-start";
|
|
4
|
-
import { useQuery } from "@tanstack/react-query";
|
|
5
2
|
import { useMemo, useState } from "react";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
+
import { WeekAgendaView } from "./WeekAgendaView";
|
|
7
|
+
import { useOrderPlanning } from "../../hooks/useOrderPlanning";
|
|
8
|
+
import { invalidatePlanning } from "../../lib/invalidatePlanning";
|
|
9
9
|
const ALL = "__all__";
|
|
10
|
-
function formatDate(iso) {
|
|
11
|
-
if (!iso)
|
|
12
|
-
return "—";
|
|
13
|
-
const d = new Date(iso);
|
|
14
|
-
if (Number.isNaN(d.getTime()))
|
|
15
|
-
return iso;
|
|
16
|
-
return d.toLocaleDateString("nl-NL", { day: "2-digit", month: "2-digit", year: "numeric" });
|
|
17
|
-
}
|
|
18
|
-
function formatMoney(amount, currency) {
|
|
19
|
-
if (amount == null)
|
|
20
|
-
return "—";
|
|
21
|
-
try {
|
|
22
|
-
return new Intl.NumberFormat("nl-NL", {
|
|
23
|
-
style: "currency",
|
|
24
|
-
currency: currency ?? "EUR",
|
|
25
|
-
}).format(amount);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return `${amount.toFixed(2)} ${currency ?? ""}`.trim();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function statusVariant(status) {
|
|
32
|
-
const s = status.toLowerCase();
|
|
33
|
-
if (s.includes("delivered") || s.includes("completed") || s.includes("afgerond"))
|
|
34
|
-
return "bg-green-500/10 text-green-600";
|
|
35
|
-
if (s.includes("cancel") || s.includes("annul") || s.includes("reject"))
|
|
36
|
-
return "bg-red-500/10 text-red-600";
|
|
37
|
-
if (s.includes("pending") || s.includes("open") || s.includes("nieuw"))
|
|
38
|
-
return "bg-blue-500/10 text-blue-600";
|
|
39
|
-
return "bg-amber-500/10 text-amber-600";
|
|
40
|
-
}
|
|
41
10
|
export function LeveringenPage() {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
let message = "Onbekende fout";
|
|
51
|
-
if (e instanceof Response) {
|
|
52
|
-
const text = await e.clone().text().catch(() => "");
|
|
53
|
-
message = `Floriday-aanvraag mislukt (${e.status}): ${text.slice(0, 200) || e.statusText}`;
|
|
54
|
-
}
|
|
55
|
-
else if (e instanceof Error) {
|
|
56
|
-
message = e.message;
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
message = String(e);
|
|
60
|
-
}
|
|
61
|
-
console.error("[getFloridayOrders]", message);
|
|
62
|
-
return { orders: [], error: message };
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
retry: false,
|
|
66
|
-
});
|
|
67
|
-
const orders = data?.orders ?? [];
|
|
68
|
-
const error = data?.error ? new Error(data.error) : null;
|
|
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
|
+
};
|
|
69
17
|
const [customer, setCustomer] = useState(ALL);
|
|
70
18
|
const [deliveryType, setDeliveryType] = useState(ALL);
|
|
71
19
|
const [dateFrom, setDateFrom] = useState("");
|
|
72
20
|
const [dateTo, setDateTo] = useState("");
|
|
73
|
-
const customers = useMemo(() => Array.from(new Set(
|
|
74
|
-
const deliveryTypes = useMemo(() => Array.from(new Set(
|
|
21
|
+
const customers = useMemo(() => Array.from(new Set(planned.map((p) => p.order.customerName).filter(Boolean))).sort(), [planned]);
|
|
22
|
+
const deliveryTypes = useMemo(() => Array.from(new Set(planned.map((p) => p.order.deliveryType).filter(Boolean))).sort(), [planned]);
|
|
75
23
|
const filtered = useMemo(() => {
|
|
76
|
-
return
|
|
24
|
+
return planned.filter(({ order: o }) => {
|
|
77
25
|
if (customer !== ALL && o.customerName !== customer)
|
|
78
26
|
return false;
|
|
79
27
|
if (deliveryType !== ALL && o.deliveryType !== deliveryType)
|
|
@@ -84,6 +32,6 @@ export function LeveringenPage() {
|
|
|
84
32
|
return false;
|
|
85
33
|
return true;
|
|
86
34
|
});
|
|
87
|
-
}, [
|
|
88
|
-
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) =>
|
|
35
|
+
}, [planned, customer, deliveryType, dateFrom, dateTo]);
|
|
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." }) })] }) }));
|
|
89
37
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FloridayOrder } from "../../lib/floriday.functions";
|
|
2
|
+
export interface PickAgendaViewProps {
|
|
3
|
+
onOrderClick?: (order: FloridayOrder) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare function PickAgendaView({ onOrderClick }: PickAgendaViewProps): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=PickAgendaView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsContent, TabsList, TabsTrigger, } from "@flowselections/core";
|
|
5
|
+
import { RefreshCw } from "lucide-react";
|
|
6
|
+
import { toast } from "@flowselections/core";
|
|
7
|
+
import { WeekAgendaView } from "./WeekAgendaView";
|
|
8
|
+
import { DagDetailDialog } from "./DagDetailDialog";
|
|
9
|
+
import { useOrderPlanning } from "../../hooks/useOrderPlanning";
|
|
10
|
+
import { invalidatePlanning } from "../../lib/invalidatePlanning";
|
|
11
|
+
const ALL = "__all__";
|
|
12
|
+
export function PickAgendaView({ onOrderClick }) {
|
|
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
|
+
};
|
|
20
|
+
const [customer, setCustomer] = useState(ALL);
|
|
21
|
+
const [deliveryType, setDeliveryType] = useState(ALL);
|
|
22
|
+
const [dateFrom, setDateFrom] = useState("");
|
|
23
|
+
const [dateTo, setDateTo] = useState("");
|
|
24
|
+
const customers = useMemo(() => Array.from(new Set(planned.map((p) => p.order.customerName).filter(Boolean))).sort(), [planned]);
|
|
25
|
+
const deliveryTypes = useMemo(() => Array.from(new Set(planned.map((p) => p.order.deliveryType).filter(Boolean))).sort(), [planned]);
|
|
26
|
+
const filtered = useMemo(() => {
|
|
27
|
+
return planned.filter(({ order: o }) => {
|
|
28
|
+
if (customer !== ALL && o.customerName !== customer)
|
|
29
|
+
return false;
|
|
30
|
+
if (deliveryType !== ALL && o.deliveryType !== deliveryType)
|
|
31
|
+
return false;
|
|
32
|
+
if (dateFrom && (!o.deliveryDate || o.deliveryDate < dateFrom))
|
|
33
|
+
return false;
|
|
34
|
+
if (dateTo && (!o.deliveryDate || o.deliveryDate > dateTo + "T23:59:59"))
|
|
35
|
+
return false;
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
}, [planned, customer, deliveryType, dateFrom, dateTo]);
|
|
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) })] }));
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PickingPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/PickingPage.tsx"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,gCAY1B"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PickAgendaView } from "./PickAgendaView";
|
|
3
|
+
export function PickingPage() {
|
|
4
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Pick Agenda" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Overzicht van alle orders die gepickt moeten worden." })] }), _jsx(PickAgendaView, {})] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type WeekAgendaMode } from "./WeekAgendaView";
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
mode: WeekAgendaMode;
|
|
6
|
+
}
|
|
7
|
+
export declare function PlanningAgendaPage({ title, description, mode }: Props): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=PlanningAgendaPage.d.ts.map
|
|
@@ -0,0 +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,+BAqBrE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { WeekAgendaView } from "./WeekAgendaView";
|
|
3
|
+
import { useOrderPlanning } from "../../hooks/useOrderPlanning";
|
|
4
|
+
export function PlanningAgendaPage({ title, description, mode }) {
|
|
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
|
+
}
|
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"VervoerderForm.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerderForm.tsx"],"names":[],"mappings":"AA8CA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAyPD,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BA4XlD"}
|