@flowselections/floriday-verkoop-module 1.0.73 → 1.0.76

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 (32) hide show
  1. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -1
  2. package/dist-lib/components/verkoop/OrderKarindelingCard.js +36 -114
  3. package/dist-lib/components/verkoop/PlanCoverageBadge.d.ts +6 -0
  4. package/dist-lib/components/verkoop/PlanCoverageBadge.d.ts.map +1 -0
  5. package/dist-lib/components/verkoop/PlanCoverageBadge.js +23 -0
  6. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
  7. package/dist-lib/components/verkoop/SalesOrderDetailView.js +2 -1
  8. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
  9. package/dist-lib/components/verkoop/SalesOrdersTable.js +2 -1
  10. package/dist-lib/integrations/supabase/types.d.ts +607 -3
  11. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  12. package/dist-lib/lib/cart-replan.functions.d.ts +2 -22
  13. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
  14. package/dist-lib/lib/cart-replan.functions.js +10 -163
  15. package/dist-lib/lib/create-order.functions.d.ts +0 -57
  16. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  17. package/dist-lib/lib/create-order.functions.js +1 -205
  18. package/dist-lib/lib/logistics-engine.server.d.ts.map +1 -1
  19. package/dist-lib/lib/logistics-engine.server.js +8 -3
  20. package/dist-lib/lib/plan-coverage.d.ts +56 -0
  21. package/dist-lib/lib/plan-coverage.d.ts.map +1 -0
  22. package/dist-lib/lib/plan-coverage.js +120 -0
  23. package/dist-lib/lib/plan-coverage.server.d.ts +3 -0
  24. package/dist-lib/lib/plan-coverage.server.d.ts.map +1 -0
  25. package/dist-lib/lib/plan-coverage.server.js +97 -0
  26. package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -1
  27. package/dist-lib/lib/salesorders-list.server.js +43 -82
  28. package/dist-lib/lib/salesorders-mapping.d.ts +5 -0
  29. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  30. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/salesorders.functions.js +21 -18
  32. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAwE/D,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,+BA4OvE"}
