@flowselections/floriday-verkoop-module 1.0.11 → 1.0.13

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 (69) hide show
  1. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts +4 -0
  2. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts.map +1 -0
  3. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.js +95 -0
  4. package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts +6 -0
  5. package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts.map +1 -0
  6. package/dist-lib/components/verkoop/CartAssignmentWizard.js +194 -0
  7. package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -1
  8. package/dist-lib/components/verkoop/CreateOrderPage.js +230 -192
  9. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts +4 -0
  10. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts.map +1 -0
  11. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.js +44 -0
  12. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts +5 -0
  13. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -0
  14. package/dist-lib/components/verkoop/OrderKarindelingCard.js +160 -0
  15. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
  16. package/dist-lib/components/verkoop/SalesOrderDetailView.js +6 -3
  17. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
  18. package/dist-lib/components/verkoop/SalesOrdersTable.js +3 -4
  19. package/dist-lib/components/verkoop/SearchableSelect.d.ts +19 -0
  20. package/dist-lib/components/verkoop/SearchableSelect.d.ts.map +1 -0
  21. package/dist-lib/components/verkoop/SearchableSelect.js +21 -0
  22. package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
  23. package/dist-lib/components/verkoop/VerkoopOrdersPage.js +39 -3
  24. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1381 -86
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  26. package/dist-lib/integrations/supabase/client.d.ts +1381 -86
  27. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.server.d.ts +1381 -86
  29. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.d.ts +1402 -79
  31. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  32. package/dist-lib/lib/cart-lock.functions.d.ts +11225 -0
  33. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -0
  34. package/dist-lib/lib/cart-lock.functions.js +47 -0
  35. package/dist-lib/lib/cart-planner.d.ts +38 -0
  36. package/dist-lib/lib/cart-planner.d.ts.map +1 -0
  37. package/dist-lib/lib/cart-planner.js +217 -0
  38. package/dist-lib/lib/cart-replan.functions.d.ts +22467 -0
  39. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -0
  40. package/dist-lib/lib/cart-replan.functions.js +203 -0
  41. package/dist-lib/lib/create-order.functions.d.ts +100967 -0
  42. package/dist-lib/lib/create-order.functions.d.ts.map +1 -0
  43. package/dist-lib/lib/create-order.functions.js +343 -0
  44. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +33640 -0
  45. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -0
  46. package/dist-lib/lib/floriday-auto-fulfillment.functions.js +65 -0
  47. package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts +21 -0
  48. package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts.map +1 -0
  49. package/dist-lib/lib/floriday-auto-fulfillment.server.js +352 -0
  50. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +11220 -0
  51. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -0
  52. package/dist-lib/lib/floriday-logistic-labels.functions.js +171 -0
  53. package/dist-lib/lib/floriday-public.functions.d.ts +9067 -16
  54. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  55. package/dist-lib/lib/floriday-public.functions.js +45 -47
  56. package/dist-lib/lib/floriday-writes.functions.d.ts +6475 -0
  57. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  58. package/dist-lib/lib/salesorders-mapping.d.ts +2 -0
  59. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  60. package/dist-lib/lib/salesorders-mapping.js +46 -20
  61. package/dist-lib/lib/salesorders.functions.d.ts +4273 -388
  62. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/salesorders.functions.js +8 -0
  64. package/dist-lib/lib/tradeitems.functions.d.ts +1381 -86
  65. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  66. package/dist-lib/lib/useSalesOrdersQuery.d.ts +1 -0
  67. package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
  68. package/dist-lib/lib/useSalesOrdersQuery.js +130 -41
  69. package/package.json +1 -1