1
+ {"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAkB/D,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,+BAyJvE"}
@@ -1,135 +1,57 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Toont de karindeling van de buffer waarin deze order valt.
3
- //
4
- // BELANGRIJK: deze kaart LEEST de karren uit `order_carts`/`order_cart_items`
5
- // via de server-fn `listOrderCarts`. Hij berekent niets zelf — de planner
6
- // draait server-side (cart-replan v3 / logistics_plan_buffer_sql). Een eigen
7
- // herberekening in de browser gaf eerder een andere indeling dan de database
8
- // (één artikel per kar, verkeerde kar-telling) en is daarom verwijderd.
9
- import { useEffect, useMemo, useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // Toont de karindeling van deze order zoals de Logistiek Engine die levert
3
+ // (logistics_order_plan_coverage). Deze kaart leest order_carts/
4
+ // order_cart_items niet zelf en berekent niets: alleen de regels van DEZE
5
+ // order, per kar, met of de kar gedeeld is met de buffer.
6
+ import { useState } from "react";
10
7
  import { useQuery, useQueryClient } from "@tanstack/react-query";
11
8
  import { useServerFn } from "@tanstack/react-start";
12
- import { Badge, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flowselections/core";
13
- import { CheckCircle2, Lock, LayoutGrid } from "lucide-react";
14
- import { supabase } from "../../integrations/supabase/client.js";
15
- import { formatBeladingNotation } from "../../lib/load-carrier-labels.js";
16
- import { listOrderCarts } from "../../lib/cart-replan.functions.js";
9
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flowselections/core";
10
+ import { LayoutGrid, RefreshCw } from "lucide-react";
11
+ import { getOrderPlanCoverage } from "../../lib/cart-replan.functions.js";
12
+ import { summarize } from "../../lib/plan-coverage.js";
17
13
  import { resolveProductName } from "../../lib/product-name.js";
18
14
  import { CartLayersDialog } from "./CartLayersDialog";
15
+ import { PlanCoverageBadge } from "./PlanCoverageBadge";
19
16
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20
- // De planner schrijft één regel per LAAG. Voor de weergave vatten we regels van
21
- // hetzelfde artikel/order met dezelfde belading samen tot één regel.
22
- function groupCartItems(items) {
23
- const out = new Map();
24
- for (const i of items) {
25
- const key = [
26
- i.order_item_id ?? "",
27
- i.product_id ?? "",
28
- i.product_name ?? "",
29
- i.plants_per_crate ?? "",
30
- i.crates_per_layer ?? "",
31
- i.layers_per_cart ?? "",
32
- ].join("|");
33
- const prev = out.get(key);
34
- if (prev) {
35
- prev.crates = Number(prev.crates ?? 0) + Number(i.crates ?? 0);
36
- prev.layer_share_pct = Number(prev.layer_share_pct ?? 0) + Number(i.layer_share_pct ?? 0);
37
- prev.lines += 1;
38
- }
39
- else {
40
- out.set(key, { ...i, crates: Number(i.crates ?? 0), layer_share_pct: Number(i.layer_share_pct ?? 0), lines: 1 });
41
- }
42
- }
43
- return Array.from(out.values());
44
- }
45
- function belading(i) {
46
- // Eén notatie in de hele module: lagen × fusten/laag × planten/fust
47
- // (Floriday-UI-volgorde, bewust anders dan de API-veldvolgorde).
48
- const l = i.layers_per_cart ?? 0;
49
- const c = i.crates_per_layer ?? 0;
50
- const p = i.plants_per_crate ?? 0;
51
- if (!l || !c || !p)
52
- return "—";
53
- return formatBeladingNotation(l, c, p) ?? `${l}×${c}×${p}`;
54
- }
17
+ const PICK_LABEL = {
18
+ geen_pickrij: "Geen pickrij",
19
+ open: "Open",
20
+ bezig: "Bezig met picken",
21
+ gepickt: "Gepickt",
22
+ };
23
+ const fmt = (v) => (v ? new Date(v).toLocaleString("nl-NL") : null);
55
24
  export function OrderKarindelingCard({ order }) {
56
- // Lokale/mailbox-orders hebben een prefix (`local:`, `mailbox-proposal:`) in
57
- // hun DTO-id; de DB gebruikt de kale UUID.
58
- const rawId = order?.id ?? order.raw?.order?.id ?? null;
25
+ // Lokale orders hebben een prefix in hun DTO-id; Floriday-orders hun
26
+ // salesOrderId. De server zoekt de juiste order op.
27
+ const rawId = order?.id ?? null;
59
28
  const stripped = typeof rawId === "string" ? rawId.replace(/^(local:|mailbox-proposal:)/, "") : null;
60
- const orderId = stripped && UUID_RE.test(stripped) ? stripped : null;
61
- const fetchCarts = useServerFn(listOrderCarts);
29
+ const orderId = stripped && (UUID_RE.test(stripped) || stripped.length > 0) ? stripped : null;
30
+ const fetchCoverage = useServerFn(getOrderPlanCoverage);
62
31
  const queryClient = useQueryClient();
63
- const queryKey = ["order-carts", orderId];
64
- const { data, isLoading, error } = useQuery({
32
+ const queryKey = ["order-plan-coverage", orderId];
33
+ const { data, isLoading, error, isFetching } = useQuery({
65
34
  queryKey,
66
35
  enabled: !!orderId,
67
- staleTime: 15000,
36
+ staleTime: 30000,
68
37
  retry: (failureCount, queryError) => /authorization|token|sessie/i.test(queryError.message) && failureCount < 5,
69
38
  retryDelay: (attempt) => Math.min(500 * 2 ** attempt, 4000),
70
39
  queryFn: async () => {
71
40
  if (!orderId)
72
41
  throw new Error("Geen geldige order geselecteerd");
73
- return fetchCarts({ data: { orderId } });
42
+ return fetchCoverage({ data: { orderId } });
74
43
  },
75
44
  });
76
- // Realtime op de karren zelf — niet meer op products/order_items, want die
77
- // voedden de verwijderde client-planner.
78
- useEffect(() => {
79
- if (!orderId)
80
- return;
81
- const invalidate = () => void queryClient.invalidateQueries({ queryKey: ["order-carts", orderId] });
82
- const channel = supabase
83
- .channel(`order-carts-${orderId}`)
84
- .on("postgres_changes", { event: "*", schema: "public", table: "order_carts" }, invalidate)
85
- .on("postgres_changes", { event: "*", schema: "public", table: "order_cart_items" }, invalidate)
86
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "orders" }, invalidate)
87
- .subscribe();
88
- return () => {
89
- void supabase.removeChannel(channel);
90
- };
91
- }, [orderId, queryClient]);
92
- const carts = (data?.carts ?? []);
93
- const orders = data?.orders ?? [];
94
- const lockedAt = data?.planLockedAt ?? null;
95
- const blocked = data?.blocked ?? null;
96
- const planState = data?.planState ?? null;
97
- // De DB-order-id (verschilt van de DTO-id bij Floriday-orders).
98
- const dbOrderId = data?.orderId ?? null;
99
- const orderNumberByItem = useMemo(() => {
100
- const numberByOrder = new Map();
101
- for (const o of orders)
102
- numberByOrder.set(o.id, o.order_number);
103
- const m = new Map();
104
- for (const it of data?.itemOrders ?? []) {
105
- m.set(it.id, { orderId: it.order_id, orderNumber: numberByOrder.get(it.order_id) ?? null });
106
- }
107
- return m;
108
- }, [data, orders]);
109
- // Bron-badge uit de data: order_carts.source + algorithm_version.
110
- const sourceLabel = useMemo(() => {
111
- const srcs = [...new Set(carts.map((c) => c.source ?? "onbekend"))];
112
- const vers = [...new Set(carts.map((c) => c.algorithm_version).filter((v) => v != null))];
113
- if (srcs.length === 0)
114
- return null;
115
- return `${srcs.join(", ")}${vers.length > 0 ? ` · v${vers.join("/")}` : ""}`;
116
- }, [carts]);
117
- const lockedDate = lockedAt ? new Date(lockedAt).toLocaleString("nl-NL") : null;
118
- const multiOrder = orders.length > 1;
45
+ const cov = data?.coverage ?? null;
46
+ const carts = Array.isArray(cov?.carts) ? cov.carts : [];
47
+ const pickRows = Array.isArray(cov?.pickRows) ? cov.pickRows : [];
119
48
  const [layersFor, setLayersFor] = useState(null);
120
- 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] })), multiOrder && (_jsxs(Badge, { variant: "outline", title: orders.map((o) => o.order_number ?? o.id).join(", "), children: ["Buffer \u00B7 ", orders.length, " orders"] })), !error && (_jsxs(Badge, { variant: "secondary", children: [carts.length, " kar(ren)", sourceLabel ? ` · bron: ${sourceLabel}` : ""] }))] })] }), _jsx(CardContent, { children: isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Karindeling laden\u2026" })) : error ? (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium text-destructive", children: "Kon de karindeling niet laden." }), _jsx("p", { className: "text-sm text-muted-foreground", children: error.message })] })) : carts.length === 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen karindeling beschikbaar voor deze order." }), planState ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: [["pending", "wachtend"].includes(String(planState.status).toLowerCase())
121
- ? "Aangemeld en wacht op de indeling"
122
- : ["running", "bezig"].includes(String(planState.status).toLowerCase())
123
- ? "Indeling wordt nu berekend"
124
- : `Laatste stand: ${planState.status}`, planState.reason ? ` · aanleiding: ${planState.reason}` : "", planState.at
125
- ? ` (${new Date(planState.at).toLocaleString("nl-NL")})`
126
- : ""] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Deze order staat nog niet aangemeld voor een indeling." })), blocked && (blocked.explanation || blocked.reason) && (_jsxs("p", { className: "text-sm text-destructive", children: [blocked.explanation ?? blocked.reason, blocked.at ? ` (${new Date(blocked.at).toLocaleString("nl-NL")})` : ""] }))] })) : (_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("button", { type: "button", className: "flex items-center gap-3 rounded-sm text-left hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: () => setLayersFor({ id: c.id, number: c.cart_number }), title: "Bekijk de lagen van deze kar", children: [_jsx(LayoutGrid, { className: "h-4 w-4 text-muted-foreground" }), _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"] }), c.order_id === null && multiOrder && (_jsx(Badge, { variant: "outline", children: "Gedeelde kar" })), c.carrier_type && (_jsxs(Badge, { variant: "outline", className: "font-mono text-[10px]", children: [c.carrier_type, c.max_layers ? ` · ${c.max_layers} lagen` : ""] }))] }), _jsxs("div", { className: "text-muted-foreground", children: [Number(c.total_layers_used ?? 0), " lagen \u00B7 benutting", " ", Math.round(Number(c.occupancy_pct ?? 0) * 10) / 10, "%"] })] }), _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: groupCartItems(c.order_cart_items ?? []).map((i) => {
127
- const own = i.order_item_id
128
- ? orderNumberByItem.get(i.order_item_id)
129
- : undefined;
130
- const crates = Number(i.crates ?? 0);
131
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: resolveProductName(i.product_name) ?? "—" }), multiOrder && own?.orderNumber && (_jsx(Badge, { variant: own.orderId === dbOrderId ? "default" : "outline", className: "text-[10px]", children: own.orderNumber })), i.lines > 1 && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [i.lines, " lagen"] }))] }) }), _jsx(TableCell, { className: "text-right", children: crates * Number(i.plants_per_crate ?? 0) }), _jsx(TableCell, { className: "text-right", children: crates }), _jsx(TableCell, { className: "font-mono text-xs", children: belading(i) }), _jsxs(TableCell, { className: "text-right", children: [Math.round(Number(i.layer_share_pct ?? 0) * 10) / 10, "%"] })] }, i.id));
132
- }) })] })] }, c.id))) })) }), _jsx(CartLayersDialog, { cartId: layersFor?.id ?? null, cartNumber: layersFor?.number ?? null, open: !!layersFor, onOpenChange: (v) => {
49
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2 sm:flex sm:flex-row sm:justify-between", children: [_jsx(CardTitle, { className: "truncate text-base", children: "Karindeling" }), _jsxs("div", { className: "flex items-center gap-2", children: [cov && cov.state !== "onbekend" && (_jsxs(Badge, { variant: "secondary", children: [carts.length, " kar(ren)"] })), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Vernieuwen", title: "Vernieuwen", onClick: () => void queryClient.invalidateQueries({ queryKey }), children: _jsx(RefreshCw, { className: "h-4 w-4 " + (isFetching ? "animate-spin" : "") }) })] })] }), _jsx(CardContent, { className: "space-y-4", children: isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Karindeling laden\u2026" })) : error ? (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Indeling onbekend" }), _jsx("p", { className: "text-sm text-muted-foreground", children: error.message })] })) : cov ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-start gap-x-6 gap-y-2 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Indeling" }), _jsx(PlanCoverageBadge, { coverage: summarize(cov) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Pickstatus" }), _jsxs("div", { className: "mt-1", children: [cov.pickStatus ? PICK_LABEL[cov.pickStatus] : "—", cov.pickStatus === "gepickt" && cov.pickedAt ? ` · ${fmt(cov.pickedAt)}` : ""] })] }), pickRows.length > 0 && (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Pickdocumenten" }), _jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: pickRows.map((p, i) => (_jsxs(Badge, { variant: "outline", className: "text-[10px]", title: [
50
+ p.printedAt ? `Geprint ${fmt(p.printedAt)}` : "Nog niet geprint",
51
+ p.completedAt ? `Afgerond ${fmt(p.completedAt)}` : null,
52
+ ]
53
+ .filter(Boolean)
54
+ .join(" · "), children: [p.documentNumber ?? "pickrij", p.completedAt ? " ✓" : ""] }, p.queueItemId ?? i))) })] }))] }), cov.state === "onbekend" && cov.error && (_jsx("p", { className: "text-sm text-muted-foreground", children: cov.error })), carts.map((c) => (_jsxs("div", { className: "rounded-md border border-border", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border bg-muted/30 px-3 py-2 text-sm", children: [_jsxs("button", { type: "button", className: "flex items-center gap-2 rounded-sm text-left hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: () => setLayersFor({ id: c.cartId, number: c.cartNumber ?? 0 }), title: "Bekijk de lagen van deze kar", children: [_jsx(LayoutGrid, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { className: "font-mono font-medium", children: ["Kar ", c.cartNumber ?? "—"] })] }), c.shared && _jsx(Badge, { variant: "outline", children: "Gedeelde bufferkar" }), c.carrierType && (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: c.carrierType }))] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel (deze order)" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Dozen" })] }) }), _jsx(TableBody, { children: (Array.isArray(c.lines) ? c.lines : []).map((l, i) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: resolveProductName(l.productName) ?? "—" }), _jsx(TableCell, { className: "text-right", children: l.crates }), _jsx(TableCell, { className: "text-right", children: l.boxes })] }, `${l.orderItemId ?? "x"}-${i}`))) })] })] }, c.cartId)))] })) : null }), _jsx(CartLayersDialog, { cartId: layersFor?.id ?? null, cartNumber: layersFor?.number ?? null, open: !!layersFor, onOpenChange: (v) => {
133
55
  if (!v)
134
56
  setLayersFor(null);
135
57
  } })] }));