@@ -0,0 +1,160 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Toont karindeling per order. De belading (planten×fusten×lagen) komt
3
+ // ALTIJD uit de producten in Voorraad (tabel `products`) — nooit uit de
4
+ // Floriday-packingConfiguration — zodat verkoop en voorraad synchroon zijn.
5
+ // Alleen wanneer de order geen `product_id`-koppeling heeft, valt de kaart
6
+ // terug op de Floriday packingConfiguration.
7
+ import { useEffect, useMemo, useState } from "react";
8
+ import { Badge, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flowselections/core";
9
+ import { CheckCircle2, Lock } from "lucide-react";
10
+ import { supabase } from "../../integrations/supabase/client";
11
+ import { planCarts } from "../../lib/cart-planner";
12
+ function deriveCartsFromFloriday(order) {
13
+ const o = order.raw.order ?? {};
14
+ const pack = o?.packingConfiguration ?? {};
15
+ const ppc = Math.max(1, Number(pack.piecesPerPackage ?? order.quantity.count ?? 1));
16
+ const cpl = Math.max(1, Number(pack.packagesPerLayer ?? 1));
17
+ const lpc = Math.max(1, Number(pack.layersPerLoadCarrier ?? 1));
18
+ const totalPieces = Math.max(0, Number(o.numberOfPieces ?? order.totalPieces ?? 0));
19
+ if (totalPieces <= 0)
20
+ return [];
21
+ const totalCrates = Math.ceil(totalPieces / ppc);
22
+ const cratesPerCart = cpl * lpc;
23
+ const fullCarts = Math.floor(totalCrates / cratesPerCart);
24
+ const remainderCrates = totalCrates - fullCarts * cratesPerCart;
25
+ const carts = [];
26
+ let n = 1;
27
+ for (let i = 0; i < fullCarts; i++) {
28
+ carts.push({
29
+ cart_number: n++,
30
+ is_complete: true,
31
+ occupancy_pct: 100,
32
+ total_layers_used: lpc,
33
+ items: [{
34
+ product_name: order.productName,
35
+ crates: cratesPerCart,
36
+ plants: cratesPerCart * ppc,
37
+ plants_per_crate: ppc,
38
+ crates_per_layer: cpl,
39
+ layers_per_cart: lpc,
40
+ layer_share_pct: 100,
41
+ }],
42
+ });
43
+ }
44
+ if (remainderCrates > 0) {
45
+ const layersUsed = Math.ceil(remainderCrates / cpl);
46
+ const occ = Math.round(((layersUsed / lpc) * 100) * 100) / 100;
47
+ carts.push({
48
+ cart_number: n,
49
+ is_complete: false,
50
+ occupancy_pct: occ,
51
+ total_layers_used: layersUsed,
52
+ items: [{
53
+ product_name: order.productName,
54
+ crates: remainderCrates,
55
+ plants: remainderCrates * ppc,
56
+ plants_per_crate: ppc,
57
+ crates_per_layer: cpl,
58
+ layers_per_cart: lpc,
59
+ layer_share_pct: occ,
60
+ }],
61
+ });
62
+ }
63
+ return carts;
64
+ }
65
+ function belading(i) {
66
+ return `${i.plants_per_crate}×${i.crates_per_layer}×${i.layers_per_cart}`;
67
+ }
68
+ export function OrderKarindelingCard({ order }) {
69
+ // Lokale/mailbox-orders hebben een prefix (`local:`, `mailbox-proposal:`) in
70
+ // hun DTO-id. De DB-tabellen (`orders`, `order_items`) gebruiken de kale UUID,
71
+ // dus die prefix moet eraf voor de query — anders faalt de UUID-cast en valt
72
+ // de kaart terug op de Floriday-modus met slechts één artikel.
73
+ const rawId = order?.id ?? order.raw?.order?.id ?? null;
74
+ const orderId = typeof rawId === "string" ? rawId.replace(/^(local:|mailbox-proposal:)/, "") : rawId;
75
+ const [lockedAt, setLockedAt] = useState(null);
76
+ const [dbItems, setDbItems] = useState(null);
77
+ // Laadt order_items + gekoppelde products (voorraad-belading) en
78
+ // ververst live bij mutaties op order_items, orders of products.
79
+ useEffect(() => {
80
+ if (!orderId)
81
+ return;
82
+ let active = true;
83
+ const load = async () => {
84
+ const [{ data: itemRows }, { data: orderRow }] = await Promise.all([
85
+ supabase
86
+ .from("order_items")
87
+ .select("id, product_id, product_name, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart)")
88
+ .eq("order_id", orderId),
89
+ supabase.from("orders").select("plan_locked_at").eq("id", orderId).maybeSingle(),
90
+ ]);
91
+ if (!active)
92
+ return;
93
+ setDbItems(itemRows ?? []);
94
+ setLockedAt(orderRow?.plan_locked_at ?? null);
95
+ };
96
+ void load();
97
+ const channel = supabase
98
+ .channel(`order-karindeling-${orderId}`)
99
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "orders", filter: `id=eq.${orderId}` }, (payload) => setLockedAt(payload.new?.plan_locked_at ?? null))
100
+ .on("postgres_changes", { event: "*", schema: "public", table: "order_items", filter: `order_id=eq.${orderId}` }, () => void load())
101
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "products" }, () => void load())
102
+ .subscribe();
103
+ return () => {
104
+ active = false;
105
+ void supabase.removeChannel(channel);
106
+ };
107
+ }, [orderId]);
108
+ // Bouw plannerinput uit voorraad-belading. Alleen items met een gekoppeld
109
+ // product (product_id + belading in products-tabel) worden meegenomen.
110
+ const plannerCarts = useMemo(() => {
111
+ if (!dbItems || dbItems.length === 0)
112
+ return null;
113
+ const inputs = [];
114
+ for (const it of dbItems) {
115
+ const p = it.products;
116
+ if (!it.product_id || !p)
117
+ continue;
118
+ const ppc = Math.max(1, Number(p.plants_per_crate ?? 0));
119
+ const cpl = Math.max(1, Number(p.crates_per_layer ?? 0));
120
+ const lpc = Math.max(1, Number(p.layers_per_cart ?? 0));
121
+ const plants = Math.max(0, Number(it.quantity ?? 0));
122
+ if (plants <= 0)
123
+ continue;
124
+ inputs.push({
125
+ order_item_id: it.id,
126
+ product_id: it.product_id,
127
+ product_name: it.product_name,
128
+ plants,
129
+ plants_per_crate: ppc,
130
+ crates_per_layer: cpl,
131
+ layers_per_cart: lpc,
132
+ });
133
+ }
134
+ if (inputs.length === 0)
135
+ return null;
136
+ return planCarts(inputs).carts;
137
+ }, [dbItems]);
138
+ const floridayCarts = useMemo(() => deriveCartsFromFloriday(order), [order]);
139
+ const usingVoorraad = plannerCarts !== null;
140
+ const carts = usingVoorraad
141
+ ? plannerCarts.map((c) => ({
142
+ cart_number: c.cart_number,
143
+ is_complete: c.is_complete,
144
+ occupancy_pct: c.occupancy_pct,
145
+ total_layers_used: c.total_layers_used,
146
+ items: c.items.map((i) => ({
147
+ product_name: i.product_name,
148
+ crates: i.crates,
149
+ plants: i.plants,
150
+ plants_per_crate: i.plants_per_crate,
151
+ crates_per_layer: i.crates_per_layer,
152
+ layers_per_cart: i.layers_per_cart,
153
+ layer_share_pct: i.layer_share_pct,
154
+ })),
155
+ }))
156
+ : floridayCarts;
157
+ const source = usingVoorraad ? "voorraad" : "floriday";
158
+ const lockedDate = lockedAt ? new Date(lockedAt).toLocaleString("nl-NL") : null;
159
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { className: "text-base", children: "Karindeling" }), _jsxs("div", { className: "flex items-center gap-2", children: [lockedAt && (_jsxs(Badge, { variant: "outline", className: "gap-1", children: [_jsx(Lock, { className: "h-3 w-3" }), "Vergrendeld \u2014 pakbon geprint ", lockedDate] })), _jsxs(Badge, { variant: "secondary", children: [carts.length, " kar(ren) \u00B7 bron: ", source === "voorraad" ? "Voorraad" : "Floriday"] })] })] }), _jsx(CardContent, { children: carts.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen karindeling beschikbaar voor deze order." })) : (_jsx("div", { className: "space-y-4", children: carts.map((c) => (_jsxs("div", { className: "rounded-md border border-border", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border bg-muted/30 px-3 py-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "font-mono font-medium", children: ["Kar ", c.cart_number] }), _jsxs(Badge, { variant: c.is_complete ? "default" : "secondary", className: "gap-1", children: [c.is_complete && _jsx(CheckCircle2, { className: "h-3 w-3" }), c.is_complete ? "Volledig" : "Deels"] })] }), _jsxs("div", { className: "text-muted-foreground", children: [c.total_layers_used, " lagen \u00B7 benutting ", c.occupancy_pct, "%"] })] }), _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, { children: "Belading" }), _jsx(TableHead, { className: "text-right", children: "Laag-aandeel" })] }) }), _jsx(TableBody, { children: c.items.map((i, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: i.product_name }), _jsx(TableCell, { className: "text-right", children: i.plants }), _jsx(TableCell, { className: "text-right", children: i.crates }), _jsx(TableCell, { className: "font-mono text-xs", children: belading(i) }), _jsxs(TableCell, { className: "text-right", children: [Math.round(i.layer_share_pct * 10) / 10, "%"] })] }, idx))) })] })] }, c.cart_number))) })) })] }));
160
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA4HnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,2CAoRA"}
1
+ {"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA+HnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,2CA8RA"}
@@ -1,5 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { CheckCircle2, ImageIcon } from "lucide-react";
3
+ import { OrderKarindelingCard } from "./OrderKarindelingCard";
4
+ import { OrderAfleverbrievenCard } from "./OrderAfleverbrievenCard";
5
+ import { AutoFulfillmentStatusCard } from "./AutoFulfillmentStatusCard";
3
6
  const eurFlex = new Intl.NumberFormat("nl-NL", {
4
7
  style: "currency",
5
8
  currency: "EUR",
@@ -123,7 +126,7 @@ export function SalesOrderDetailView({ order, sellerFallback, }) {
123
126
  if (vbnCode)
124
127
  beladingParts.push(String(vbnCode));
125
128
  if (layersPerLoadCarrier && packagesPerLayer && piecesPerPackage) {
126
- beladingParts.push(`${layersPerLoadCarrier}×${packagesPerLayer}×${piecesPerPackage}`);
129
+ beladingParts.push(`${piecesPerPackage}×${packagesPerLayer}×${layersPerLoadCarrier}`);
127
130
  }
128
131
  if (lcShort)
129
132
  beladingParts.push(lcShort);
@@ -209,5 +212,5 @@ export function SalesOrderDetailView({ order, sellerFallback, }) {
209
212
  const bestellingLabel = order.status.isCancelled
210
213
  ? "Geannuleerd"
211
214
  : STATUS_LABELS[statusKey] ?? (order.status.isShipped ? "Verzonden" : order.status.isConfirmed ? "Order bevestigd" : "Open");
212
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center gap-3 rounded-md border border-border bg-muted/30 p-3", children: [_jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: order.imageUrl ? (_jsx("img", { src: order.imageUrl, alt: "", className: "h-full w-full object-cover" })) : (_jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "truncate text-base font-semibold text-foreground", children: [order.productName, o.tradeItemVersion ? (_jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: ["\u00B7 v", o.tradeItemVersion] })) : null] }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: pickStr(o.customerOrderId) ?? order.sku ?? order.orderNumber })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_360px]", children: [_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx(SectionTitle, { children: "Verkooporder" }), _jsx(Row, { label: "Handelsvorm", children: tradingType }), _jsx(Row, { label: "Bestelmoment", children: fmtDateTime(order.orderedAt) }), _jsx(Row, { label: "Aantal", children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { children: [order.fustCount, " fusten"] }), _jsxs("div", { children: [order.karCount, " karren"] })] }) }), _jsx(Row, { label: "Belading", children: belading }), _jsx(Row, { label: "Klant referentie", children: klantReferentie }), _jsx(Row, { label: "Floriday-referentie", children: floridayRef }), _jsx(Row, { label: "Verkoopkanaal", children: verkoopkanaal }), _jsx(Row, { label: "Financi\u00EBle afhandeling", children: financial }), _jsx(Row, { label: "Verkoper", children: sellerName ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: "inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground", children: initials(sellerName) }), sellerName] })) : ("—") })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Levering" }), _jsx(Row, { label: "Afleverdatum", children: fmtDateTime(order.deliveryAt) }), _jsx(Row, { label: "Incoterm", children: incoterm }), _jsx(Row, { label: "Levering", children: _jsxs("div", { className: "inline-flex max-w-xs items-start gap-3 rounded-md border border-border bg-card p-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-primary/10 text-primary", children: _jsxs("svg", { viewBox: "0 0 24 24", className: "h-5 w-5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M3 11l9-8 9 8" }), _jsx("path", { d: "M5 10v10h14V10" }), _jsx("path", { d: "M10 20v-6h4v6" })] }) }), _jsxs("div", { className: "text-sm", children: [_jsx("div", { children: addr.line || "—" }), _jsx("div", { children: [addr.postal, addr.city].filter(Boolean).join(" ") || "—" }), _jsx("div", { children: countryLabel ?? "" }), addr.gln && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: addr.gln }))] })] }) })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Klant" }), _jsx(Row, { label: "Naam", children: _jsx("span", { className: "font-medium", children: customerName }) }), _jsx(Row, { label: "GLN", children: customerGln }), _jsx(Row, { label: "Administratienummer", children: customerAdmin }), _jsx(Row, { label: "Telefoonnummer", children: customerPhone })] })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Prijs per stuk" }), _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Product" }), _jsxs("span", { children: [oldProductPrice !== null && (_jsx("span", { className: "mr-2 text-destructive line-through", children: eurFlex.format(oldProductPrice) })), eurFlex.format(productUnit)] })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Transportkosten" }), _jsx("span", { children: eurFlex.format(transportPerPiece) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Inkoopprijs" }), _jsx("span", { children: eurFlex.format(purchaseUnit) })] }), _jsxs("div", { className: "mt-2 flex justify-between border-t border-border pt-2 font-semibold", children: [_jsx("span", { children: "Totaal" }), _jsxs("span", { children: [_jsxs("span", { className: "mr-2 text-xs font-normal text-muted-foreground", children: ["(", packages, " \u00D7 ", piecesPerPackage, " \u00D7 ", eurFlex.format(productUnit), ")"] }), eurFlex.format(totalAmount)] })] })] })] }), _jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Status" }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Bestelling" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), bestellingLabel] })] }), hasCorrection && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Correctie" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), "Correctie is verwerkt"] })] }))] })] })] })] })] }));
215
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center gap-3 rounded-md border border-border bg-muted/30 p-3", children: [_jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: order.imageUrl ? (_jsx("img", { src: order.imageUrl, alt: "", className: "h-full w-full object-cover" })) : (_jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "truncate text-base font-semibold text-foreground", children: [order.productName, o.tradeItemVersion ? (_jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: ["\u00B7 v", o.tradeItemVersion] })) : null] }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: order.orderNumber || order.sku || "—" })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_360px]", children: [_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx(SectionTitle, { children: "Verkooporder" }), _jsx(Row, { label: "Ordernummer", children: order.orderNumber || "—" }), _jsx(Row, { label: "Handelsvorm", children: tradingType }), _jsx(Row, { label: "Bestelmoment", children: fmtDateTime(order.orderedAt) }), _jsx(Row, { label: "Aantal", children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { children: [order.fustCount, " fusten"] }), _jsxs("div", { children: [order.karCount, " karren"] })] }) }), _jsx(Row, { label: "Belading", children: belading }), _jsx(Row, { label: "Klant referentie", children: klantReferentie }), _jsx(Row, { label: "Floriday-referentie", children: floridayRef }), _jsx(Row, { label: "Verkoopkanaal", children: verkoopkanaal }), _jsx(Row, { label: "Financi\u00EBle afhandeling", children: financial }), _jsx(Row, { label: "Verkoper", children: sellerName ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: "inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground", children: initials(sellerName) }), sellerName] })) : ("—") })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Levering" }), _jsx(Row, { label: "Afleverdatum", children: fmtDateTime(order.deliveryAt) }), _jsx(Row, { label: "Incoterm", children: incoterm }), _jsx(Row, { label: "Levering", children: _jsxs("div", { className: "inline-flex max-w-xs items-start gap-3 rounded-md border border-border bg-card p-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-primary/10 text-primary", children: _jsxs("svg", { viewBox: "0 0 24 24", className: "h-5 w-5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M3 11l9-8 9 8" }), _jsx("path", { d: "M5 10v10h14V10" }), _jsx("path", { d: "M10 20v-6h4v6" })] }) }), _jsxs("div", { className: "text-sm", children: [_jsx("div", { children: addr.line || "—" }), _jsx("div", { children: [addr.postal, addr.city].filter(Boolean).join(" ") || "—" }), _jsx("div", { children: countryLabel ?? "" }), addr.gln && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: addr.gln }))] })] }) })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Klant" }), _jsx(Row, { label: "Naam", children: _jsx("span", { className: "font-medium", children: customerName }) }), _jsx(Row, { label: "GLN", children: customerGln }), _jsx(Row, { label: "Administratienummer", children: customerAdmin }), _jsx(Row, { label: "Telefoonnummer", children: customerPhone })] })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Prijs per stuk" }), _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Product" }), _jsxs("span", { children: [oldProductPrice !== null && (_jsx("span", { className: "mr-2 text-destructive line-through", children: eurFlex.format(oldProductPrice) })), eurFlex.format(productUnit)] })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Transportkosten" }), _jsx("span", { children: eurFlex.format(transportPerPiece) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Inkoopprijs" }), _jsx("span", { children: eurFlex.format(purchaseUnit) })] }), _jsxs("div", { className: "mt-2 flex justify-between border-t border-border pt-2 font-semibold", children: [_jsx("span", { children: "Totaal" }), _jsxs("span", { children: [_jsxs("span", { className: "mr-2 text-xs font-normal text-muted-foreground", children: ["(", packages, " \u00D7 ", piecesPerPackage, " \u00D7 ", eurFlex.format(productUnit), ")"] }), eurFlex.format(totalAmount)] })] })] })] }), _jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Status" }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Bestelling" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), bestellingLabel] })] }), hasCorrection && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Correctie" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), "Correctie is verwerkt"] })] }))] })] })] })] }), _jsx(OrderKarindelingCard, { order: order }), o?.salesOrderId ? (_jsxs(_Fragment, { children: [_jsx(AutoFulfillmentStatusCard, { salesOrderId: String(o.salesOrderId) }), _jsx(OrderAfleverbrievenCard, { salesOrderId: String(o.salesOrderId) })] })) : null] }));
213
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAuEnE,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,GACd,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD,2CA6IA"}
1
+ {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAsEnE,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,GACd,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD,2CAyIA"}
@@ -3,7 +3,6 @@ import { useState } from "react";
3
3
  import { Badge, Checkbox, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
4
4
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@flowselections/core";
5
5
  import { MoreVertical, Package, ArrowDown } from "lucide-react";
6
- const eur = new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR" });
7
6
  function formatDateShort(iso) {
8
7
  if (!iso)
9
8
  return { date: "—", time: "" };
@@ -44,7 +43,7 @@ function StatusTag({ status, rawOrder, }) {
44
43
  if (rawStatus === "COMMITTED" || status.isConfirmed) {
45
44
  return (_jsx(Badge, { variant: "outline", className: "border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400", children: "Verzend klaar" }));
46
45
  }
47
- return (_jsx(Badge, { variant: "outline", className: "border-muted-foreground/30 bg-muted text-muted-foreground", children: "Nog piken" }));
46
+ return (_jsx(Badge, { variant: "outline", className: "border-muted-foreground/30 bg-muted text-muted-foreground", children: "Nog picken" }));
48
47
  }
49
48
  export function SalesOrdersTable({ orders, loading, onSelectOrder, }) {
50
49
  const [selected, setSelected] = useState(new Set());
@@ -54,10 +53,10 @@ export function SalesOrdersTable({ orders, loading, onSelectOrder, }) {
54
53
  n.has(id) ? n.delete(id) : n.add(id);
55
54
  return n;
56
55
  });
57
- return (_jsx("div", { className: "rounded-lg border border-border bg-card", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { checked: allChecked, onCheckedChange: (v) => setSelected(v === true ? new Set(orders.map((o) => o.id)) : new Set()) }) }), _jsx(TableHead, { children: "Order" }), _jsx(TableHead, { children: _jsxs("span", { className: "inline-flex items-center gap-1", children: ["Bestelmoment ", _jsx(ArrowDown, { className: "h-3 w-3" })] }) }), _jsx(TableHead, { children: "Karren" }), _jsx(TableHead, { children: "Fusten" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Levering" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-10" })] }) }), _jsxs(TableBody, { children: [loading &&
56
+ return (_jsx("div", { className: "rounded-lg border border-border bg-card", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { checked: allChecked, onCheckedChange: (v) => setSelected(v === true ? new Set(orders.map((o) => o.id)) : new Set()) }) }), _jsx(TableHead, { children: "Omzet" }), _jsx(TableHead, { children: _jsxs("span", { className: "inline-flex items-center gap-1", children: ["Bestelmoment ", _jsx(ArrowDown, { className: "h-3 w-3" })] }) }), _jsx(TableHead, { children: "Karren" }), _jsx(TableHead, { children: "Fusten" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Levering" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-10" })] }) }), _jsxs(TableBody, { children: [loading &&
58
57
  Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 9 }).map((__, j) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-6 w-full" }) }, j))) }, `s-${i}`))), !loading && orders.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 9, className: "py-12 text-center text-muted-foreground", children: "Geen verkooporders gevonden." }) })), !loading &&
59
58
  orders.map((o) => {
60
59
  const ordered = formatDateShort(o.orderedAt);
61
- return (_jsxs(TableRow, { className: "align-top cursor-pointer", onClick: () => onSelectOrder?.(o), children: [_jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: selected.has(o.id), onCheckedChange: () => toggle(o.id) }) }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: o.orderNumber || "—" }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-4 w-4 text-primary" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-foreground", children: ordered.date }), _jsx("div", { className: "text-xs text-muted-foreground", children: ordered.time })] })] }) }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium text-foreground", children: o.karCount }), _jsx("div", { className: "text-xs text-muted-foreground", children: eur.format(o.revenue) })] }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: o.fustCount }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-foreground", children: o.customer.name }), o.isLocal && (_jsx(Badge, { variant: "outline", className: "border-primary/30 text-primary", children: "Lokaal" }))] }), _jsx("div", { className: "text-xs text-muted-foreground", children: [o.customer.postalCode, o.customer.city].filter(Boolean).join(" ") })] }), _jsx(TableCell, { className: "text-sm text-foreground", children: formatDateLong(o.deliveryAt) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StatusTag, { status: o.status, rawOrder: o.raw?.order }), o.orderNumber && (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: o.orderNumber }))] }) }), _jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { children: "Bewerken" }), _jsx(DropdownMenuItem, { children: "Details bekijken" }), _jsx(DropdownMenuItem, { children: "Bevestigen" }), _jsx(DropdownMenuItem, { className: "text-destructive focus:text-destructive", children: "Annuleren" })] })] }) })] }, o.id));
60
+ return (_jsxs(TableRow, { className: "align-top cursor-pointer", onClick: () => onSelectOrder?.(o), children: [_jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: selected.has(o.id), onCheckedChange: () => toggle(o.id) }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "font-medium text-foreground", children: ["\u20AC ", o.revenue.toFixed(2)] }), o.orderNumber && (_jsx("div", { className: "text-xs text-muted-foreground", children: o.orderNumber }))] }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-4 w-4 text-primary" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-foreground", children: ordered.date }), _jsx("div", { className: "text-xs text-muted-foreground", children: ordered.time })] })] }) }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: o.karCount }) }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: o.fustCount }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-foreground", children: o.customer.name }), o.isLocal && (_jsx(Badge, { variant: "outline", className: "border-primary/30 text-primary", children: "Lokaal" }))] }), _jsx("div", { className: "text-xs text-muted-foreground", children: [o.customer.postalCode, o.customer.city].filter(Boolean).join(" ") })] }), _jsx(TableCell, { className: "text-sm text-foreground", children: formatDateLong(o.deliveryAt) }), _jsx(TableCell, { children: _jsx(StatusTag, { status: o.status, rawOrder: o.raw?.order }) }), _jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { children: "Bewerken" }), _jsx(DropdownMenuItem, { children: "Details bekijken" }), _jsx(DropdownMenuItem, { children: "Bevestigen" }), _jsx(DropdownMenuItem, { className: "text-destructive focus:text-destructive", children: "Annuleren" })] })] }) })] }, o.id));
62
61
  })] })] }) }));