@@ -0,0 +1,6 @@
1
+ import type { PlanCoverageSummary } from "../../lib/plan-coverage.js";
2
+ export declare function PlanCoverageBadge({ coverage, showReason, }: {
3
+ coverage: PlanCoverageSummary | null | undefined;
4
+ showReason?: boolean;
5
+ }): import("react").JSX.Element | null;
6
+ //# sourceMappingURL=PlanCoverageBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlanCoverageBadge.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/PlanCoverageBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,UAAiB,GAClB,EAAE;IACD,QAAQ,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,sCAkCA"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangle } from "lucide-react";
3
+ // Indelingsstatus zoals de Logistiek Engine die levert. Alleen weergave.
4
+ export function PlanCoverageBadge({ coverage, showReason = true, }) {
5
+ if (!coverage)
6
+ return null;
7
+ const { state, label, progressPct, reason, shared } = coverage;
8
+ const base = "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium";
9
+ if (state === "geen_indeling") {
10
+ return (_jsxs("div", { className: "mt-1 min-w-0", children: [_jsxs("span", { className: `${base} border-destructive/40 bg-destructive/10 text-destructive`, title: reason ?? undefined, children: [_jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }), label] }), showReason && reason && _jsx("div", { className: "mt-0.5 text-xs text-destructive", children: reason })] }));
11
+ }
12
+ const tone = state === "deels"
13
+ ? "border-primary/40 bg-primary/10 text-foreground"
14
+ : state === "ingedeeld"
15
+ ? "border-border bg-muted text-foreground"
16
+ : "border-border bg-muted/50 text-muted-foreground";
17
+ const text = state === "deels" && progressPct != null
18
+ ? `${label} · ${Math.round(progressPct)}%`
19
+ : state === "ingedeeld" && shared
20
+ ? `${label} · gedeeld met buffer`
21
+ : label;
22
+ return (_jsx("div", { className: "mt-1", children: _jsx("span", { className: `${base} ${tone}`, title: reason ?? undefined, children: text }) }));
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA0JnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,EACd,eAAuB,GACxB,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,+BA2YA"}
1
+ {"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA2JnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,EACd,eAAuB,GACxB,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,+BA8YA"}
@@ -12,6 +12,7 @@ import { OrderShipmentsCard } from "./OrderShipmentsCard";
12
12
  import { OrderControlStatusCard } from "./OrderControlStatusCard";
13
13
  import { OrderCarrierPicker } from "./OrderCarrierPicker";
14
14
  import { OrderPickupMomentPicker } from "./OrderPickupMomentPicker";
15
+ import { PlanCoverageBadge } from "./PlanCoverageBadge";
15
16
  function AddressCard({ icon, lines, extra, }) {
16
17
  return (_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: icon }), _jsxs("div", { className: "text-sm", children: [lines.length > 0 ? lines.map((l, i) => _jsx("div", { children: l }, i)) : _jsx("div", { children: "\u2014" }), extra && _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: extra })] })] }));
17
18
  }
@@ -225,7 +226,7 @@ export function SalesOrderDetailView({ order, sellerFallback, hideKarindeling =
225
226
  const bestellingLabel = orderStageLabel(order.stage ?? (order.status.isCancelled ? "cancelled" : "incoming"));
226
227
  return (_jsxs("div", { className: "space-y-6", children: [order.needsManualReview ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Handmatige beoordeling nodig" }), _jsxs("div", { className: "text-muted-foreground", children: [order.needsManualReview.reason, order.needsManualReview.at
227
228
  ? ` (geconstateerd op ${new Date(order.needsManualReview.at).toLocaleString("nl-NL")})`
228
- : ""] })] })) : null, order.mixedDeliveryDates ? (_jsxs("div", { className: "rounded-md border border-border bg-muted/40 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Bestellingen met verschillende leverdatums" }), _jsx("div", { className: "text-muted-foreground", children: "Deze order bestaat uit Floriday-bestellingen met verschillende leverdatums. De vroegste leverdatum is aangehouden." })] })) : null, (order.unlinkedProducts?.length ?? 0) > 0 ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Floriday-artikel niet gekoppeld aan product" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-muted-foreground", children: order.unlinkedProducts.map((u, i) => (_jsxs("li", { children: [u.name, " \u2014 trade-item-id ", u.tradeItemId ?? "onbekend"] }, i))) }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: "Koppel dit trade-item aan een product; de volgende synchronisatie neemt het dan over." })] })) : null, _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: [formatLoadUnits(order.fustCount, order.crateTypeName), " fusten"] }), _jsxs("div", { children: [formatLoadUnits(order.karCount, order.cartTypeName), " karren", order.karShared ? " (gedeeld met buffer)" : ""] })] }) }), _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: "Afleveradres", children: _jsx(AddressCard, { icon: _jsx(MapPin, { className: "h-5 w-5" }), lines: resolvedAddresses?.delivery
229
+ : ""] })] })) : null, order.mixedDeliveryDates ? (_jsxs("div", { className: "rounded-md border border-border bg-muted/40 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Bestellingen met verschillende leverdatums" }), _jsx("div", { className: "text-muted-foreground", children: "Deze order bestaat uit Floriday-bestellingen met verschillende leverdatums. De vroegste leverdatum is aangehouden." })] })) : null, (order.unlinkedProducts?.length ?? 0) > 0 ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Floriday-artikel niet gekoppeld aan product" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-muted-foreground", children: order.unlinkedProducts.map((u, i) => (_jsxs("li", { children: [u.name, " \u2014 trade-item-id ", u.tradeItemId ?? "onbekend"] }, i))) }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: "Koppel dit trade-item aan een product; de volgende synchronisatie neemt het dan over." })] })) : null, _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: [formatLoadUnits(order.fustCount, order.crateTypeName), " fusten"] }), _jsxs("div", { children: [formatLoadUnits(order.karCount, order.cartTypeName), " karren", order.karShared ? " (gedeeld met buffer)" : "", _jsx(PlanCoverageBadge, { coverage: order.planCoverage })] })] }) }), _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: "Afleveradres", children: _jsx(AddressCard, { icon: _jsx(MapPin, { className: "h-5 w-5" }), lines: resolvedAddresses?.delivery
229
230
  ? [
230
231
  resolvedAddresses.delivery.name,
231
232
  resolvedAddresses.delivery.line,
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAuBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAuGnE,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7C,qBA8SA"}
1
+ {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAuBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAwGnE,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7C,qBA+SA"}
@@ -6,6 +6,7 @@ import { useAllowedLoadCarriers } from "../../lib/useAllowedLoadCarriers.js";
6
6
  import { orderStageBadgeClass, orderStageLabel } from "../../lib/order-status.js";
7
7
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@flowselections/core";
8
8
  import { MoreVertical, AlertTriangle, ArrowDown } from "lucide-react";
9
+ import { PlanCoverageBadge } from "./PlanCoverageBadge";
9
10
  function formatDateShort(iso) {
10
11
  if (!iso)
11
12
  return { date: "—", time: "" };
@@ -109,7 +110,7 @@ export function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder
109
110
  .map((u) => `${u.name} (trade-item ${u.tradeItemId ?? "onbekend"})`)
110
111
  .join("\n"), children: [_jsx(AlertTriangle, { className: "mt-0.5 h-3 w-3 shrink-0" }), _jsxs("span", { children: ["Floriday-artikel niet gekoppeld aan product:", " ", row.unlinkedProducts
111
112
  .map((u) => `${u.name} (trade-item ${u.tradeItemId ?? "onbekend"})`)
112
- .join(", ")] })] }))] }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: row.engineCounts ? (_jsxs(_Fragment, { children: [formatLoadUnits(row.karCount, row.cartTypeName), row.karShared && (_jsx("span", { className: "ml-1 text-xs font-normal text-muted-foreground", children: "(gedeeld met buffer)" }))] })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }) }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: formatLoadUnits(row.fustCount, row.crateTypeName) }) }), _jsx(TableCell, { className: "text-sm text-foreground", children: row.carrierName || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { children: _jsx(StatusTag, { order: row }) }), showRevenue && (_jsxs(TableCell, { className: "font-medium text-foreground", children: ["\u20AC ", Math.round(row.revenue ?? 0).toLocaleString("nl-NL")] })), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm text-foreground", children: ordered.date }), _jsx("div", { className: "text-xs text-muted-foreground", children: isBuffer
113
+ .join(", ")] })] }))] }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium text-foreground", children: row.engineCounts && row.karCount > 0 ? (_jsxs(_Fragment, { children: [formatLoadUnits(row.karCount, row.cartTypeName), row.karShared && (_jsx("span", { className: "ml-1 text-xs font-normal text-muted-foreground", children: "(gedeeld met buffer)" }))] })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx(PlanCoverageBadge, { coverage: row.planCoverage })] }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: formatLoadUnits(row.fustCount, row.crateTypeName) }) }), _jsx(TableCell, { className: "text-sm text-foreground", children: row.carrierName || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { children: _jsx(StatusTag, { order: row }) }), showRevenue && (_jsxs(TableCell, { className: "font-medium text-foreground", children: ["\u20AC ", Math.round(row.revenue ?? 0).toLocaleString("nl-NL")] })), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm text-foreground", children: ordered.date }), _jsx("div", { className: "text-xs text-muted-foreground", children: isBuffer
113
114
  ? `${bufferSize} orders`
114
115
  : row.orderNumber || ordered.time })] }), _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, { disabled: isBuffer || !row.isLocal, onClick: () => !isBuffer && row.isLocal && onEditOrder?.(row), title: isBuffer
115
116
  ? "Buffer-orders kunnen niet gebundeld bewerkt worden."