63
62
  }
@@ -0,0 +1,19 @@
1
+ export interface SearchableOption {
2
+ value: string;
3
+ label: string;
4
+ hint?: string | null;
5
+ }
6
+ interface SearchableSelectProps {
7
+ value: string | null;
8
+ options: SearchableOption[];
9
+ placeholder?: string;
10
+ emptyLabel?: string;
11
+ allowClear?: boolean;
12
+ clearLabel?: string;
13
+ onChange: (value: string | null) => void;
14
+ className?: string;
15
+ disabled?: boolean;
16
+ }
17
+ export declare function SearchableSelect({ value, options, placeholder, emptyLabel, allowClear, clearLabel, onChange, className, disabled, }: SearchableSelectProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=SearchableSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SearchableSelect.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EAAE,OAAO,EAAE,WAAuB,EAAE,UAA6B,EACtE,UAAiB,EAAE,UAAiC,EACpD,QAAQ,EAAE,SAAS,EAAE,QAAgB,GACtC,EAAE,qBAAqB,2CAuFvB"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Button, Input, Popover, PopoverContent, PopoverTrigger, } from "@flowselections/core";
4
+ import { Check, ChevronsUpDown, Search } from "lucide-react";
5
+ export function SearchableSelect({ value, options, placeholder = "Kies...", emptyLabel = "Geen resultaat", allowClear = true, clearLabel = "— Geen koppeling —", onChange, className, disabled = false, }) {
6
+ const [open, setOpen] = useState(false);
7
+ const [q, setQ] = useState("");
8
+ const selected = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value]);
9
+ const filtered = useMemo(() => {
10
+ const needle = q.trim().toLowerCase();
11
+ if (!needle)
12
+ return options.slice(0, 200);
13
+ return options
14
+ .filter((o) => o.label.toLowerCase().includes(needle) ||
15
+ (o.hint ?? "").toLowerCase().includes(needle))
16
+ .slice(0, 200);
17
+ }, [options, q]);
18
+ return (_jsxs(Popover, { open: open, onOpenChange: (o) => { if (disabled)
19
+ return; setOpen(o); if (!o)
20
+ setQ(""); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, disabled: disabled, className: `w-full justify-between font-normal ${className ?? ""}`, children: [_jsx("span", { className: `truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.label ?? placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: [_jsx("div", { className: "p-2 border-b", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: q, onChange: (e) => setQ(e.target.value), placeholder: `Zoek in ${options.length} items...`, className: "pl-8 h-9" })] }) }), _jsxs("div", { className: "max-h-64 overflow-y-auto py-1", children: [allowClear && (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-center gap-2", onClick: () => { onChange(null); setOpen(false); }, children: [value === null && _jsx(Check, { className: "h-3 w-3" }), _jsx("span", { className: "text-muted-foreground italic", children: clearLabel })] })), filtered.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-sm text-muted-foreground text-center", children: emptyLabel })) : (filtered.map((o) => (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-start gap-2", onClick: () => { onChange(o.value); setOpen(false); }, children: [_jsx(Check, { className: `h-3 w-3 mt-1 shrink-0 ${value === o.value ? "" : "invisible"}` }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "truncate", children: o.label }), o.hint && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.hint })] })] }, o.value)))), filtered.length === 200 && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground text-center border-t", children: "Toon eerste 200 resultaten \u2014 verfijn je zoekopdracht" }))] })] })] }));
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VerkoopOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/VerkoopOrdersPage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,iBAAiB,4CA6RhC"}
1
+ {"version":3,"file":"VerkoopOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/VerkoopOrdersPage.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,iBAAiB,4CA2UhC"}
@@ -4,6 +4,8 @@ import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
4
4
  import { Download, Plus, AlertTriangle, RefreshCw, ChevronLeft, ChevronRight, Filter, } from "lucide-react";
5
5
  import { useServerFn } from "@tanstack/react-start";
6
6
  import { useNavigate } from "@tanstack/react-router";
7
+ import { useQueryClient } from "@tanstack/react-query";
8
+ import { supabase } from "../../integrations/supabase/client";
7
9
  import { runSalesOrdersSync } from "../../lib/salesorders.functions";
8
10
  import { runTradeItemsSync } from "../../lib/tradeitems.functions";
9
11
  import { applyFilters, computeTotals, ordersToCsv, } from "../../lib/salesorders-mapping";
@@ -12,7 +14,6 @@ import { AnalyticsCards } from "./AnalyticsCards";
12
14
  import { SalesOrdersFilters } from "./SalesOrdersFilters";
13
15
  import { SalesOrdersFilterPanel, countActiveFilters, } from "./SalesOrdersFilterPanel";
14
16
  import { SalesOrdersTable } from "./SalesOrdersTable";
15
- import { FlorydaySyncHealthBanner } from "../../components/floriday/FlorydaySyncHealthBanner";
16
17
  const STATUS_TABS = [
17
18
  { id: "all", label: "Alle" },
18
19
  { id: "open", label: "Open" },
@@ -53,10 +54,45 @@ export function VerkoopOrdersPage() {
53
54
  const [page, setPage] = useState(1);
54
55
  const [pageSize, setPageSize] = useState(20);
55
56
  const navigate = useNavigate();
57
+ const queryClient = useQueryClient();
56
58
  const triggerSync = useServerFn(runSalesOrdersSync);
57
59
  const triggerTradeItemsSync = useServerFn(runTradeItemsSync);
58
60
  const { data, isLoading, error, refetch, isFetching } = useSalesOrdersQuery();
59
61
  const orders = data?.orders ?? [];
62
+ const activeEnvironment = data?.environment ?? "staging";
63
+ // Automatisch omschakelen wanneer de Authenticatie-module de omgeving wijzigt.
64
+ useEffect(() => {
65
+ const channel = supabase
66
+ .channel("floriday-settings-env")
67
+ .on("postgres_changes", { event: "*", schema: "public", table: "floriday_settings" }, () => {
68
+ queryClient.invalidateQueries({ queryKey: ["floriday", "salesorders-cache"] });
69
+ })
70
+ .subscribe();
71
+ return () => {
72
+ supabase.removeChannel(channel);
73
+ };
74
+ }, [queryClient]);
75
+ // Realtime — lijst live bijwerken zodra cache-regels binnenkomen/wijzigen.
76
+ useEffect(() => {
77
+ let timer = null;
78
+ const invalidate = () => {
79
+ if (timer)
80
+ clearTimeout(timer);
81
+ timer = setTimeout(() => {
82
+ queryClient.invalidateQueries({ queryKey: ["floriday", "salesorders-cache"] });
83
+ }, 500);
84
+ };
85
+ const channel = supabase
86
+ .channel("floriday-salesorders-cache")
87
+ .on("postgres_changes", { event: "*", schema: "public", table: "floriday_salesorders_cache" }, invalidate)
88
+ .on("postgres_changes", { event: "*", schema: "public", table: "floriday_fulfillment_orders_cache" }, invalidate)
89
+ .subscribe();
90
+ return () => {
91
+ if (timer)
92
+ clearTimeout(timer);
93
+ supabase.removeChannel(channel);
94
+ };
95
+ }, [queryClient]);
60
96
  const statusFiltered = useMemo(() => orders.filter((o) => matchesStatus(o, tab)), [orders, tab]);
61
97
  const filtered = useMemo(() => applyFilters(statusFiltered, filters), [statusFiltered, filters]);
62
98
  const totals = useMemo(() => computeTotals(filtered), [filtered]);
@@ -118,7 +154,7 @@ export function VerkoopOrdersPage() {
118
154
  ]);
119
155
  const syncedOrders = orders.results.reduce((s, x) => s + x.synced, 0);
120
156
  const syncedItems = items.results.reduce((s, x) => s + x.synced, 0);
121
- toast.success(`Sync klaar — ${syncedOrders} order(s), ${syncedItems} artikel(en) bijgewerkt`);
157
+ toast.success(`Sync klaar (${activeEnvironment === "live" ? "productie" : "staging"}) — ${syncedOrders} order(s), ${syncedItems} artikel(en) bijgewerkt`);
122
158
  await refetch();
123
159
  }
124
160
  catch (e) {
@@ -128,7 +164,7 @@ export function VerkoopOrdersPage() {
128
164
  setSyncing(false);
129
165
  }
130
166
  };
131
- return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsx(FlorydaySyncHealthBanner, { environment: "staging", resources: ["sales-orders", "trade-items", "customer-organizations"] }), _jsx("div", { className: "flex items-center justify-between border-b border-border", children: _jsx("div", { className: "flex items-center gap-6 overflow-x-auto", children: STATUS_TABS.map((t) => {
167
+ return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsx("div", { className: "flex items-center justify-between border-b border-border", children: _jsx("div", { className: "flex items-center gap-6 overflow-x-auto", children: STATUS_TABS.map((t) => {
132
168
  const active = tab === t.id;
133
169
  const count = statusCounts[t.id];
134
170
  return (_jsxs("button", { type: "button", onClick: () => setTab(t.id), className: "relative -mb-px flex items-center gap-2 whitespace-nowrap border-b-2 px-1 pb-3 pt-1 text-sm transition-colors " +