@flowselections/floriday-verkoop-module 1.0.21 → 1.0.25

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 (95) hide show
  1. package/dist-lib/components/karindeling/AfmetingenTab.d.ts +6 -0
  2. package/dist-lib/components/karindeling/AfmetingenTab.d.ts.map +1 -0
  3. package/dist-lib/components/karindeling/AfmetingenTab.js +128 -0
  4. package/dist-lib/components/karindeling/KarindelingPage.d.ts +2 -0
  5. package/dist-lib/components/karindeling/KarindelingPage.d.ts.map +1 -0
  6. package/dist-lib/components/karindeling/KarindelingPage.js +102 -0
  7. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -1
  8. package/dist-lib/components/verkoop/OrderKarindelingCard.js +83 -37
  9. package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts.map +1 -1
  10. package/dist-lib/components/verkoop/SalesOrderDetailPage.js +59 -8
  11. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts +2 -1
  12. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
  13. package/dist-lib/components/verkoop/SalesOrderDetailView.js +2 -2
  14. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
  15. package/dist-lib/components/verkoop/SalesOrdersTable.js +36 -4
  16. package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
  17. package/dist-lib/components/verkoop/VerkoopOrdersPage.js +18 -2
  18. package/dist-lib/index.d.ts +1 -0
  19. package/dist-lib/index.d.ts.map +1 -1
  20. package/dist-lib/index.js +12 -1
  21. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2353 -374
  22. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/client.d.ts +2353 -374
  24. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/client.js +24 -9
  26. package/dist-lib/integrations/supabase/client.server.d.ts +2353 -374
  27. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/types.d.ts +2406 -376
  29. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.js +11 -9
  31. package/dist-lib/lib/cart-lock.functions.d.ts +2353 -374
  32. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/cart-planner.d.ts +9 -1
  34. package/dist-lib/lib/cart-planner.d.ts.map +1 -1
  35. package/dist-lib/lib/cart-planner.js +21 -4
  36. package/dist-lib/lib/cart-planning-settings.functions.d.ts +152282 -0
  37. package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -0
  38. package/dist-lib/lib/cart-planning-settings.functions.js +375 -0
  39. package/dist-lib/lib/cart-replan.functions.d.ts +18468 -651
  40. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/cart-replan.functions.js +85 -27
  42. package/dist-lib/lib/create-order.functions.d.ts +25367 -1619
  43. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/cron/index.d.ts +5 -0
  45. package/dist-lib/lib/cron/index.d.ts.map +1 -0
  46. package/dist-lib/lib/cron/index.js +4 -0
  47. package/dist-lib/lib/cron/mirror-floriday-orders.d.ts +4 -0
  48. package/dist-lib/lib/cron/mirror-floriday-orders.d.ts.map +1 -0
  49. package/dist-lib/lib/cron/mirror-floriday-orders.js +23 -0
  50. package/dist-lib/lib/cron/replan-carts.d.ts +4 -0
  51. package/dist-lib/lib/cron/replan-carts.d.ts.map +1 -0
  52. package/dist-lib/lib/cron/replan-carts.js +18 -0
  53. package/dist-lib/lib/cron/sync-salesorders.d.ts +4 -0
  54. package/dist-lib/lib/cron/sync-salesorders.d.ts.map +1 -0
  55. package/dist-lib/lib/cron/sync-salesorders.js +18 -0
  56. package/dist-lib/lib/cron/sync-tradeitems.d.ts +4 -0
  57. package/dist-lib/lib/cron/sync-tradeitems.d.ts.map +1 -0
  58. package/dist-lib/lib/cron/sync-tradeitems.js +18 -0
  59. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +6539 -602
  60. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floriday-auto-fulfillment.server.js +1 -1
  62. package/dist-lib/lib/floriday-central-token.server.d.ts +12 -0
  63. package/dist-lib/lib/floriday-central-token.server.d.ts.map +1 -0
  64. package/dist-lib/lib/floriday-central-token.server.js +58 -0
  65. package/dist-lib/lib/floriday-gateway.server.d.ts +2 -2
  66. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  67. package/dist-lib/lib/floriday-gateway.server.js +10 -37
  68. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +2353 -374
  69. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/floriday-logistic-labels.functions.js +14 -4
  71. package/dist-lib/lib/floriday-mirror.functions.d.ts +13846 -0
  72. package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -0
  73. package/dist-lib/lib/floriday-mirror.functions.js +278 -0
  74. package/dist-lib/lib/floriday-public.functions.d.ts +14907 -1054
  75. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  76. package/dist-lib/lib/floriday-sync.d.ts +0 -1
  77. package/dist-lib/lib/floriday-sync.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday-sync.js +1 -1
  79. package/dist-lib/lib/floriday-writes.functions.d.ts +10723 -828
  80. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  81. package/dist-lib/lib/order-buffers.functions.d.ts +41540 -0
  82. package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -0
  83. package/dist-lib/lib/order-buffers.functions.js +244 -0
  84. package/dist-lib/lib/order-shipments.functions.d.ts +8631 -715
  85. package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
  86. package/dist-lib/lib/salesorders-mapping.d.ts +3 -0
  87. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  88. package/dist-lib/lib/salesorders.functions.d.ts +10725 -828
  89. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  90. package/dist-lib/lib/salesorders.functions.js +129 -3
  91. package/dist-lib/lib/tradeitems.functions.d.ts +2353 -374
  92. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  93. package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
  94. package/dist-lib/lib/useSalesOrdersQuery.js +37 -2
  95. package/package.json +27 -5
@@ -0,0 +1,6 @@
1
+ import { type CartType, type CrateType } from "../../lib/cart-planning-settings.functions";
2
+ export declare function AfmetingenTab({ cartTypes, crateTypes, }: {
3
+ cartTypes: CartType[];
4
+ crateTypes: CrateType[];
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=AfmetingenTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AfmetingenTab.d.ts","sourceRoot":"","sources":["../../../src/components/karindeling/AfmetingenTab.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAQL,KAAK,QAAQ,EACb,KAAK,SAAS,EACf,MAAM,wCAAwC,CAAC;AAMhD,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,UAAU,GACX,EAAE;IACD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB,2CA+BA"}
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Separator, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
+ import { Star, Trash2 } from "lucide-react";
7
+ import { deleteCartType, deleteCrateType, listUsedCartCarriers, listUsedCratePackages, setDefaultCartType, upsertCartType, upsertCrateType, } from "../../lib/cart-planning-settings.functions";
8
+ function areaM2(lengthCm, widthCm) {
9
+ return ((lengthCm * widthCm) / 10000).toFixed(2);
10
+ }
11
+ export function AfmetingenTab({ cartTypes, crateTypes, }) {
12
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Karren / containers" }), _jsxs(CardDescription, { children: ["Stel per kar-type de laag-afmetingen in. Fusten per laag =", " ", _jsx("em", { children: "laag-oppervlakte / fust-oppervlakte" }), ". \u00C9\u00E9n type is de standaard en wordt gebruikt door de planner."] })] }), _jsx(CardContent, { className: "space-y-4", children: _jsx(CartTypesEditor, { cartTypes: cartTypes }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Fusten" }), _jsx(CardDescription, { children: "Stel per fust-type de afmetingen in. Wordt gecombineerd met het gekozen kar-type om te bepalen hoeveel fusten er op een laag passen (ook bij menging)." })] }), _jsx(CardContent, { className: "space-y-4", children: _jsx(CrateTypesEditor, { crateTypes: crateTypes }) })] })] }));
13
+ }
14
+ function CartTypesEditor({ cartTypes }) {
15
+ const qc = useQueryClient();
16
+ const upsert = useServerFn(upsertCartType);
17
+ const remove = useServerFn(deleteCartType);
18
+ const setDefault = useServerFn(setDefaultCartType);
19
+ const fetchCarriers = useServerFn(listUsedCartCarriers);
20
+ const { data: carriers = [] } = useQuery({
21
+ queryKey: ["used-cart-carriers"],
22
+ queryFn: () => fetchCarriers(),
23
+ });
24
+ const [name, setName] = useState("");
25
+ const [len, setLen] = useState(135);
26
+ const [wid, setWid] = useState(56);
27
+ const [layers, setLayers] = useState(4);
28
+ const invalidate = () => qc.invalidateQueries({ queryKey: ["cart-planning-bundle"] });
29
+ const addMut = useMutation({
30
+ mutationFn: () => upsert({
31
+ data: {
32
+ name: name.trim(),
33
+ layer_length_cm: len,
34
+ layer_width_cm: wid,
35
+ layers_per_cart: layers,
36
+ is_default: cartTypes.length === 0,
37
+ },
38
+ }),
39
+ onSuccess: () => {
40
+ toast.success("Kar-type opgeslagen");
41
+ setName("");
42
+ setLen(135);
43
+ setWid(56);
44
+ setLayers(4);
45
+ invalidate();
46
+ },
47
+ onError: (e) => toast.error(e?.message ?? "Fout"),
48
+ });
49
+ const delMut = useMutation({
50
+ mutationFn: (id) => remove({ data: { id } }),
51
+ onSuccess: invalidate,
52
+ });
53
+ const defMut = useMutation({
54
+ mutationFn: (id) => setDefault({ data: { id } }),
55
+ onSuccess: () => {
56
+ toast.success("Standaard bijgewerkt");
57
+ invalidate();
58
+ },
59
+ });
60
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-3 rounded-md border p-3 lg:grid-cols-5", children: [_jsxs("div", { className: "space-y-2 lg:col-span-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { list: "used-cart-carriers", value: name, onChange: (e) => {
61
+ const v = e.target.value;
62
+ setName(v);
63
+ const match = carriers.find((c) => c.label === v || c.name === v);
64
+ if (match) {
65
+ if (match.layer_length_cm)
66
+ setLen(match.layer_length_cm);
67
+ if (match.layer_width_cm)
68
+ setWid(match.layer_width_cm);
69
+ if (match.layers_per_cart)
70
+ setLayers(match.layers_per_cart);
71
+ }
72
+ }, placeholder: carriers.length > 0
73
+ ? "Kies uit lijst of typ zelf"
74
+ : "bv. Deense container" }), _jsx("datalist", { id: "used-cart-carriers", children: carriers.map((c) => (_jsx("option", { value: c.label, children: c.usage_count > 0 ? `${c.usage_count}× gebruikt` : "niet in gebruik" }, c.key))) }), carriers.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [carriers.length, " kar-types bekend uit Floriday; afmetingen worden automatisch ingevuld."] }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Lengte (cm)" }), _jsx(Input, { type: "number", min: 1, value: len, onChange: (e) => setLen(Number(e.target.value)) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Breedte (cm)" }), _jsx(Input, { type: "number", min: 1, value: wid, onChange: (e) => setWid(Number(e.target.value)) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Lagen / kar" }), _jsx(Input, { type: "number", min: 1, value: layers, onChange: (e) => setLayers(Number(e.target.value)) })] }), _jsx("div", { className: "lg:col-span-5", children: _jsx(Button, { disabled: !name.trim() || addMut.isPending, onClick: () => addMut.mutate(), children: "Toevoegen" }) })] }), _jsx(Separator, {}), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { className: "text-right", children: "Lengte" }), _jsx(TableHead, { className: "text-right", children: "Breedte" }), _jsx(TableHead, { className: "text-right", children: "Oppervlakte (m\u00B2)" }), _jsx(TableHead, { children: "Standaard" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: cartTypes.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "text-sm text-muted-foreground", children: "Nog geen kar-types." }) })) : (cartTypes.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: c.name }), _jsxs(TableCell, { className: "text-right", children: [c.layer_length_cm, " cm"] }), _jsxs(TableCell, { className: "text-right", children: [c.layer_width_cm, " cm"] }), _jsx(TableCell, { className: "text-right", children: areaM2(c.layer_length_cm, c.layer_width_cm) }), _jsx(TableCell, { children: c.is_default ? (_jsx(Badge, { children: "Standaard" })) : (_jsxs(Button, { size: "sm", variant: "ghost", onClick: () => defMut.mutate(c.id), disabled: defMut.isPending, children: [_jsx(Star, { className: "mr-1 h-4 w-4" }), " Maak standaard"] })) }), _jsx(TableCell, { children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => delMut.mutate(c.id), children: _jsx(Trash2, { className: "h-4 w-4" }) }) })] }, c.id)))) })] })] }));
75
+ }
76
+ function CrateTypesEditor({ crateTypes }) {
77
+ const qc = useQueryClient();
78
+ const upsert = useServerFn(upsertCrateType);
79
+ const remove = useServerFn(deleteCrateType);
80
+ const fetchUsed = useServerFn(listUsedCratePackages);
81
+ const { data: usedPackages = [] } = useQuery({
82
+ queryKey: ["used-crate-packages"],
83
+ queryFn: () => fetchUsed(),
84
+ });
85
+ const [name, setName] = useState("");
86
+ const [len, setLen] = useState(60);
87
+ const [wid, setWid] = useState(40);
88
+ const [plants, setPlants] = useState(1);
89
+ const invalidate = () => qc.invalidateQueries({ queryKey: ["cart-planning-bundle"] });
90
+ const addMut = useMutation({
91
+ mutationFn: () => upsert({
92
+ data: {
93
+ name: name.trim(),
94
+ length_cm: len,
95
+ width_cm: wid,
96
+ plants_per_crate: plants,
97
+ },
98
+ }),
99
+ onSuccess: () => {
100
+ toast.success("Fust-type opgeslagen");
101
+ setName("");
102
+ setLen(60);
103
+ setWid(40);
104
+ setPlants(1);
105
+ invalidate();
106
+ },
107
+ onError: (e) => toast.error(e?.message ?? "Fout"),
108
+ });
109
+ const delMut = useMutation({
110
+ mutationFn: (id) => remove({ data: { id } }),
111
+ onSuccess: invalidate,
112
+ });
113
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-3 rounded-md border p-3 lg:grid-cols-5", children: [_jsxs("div", { className: "space-y-2 lg:col-span-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { list: "used-crate-packages", value: name, onChange: (e) => {
114
+ const v = e.target.value;
115
+ setName(v);
116
+ const match = usedPackages.find((p) => p.label === v);
117
+ if (match) {
118
+ if (match.pieces_per_package)
119
+ setPlants(match.pieces_per_package);
120
+ if (match.length_cm)
121
+ setLen(match.length_cm);
122
+ if (match.width_cm)
123
+ setWid(match.width_cm);
124
+ }
125
+ }, placeholder: usedPackages.length > 0
126
+ ? "Kies uit lijst of typ zelf"
127
+ : "bv. Fust 40x60" }), _jsx("datalist", { id: "used-crate-packages", children: usedPackages.map((p) => (_jsx("option", { value: p.label, children: p.usage_count > 0 ? `${p.usage_count}× gebruikt` : "niet in gebruik" }, p.key))) }), usedPackages.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [usedPackages.length, " fusten bekend uit VBN-lijst; afmetingen worden automatisch ingevuld."] }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Lengte (cm)" }), _jsx(Input, { type: "number", min: 1, value: len, onChange: (e) => setLen(Number(e.target.value)) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Breedte (cm)" }), _jsx(Input, { type: "number", min: 1, value: wid, onChange: (e) => setWid(Number(e.target.value)) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Planten / fust" }), _jsx(Input, { type: "number", min: 1, value: plants, onChange: (e) => setPlants(Number(e.target.value)) })] }), _jsx("div", { className: "lg:col-span-5", children: _jsx(Button, { disabled: !name.trim() || addMut.isPending, onClick: () => addMut.mutate(), children: "Toevoegen" }) })] }), _jsx(Separator, {}), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { className: "text-right", children: "Lengte" }), _jsx(TableHead, { className: "text-right", children: "Breedte" }), _jsx(TableHead, { className: "text-right", children: "Oppervlakte (m\u00B2)" }), _jsx(TableHead, { className: "text-right", children: "Planten / fust" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: crateTypes.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "text-sm text-muted-foreground", children: "Nog geen fust-types." }) })) : (crateTypes.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: c.name }), _jsxs(TableCell, { className: "text-right", children: [c.length_cm, " cm"] }), _jsxs(TableCell, { className: "text-right", children: [c.width_cm, " cm"] }), _jsx(TableCell, { className: "text-right", children: areaM2(c.length_cm, c.width_cm) }), _jsx(TableCell, { className: "text-right", children: c.plants_per_crate }), _jsx(TableCell, { children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => delMut.mutate(c.id), children: _jsx(Trash2, { className: "h-4 w-4" }) }) })] }, c.id)))) })] })] }));
128
+ }
@@ -0,0 +1,2 @@
1
+ export declare function KarindelingPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=KarindelingPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KarindelingPage.d.ts","sourceRoot":"","sources":["../../../src/components/karindeling/KarindelingPage.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,eAAe,4CAuD9B"}
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
+ import { Trash2 } from "lucide-react";
7
+ import { deletePickTime, getCartPlanningBundle, updateCartPlanningSettings, upsertPickTime, } from "../../lib/cart-planning-settings.functions";
8
+ import { rebufferAllOrders } from "../../lib/order-buffers.functions";
9
+ import { AfmetingenTab } from "./AfmetingenTab";
10
+ export function KarindelingPage() {
11
+ const fetchBundle = useServerFn(getCartPlanningBundle);
12
+ const { data, isLoading } = useQuery({
13
+ queryKey: ["cart-planning-bundle"],
14
+ queryFn: () => fetchBundle(),
15
+ });
16
+ return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Karindeling" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Bepaal hoe de karplanner orders indeelt op karren." })] }), _jsxs(Tabs, { defaultValue: "regels", className: "space-y-4", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "regels", children: "Regels" }), _jsx(TabsTrigger, { value: "afmetingen", children: "Afmetingen" }), _jsx(TabsTrigger, { value: "picktijden", children: "Picktijden" }), _jsx(TabsTrigger, { value: "combineren", children: "Combineren" }), _jsx(TabsTrigger, { value: "orderbuffer", children: "Orderbuffer" }), _jsx(TabsTrigger, { value: "pickstation", children: "Pickstation" })] }), isLoading || !data ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(TabsContent, { value: "regels", children: _jsx(RegelsTab, { settings: data.settings }) }), _jsx(TabsContent, { value: "afmetingen", children: _jsx(AfmetingenTab, { cartTypes: data.cartTypes, crateTypes: data.crateTypes }) }), _jsx(TabsContent, { value: "picktijden", children: _jsx(PicktijdenTab, { settings: data.settings, pickTimes: data.pickTimes }) }), _jsx(TabsContent, { value: "combineren", children: _jsx(CombinerenTab, { settings: data.settings }) }), _jsx(TabsContent, { value: "orderbuffer", children: _jsx(OrderbufferTab, { settings: data.settings }) }), _jsx(TabsContent, { value: "pickstation", children: _jsx(PickstationTab, { settings: data.settings }) })] }))] })] }));
17
+ }
18
+ function useSaveSettings() {
19
+ const qc = useQueryClient();
20
+ const save = useServerFn(updateCartPlanningSettings);
21
+ return useMutation({
22
+ mutationFn: (input) => save({ data: input }),
23
+ onSuccess: () => {
24
+ toast.success("Opgeslagen");
25
+ qc.invalidateQueries({ queryKey: ["cart-planning-bundle"] });
26
+ },
27
+ onError: (e) => toast.error(e?.message ?? "Opslaan mislukt"),
28
+ });
29
+ }
30
+ function stripId(s) {
31
+ const { id: _id, ...rest } = s;
32
+ return rest;
33
+ }
34
+ function RegelsTab({ settings }) {
35
+ const [s, setS] = useState(settings);
36
+ const mut = useSaveSettings();
37
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Globale planner-regels" }), _jsx(CardDescription, { children: "Deze regels gelden voor alle orders die automatisch op karren worden ingedeeld." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Max % per partij per laag" }), _jsx(Input, { type: "number", min: 1, max: 100, value: s.max_party_share_per_layer_pct, onChange: (e) => setS({ ...s, max_party_share_per_layer_pct: Number(e.target.value) }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Hoeveel van \u00E9\u00E9n laag mag door \u00E9\u00E9n partij bezet worden. Standaard 50%." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Max verschillende beladingen per laag" }), _jsx(Input, { type: "number", min: 1, max: 999, value: s.max_beladingen_per_layer >= 999 ? "" : s.max_beladingen_per_layer, disabled: s.max_beladingen_per_layer >= 999, onChange: (e) => setS({ ...s, max_beladingen_per_layer: Number(e.target.value) }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: s.max_beladingen_per_layer >= 999, onCheckedChange: (v) => setS({ ...s, max_beladingen_per_layer: v ? 999 : 2 }) }), "Onbeperkt aantal soorten per belading"] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Hoeveel verschillende artikelen samen in \u00E9\u00E9n laag mogen. Standaard 2." })] }), _jsxs("div", { className: "space-y-2 lg:col-span-2", children: [_jsx(Label, { children: "Optimalisatie-strategie" }), _jsxs(Select, { value: s.optimization_strategy, onValueChange: (v) => setS({ ...s, optimization_strategy: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "min_carts", children: "Minimaal aantal karren" }), _jsx(SelectItem, { value: "min_mixing", children: "Minimaal mengen (voorkeur voor pure karren)" })] })] })] })] }), _jsx(Button, { disabled: mut.isPending, onClick: () => mut.mutate(stripId(s)), children: "Opslaan" })] })] }));
38
+ }
39
+ function PicktijdenTab({ settings, pickTimes, }) {
40
+ const qc = useQueryClient();
41
+ const upsert = useServerFn(upsertPickTime);
42
+ const remove = useServerFn(deletePickTime);
43
+ const [potSize, setPotSize] = useState("");
44
+ const [minutes, setMinutes] = useState(0);
45
+ const saveSettings = useSaveSettings();
46
+ const addMut = useMutation({
47
+ mutationFn: () => upsert({ data: { pot_size: potSize.trim(), seconds_per_cart: Math.round(minutes * 60) } }),
48
+ onSuccess: () => {
49
+ toast.success("Toegevoegd");
50
+ setPotSize("");
51
+ setMinutes(0);
52
+ qc.invalidateQueries({ queryKey: ["cart-planning-bundle"] });
53
+ },
54
+ onError: (e) => toast.error(e?.message ?? "Fout"),
55
+ });
56
+ const delMut = useMutation({
57
+ mutationFn: (id) => remove({ data: { id } }),
58
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["cart-planning-bundle"] }),
59
+ });
60
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Picktijden" }), _jsx(CardDescription, { children: "Kies hoe je de picksnelheid meet: per potmaat (minuten per kar) of als planten per uur." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Meetmethode" }), _jsxs(Select, { value: settings.pick_time_mode, onValueChange: (v) => saveSettings.mutate({ ...stripId(settings), pick_time_mode: v }), children: [_jsx(SelectTrigger, { className: "max-w-md", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "per_pot_size", children: "Minuten per kar per potmaat" }), _jsx(SelectItem, { value: "plants_per_hour", children: "Planten per uur" }), _jsx(SelectItem, { value: "plants_per_day", children: "Planten per dag" })] })] })] }), settings.pick_time_mode === "plants_per_hour" ? (_jsx(PlantsPerHourEditor, { settings: settings })) : settings.pick_time_mode === "plants_per_day" ? (_jsx(PlantsPerDayEditor, { settings: settings })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-end gap-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Potmaat" }), _jsx(Input, { value: potSize, onChange: (e) => setPotSize(e.target.value), placeholder: "bv. P12" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Minuten per kar per medewerker" }), _jsx(Input, { type: "number", min: 0, step: 0.5, value: minutes, onChange: (e) => setMinutes(Number(e.target.value)) })] }), _jsx(Button, { disabled: addMut.isPending || !potSize.trim(), onClick: () => addMut.mutate(), children: "Toevoegen / bijwerken" })] }), _jsx(Separator, {}), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Potmaat" }), _jsx(TableHead, { className: "text-right", children: "Minuten / kar / medewerker" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: pickTimes.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, className: "text-sm text-muted-foreground", children: "Nog geen picktijden ingesteld." }) })) : (pickTimes.map((pt) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: pt.pot_size }), _jsx(TableCell, { className: "text-right", children: (pt.seconds_per_cart / 60).toLocaleString("nl-NL", { maximumFractionDigits: 2 }) }), _jsx(TableCell, { children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => delMut.mutate(pt.id), children: _jsx(Trash2, { className: "h-4 w-4" }) }) })] }, pt.id)))) })] })] }))] })] }));
61
+ }
62
+ function PlantsPerHourEditor({ settings }) {
63
+ const [pph, setPph] = useState(settings.plants_per_hour);
64
+ const save = useSaveSettings();
65
+ return (_jsxs("div", { className: "flex items-end gap-2 rounded-md border p-3", children: [_jsxs("div", { className: "space-y-2 flex-1 max-w-md", children: [_jsx(Label, { children: "Planten per uur per medewerker" }), _jsx(Input, { type: "number", min: 0, value: pph, onChange: (e) => setPph(Number(e.target.value)) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt gebruikt om de totale picktijd te schatten op basis van het aantal planten." })] }), _jsx(Button, { disabled: save.isPending, onClick: () => save.mutate({ ...stripId(settings), plants_per_hour: pph }), children: "Opslaan" })] }));
66
+ }
67
+ function PlantsPerDayEditor({ settings }) {
68
+ const [ppd, setPpd] = useState(settings.plants_per_day ?? 0);
69
+ const save = useSaveSettings();
70
+ return (_jsxs("div", { className: "flex items-end gap-2 rounded-md border p-3", children: [_jsxs("div", { className: "space-y-2 flex-1 max-w-md", children: [_jsx(Label, { children: "Planten per dag per medewerker" }), _jsx(Input, { type: "number", min: 0, value: ppd, onChange: (e) => setPpd(Number(e.target.value)) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt gebruikt om de totale picktijd te schatten op basis van het aantal planten per dag." })] }), _jsx(Button, { disabled: save.isPending, onClick: () => save.mutate({ ...stripId(settings), plants_per_day: ppd }), children: "Opslaan" })] }));
71
+ }
72
+ function CombinerenTab({ settings }) {
73
+ const [s, setS] = useState(settings);
74
+ const mut = useSaveSettings();
75
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Orders combineren op \u00E9\u00E9n kar" }), _jsx(CardDescription, { children: "Wanneer mogen order_items van verschillende (Floriday-)orders op dezelfde kar samengebracht worden?" })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md border p-3", children: [_jsxs("div", { children: [_jsx(Label, { children: "Floriday-orders combineren" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aan = de planner mag Floriday-orders bundelen; uit = elke order krijgt zijn eigen karren." })] }), _jsx(Switch, { checked: s.combine_floriday_orders, onCheckedChange: (v) => setS({ ...s, combine_floriday_orders: v }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Combineer-venster" }), _jsxs(Select, { value: s.combine_floriday_orders_window, onValueChange: (v) => setS({ ...s, combine_floriday_orders_window: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "same_day", children: "Zelfde leverdag" }), _jsx(SelectItem, { value: "same_delivery_slot", children: "Zelfde levermoment" }), _jsx(SelectItem, { value: "same_week", children: "Zelfde week" })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bereik" }), _jsxs(Select, { value: s.combine_orders_scope, onValueChange: (v) => setS({ ...s, combine_orders_scope: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "same_customer", children: "Alleen zelfde klant" }), _jsx(SelectItem, { value: "same_delivery_location", children: "Zelfde leverlocatie" }), _jsx(SelectItem, { value: "never", children: "Nooit combineren" })] })] })] }), _jsx(Separator, {}), _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Vanaf hoeveel lagen krijgt een klant een eigen kar?" }), _jsx(Input, { type: "number", min: 1, value: s.own_cart_from_layers, onChange: (e) => setS({ ...s, own_cart_from_layers: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Maximaal aantal klanten op \u00E9\u00E9n kar" }), _jsx(Input, { type: "number", min: 1, value: s.max_customers_per_cart, onChange: (e) => setS({ ...s, max_customers_per_cart: Number(e.target.value) }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Klanten per laag" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: s.max_one_customer_per_layer ? "outline" : "default", onClick: () => setS({ ...s, max_one_customer_per_layer: false }), children: "Meerdere klanten op \u00E9\u00E9n laag" }), _jsx(Button, { variant: s.max_one_customer_per_layer ? "default" : "outline", onClick: () => setS({ ...s, max_one_customer_per_layer: true }), children: "Max \u00E9\u00E9n klant per laag" })] })] }), _jsxs("div", { className: "grid gap-3 lg:grid-cols-2", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: s.require_same_carrier, onCheckedChange: (v) => setS({ ...s, require_same_carrier: !!v }) }), "Alleen dezelfde vervoerder"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: s.require_same_delivery_location, onCheckedChange: (v) => setS({ ...s, require_same_delivery_location: !!v }) }), "Alleen dezelfde afleverlocatie"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: s.require_same_crate_type, onCheckedChange: (v) => setS({ ...s, require_same_crate_type: !!v }) }), "Alleen hetzelfde fusttype"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: s.require_same_delivery_date, onCheckedChange: (v) => setS({ ...s, require_same_delivery_date: !!v }) }), "Alleen dezelfde leverdatum"] })] }), _jsx(Button, { disabled: mut.isPending, onClick: () => mut.mutate(stripId(s)), children: "Opslaan" })] })] }));
76
+ }
77
+ function PickstationTab({ settings }) {
78
+ const [s, setS] = useState(settings);
79
+ const mut = useSaveSettings();
80
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Na verzending naar pickstation" }), _jsx(CardDescription, { children: "Wanneer een kar-pakbon eenmaal geprint is voor het pickstation, mag de karindeling dan nog wijzigen?" })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md border p-3", children: [_jsxs("div", { children: [_jsx(Label, { children: "Wijzigen na pickstation-print toestaan" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Standaard uit: zodra de pakbon geprint is wordt de karindeling vergrendeld." })] }), _jsx(Switch, { checked: s.allow_editing_after_pickstation_send, onCheckedChange: (v) => setS({ ...s, allow_editing_after_pickstation_send: v }) })] }), _jsx(Badge, { variant: "outline", children: s.allow_editing_after_pickstation_send ? "Bewerken toegestaan" : "Vergrendeld na print" }), _jsx("div", { children: _jsx(Button, { disabled: mut.isPending, onClick: () => mut.mutate(stripId(s)), children: "Opslaan" }) })] })] }));
81
+ }
82
+ function OrderbufferTab({ settings }) {
83
+ const [s, setS] = useState(settings);
84
+ const mut = useSaveSettings();
85
+ const rebuffer = useServerFn(rebufferAllOrders);
86
+ const [rebuffering, setRebuffering] = useState(false);
87
+ const save = async () => {
88
+ await mut.mutateAsync(stripId(s));
89
+ setRebuffering(true);
90
+ try {
91
+ const res = await rebuffer();
92
+ toast.success(`Buffer bijgewerkt · ${res.processed} order(s) hergegroepeerd`);
93
+ }
94
+ catch (err) {
95
+ toast.error(`Herbufferen faalde: ${err.message}`);
96
+ }
97
+ finally {
98
+ setRebuffering(false);
99
+ }
100
+ };
101
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Orderbuffer" }), _jsx(CardDescription, { children: "Bepaal wanneer orders in dezelfde buffer worden geplaatst voordat ze op karren worden ingedeeld." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Wanneer horen orders in dezelfde buffer?" }), _jsxs(Select, { value: s.buffer_grouping_mode, onValueChange: (v) => setS({ ...s, buffer_grouping_mode: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "same_pickup_and_location", children: "Zelfde ophaalmoment \u00E9n zelfde afleverlocatie (aanbevolen)" }), _jsx(SelectItem, { value: "same_pickup", children: "Alleen zelfde ophaalmoment" }), _jsx(SelectItem, { value: "same_location", children: "Alleen zelfde afleverlocatie" }), _jsx(SelectItem, { value: "same_customer", children: "Alleen zelfde klant" }), _jsx(SelectItem, { value: "never", children: "Nooit samenvoegen \u2014 elke order eigen buffer" })] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Na opslaan worden alle bestaande orders opnieuw in de juiste buffer geplaatst. De karindeling per buffer wordt bij de eerstvolgende (her)berekening opnieuw uitgevoerd." })] }), _jsx(Button, { disabled: mut.isPending || rebuffering, onClick: save, children: rebuffering ? "Bezig met herbufferen…" : "Opslaan" })] })] }));
102
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AA8F/D,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,2CAgLvE"}
1
+ {"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAuG/D,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,2CA4OvE"}
@@ -76,27 +76,53 @@ export function OrderKarindelingCard({ order }) {
76
76
  const [lockedAt, setLockedAt] = useState(null);
77
77
  const [dbItems, setDbItems] = useState(null);
78
78
  const [plannerInputs, setPlannerInputs] = useState(null);
79
- // Laadt order_items + gekoppelde products (voorraad-belading) en
80
- // ververst live bij mutaties op order_items, orders of products.
79
+ const [bufferOrders, setBufferOrders] = useState([]);
80
+ const [, setBufferId] = useState(null);
81
81
  useEffect(() => {
82
82
  if (!orderId)
83
83
  return;
84
84
  let active = true;
85
85
  const load = async () => {
86
- const [{ data: itemRows }, { data: orderRow }] = await Promise.all([
87
- supabase
88
- .from("order_items")
89
- .select("id, product_id, product_name, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart)")
90
- .eq("order_id", orderId),
91
- supabase.from("orders").select("plan_locked_at").eq("id", orderId).maybeSingle(),
92
- ]);
86
+ // 1) Bepaal de buffer van deze order.
87
+ const { data: orderRow } = await supabase
88
+ .from("orders")
89
+ .select("plan_locked_at, buffer_id")
90
+ .eq("id", orderId)
91
+ .maybeSingle();
92
+ if (!active)
93
+ return;
94
+ const bid = orderRow?.buffer_id ?? null;
95
+ setLockedAt(orderRow?.plan_locked_at ?? null);
96
+ setBufferId(bid);
97
+ // 2) Verzamel alle orders in deze buffer (of alleen deze order als er
98
+ // geen buffer bekend is).
99
+ let orderIds = [orderId];
100
+ let siblingOrders = [];
101
+ if (bid) {
102
+ const { data: sibs } = await supabase
103
+ .from("orders")
104
+ .select("id, order_number")
105
+ .eq("buffer_id", bid)
106
+ .is("deleted_at", null);
107
+ siblingOrders = (sibs ?? []).map((s) => ({
108
+ id: s.id,
109
+ order_number: s.order_number,
110
+ }));
111
+ if (siblingOrders.length > 0)
112
+ orderIds = siblingOrders.map((s) => s.id);
113
+ }
114
+ if (!active)
115
+ return;
116
+ setBufferOrders(siblingOrders);
117
+ // 3) Items van alle orders in de buffer.
118
+ const { data: itemRows } = await supabase
119
+ .from("order_items")
120
+ .select("id, order_id, product_id, product_name, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart)")
121
+ .in("order_id", orderIds);
93
122
  if (!active)
94
123
  return;
95
124
  const items = itemRows ?? [];
96
125
  setDbItems(items);
97
- setLockedAt(orderRow?.plan_locked_at ?? null);
98
- // Alle order_items doorgeven — expand-helper resolveert zelf groepen
99
- // (via product_id óf via naamprefix "[Groep] ...") en losse artikelen.
100
126
  const raws = items.map((it) => ({
101
127
  id: it.id,
102
128
  product_id: it.product_id,
@@ -114,8 +140,8 @@ export function OrderKarindelingCard({ order }) {
114
140
  void load();
115
141
  const channel = supabase
116
142
  .channel(`order-karindeling-${orderId}`)
117
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "orders", filter: `id=eq.${orderId}` }, (payload) => setLockedAt(payload.new?.plan_locked_at ?? null))
118
- .on("postgres_changes", { event: "*", schema: "public", table: "order_items", filter: `order_id=eq.${orderId}` }, () => void load())
143
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "orders" }, () => void load())
144
+ .on("postgres_changes", { event: "*", schema: "public", table: "order_items" }, () => void load())
119
145
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "products" }, () => void load())
120
146
  .subscribe();
121
147
  return () => {
@@ -123,36 +149,56 @@ export function OrderKarindelingCard({ order }) {
123
149
  void supabase.removeChannel(channel);
124
150
  };
125
151
  }, [orderId]);
126
- // Bouw plannerinput. Bij een groep-order_item zijn de sub-items al
127
- // opgesplitst met member-belading; anders is er één regel per item.
152
+ // Map: order_item_id -> order_id, en order_id -> order_number.
153
+ const itemToOrder = useMemo(() => {
154
+ const m = new Map();
155
+ for (const it of dbItems ?? [])
156
+ m.set(it.id, it.order_id);
157
+ return m;
158
+ }, [dbItems]);
159
+ const orderNumberById = useMemo(() => {
160
+ const m = new Map();
161
+ for (const o of bufferOrders)
162
+ m.set(o.id, o.order_number);
163
+ return m;
164
+ }, [bufferOrders]);
128
165
  const plannerCarts = useMemo(() => {
129
- if (!plannerInputs || plannerInputs.length === 0) {
130
- if (dbItems && dbItems.length > 0)
131
- return null;
166
+ if (!plannerInputs || plannerInputs.length === 0)
132
167
  return null;
133
- }
134
168
  return planCarts(plannerInputs).carts;
135
- }, [plannerInputs, dbItems]);
169
+ }, [plannerInputs]);
136
170
  const floridayCarts = useMemo(() => deriveCartsFromFloriday(order), [order]);
137
171
  const usingVoorraad = plannerCarts !== null;
138
172
  const carts = usingVoorraad
139
- ? plannerCarts.map((c) => ({
140
- cart_number: c.cart_number,
141
- is_complete: c.is_complete,
142
- occupancy_pct: c.occupancy_pct,
143
- total_layers_used: c.total_layers_used,
144
- items: c.items.map((i) => ({
145
- product_name: i.product_name,
146
- crates: i.crates,
147
- plants: i.plants,
148
- plants_per_crate: i.plants_per_crate,
149
- crates_per_layer: i.crates_per_layer,
150
- layers_per_cart: i.layers_per_cart,
151
- layer_share_pct: i.layer_share_pct,
152
- })),
153
- }))
173
+ ? plannerCarts.map((c) => {
174
+ const orderIds = new Set();
175
+ const items = c.items.map((i) => {
176
+ const oid = itemToOrder.get(i.order_item_id) ?? null;
177
+ if (oid)
178
+ orderIds.add(oid);
179
+ return {
180
+ product_name: i.product_name,
181
+ crates: i.crates,
182
+ plants: i.plants,
183
+ plants_per_crate: i.plants_per_crate,
184
+ crates_per_layer: i.crates_per_layer,
185
+ layers_per_cart: i.layers_per_cart,
186
+ layer_share_pct: i.layer_share_pct,
187
+ order_id: oid,
188
+ order_number: oid ? orderNumberById.get(oid) ?? null : null,
189
+ };
190
+ });
191
+ return {
192
+ cart_number: c.cart_number,
193
+ is_complete: c.is_complete,
194
+ occupancy_pct: c.occupancy_pct,
195
+ total_layers_used: c.total_layers_used,
196
+ items,
197
+ order_ids: [...orderIds],
198
+ };
199
+ })
154
200
  : floridayCarts;
155
201
  const source = usingVoorraad ? "voorraad" : "floriday";
156
202
  const lockedDate = lockedAt ? new Date(lockedAt).toLocaleString("nl-NL") : null;
157
- 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))) })) })] }));
203
+ 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] })), bufferOrders.length > 1 && (_jsxs(Badge, { variant: "outline", title: bufferOrders.map((o) => o.order_number).join(", "), children: ["Buffer \u00B7 ", bufferOrders.length, " orders"] })), _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: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: i.product_name }), i.order_number && bufferOrders.length > 1 && (_jsx(Badge, { variant: i.order_id === orderId ? "default" : "outline", className: "text-[10px]", children: i.order_number }))] }) }), _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))) })) })] }));
158
204
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrderDetailPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailPage.tsx"],"names":[],"mappings":"AASA,wBAAgB,oBAAoB,4CA4EnC"}
1
+ {"version":3,"file":"SalesOrderDetailPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailPage.tsx"],"names":[],"mappings":"AAkBA,wBAAgB,oBAAoB,4CAsLnC"}
@@ -1,12 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useMemo, useState } from "react";
3
3
  import { Link, useParams } from "@tanstack/react-router";
4
- import { Button } from "@flowselections/core";
5
- import { ArrowLeft, Pencil } from "lucide-react";
4
+ import { Badge, Button } from "@flowselections/core";
5
+ import { ArrowLeft, Pencil, Layers, ChevronDown, ChevronRight, ImageIcon } from "lucide-react";
6
6
  import { SalesOrderDetailView } from "../../components/verkoop/SalesOrderDetailView";
7
+ import { OrderKarindelingCard } from "../../components/verkoop/OrderKarindelingCard";
7
8
  import { EditLocalOrderDialog } from "../../components/verkoop/EditLocalOrderDialog";
8
9
  import { useSalesOrdersQuery } from "../../lib/useSalesOrdersQuery";
9
10
  import { useSalesOrderDetailQuery } from "../../lib/useSalesOrderDetailQuery";
11
+ const eur = new Intl.NumberFormat("nl-NL", {
12
+ style: "currency",
13
+ currency: "EUR",
14
+ minimumFractionDigits: 2,
15
+ maximumFractionDigits: 2,
16
+ });
10
17
  export function SalesOrderDetailPage() {
11
18
  const { salesOrderId } = useParams({
12
19
  from: "/_authenticated/floriday-verkoop/verkooporders/$salesOrderId",
@@ -16,20 +23,64 @@ export function SalesOrderDetailPage() {
16
23
  const isFloriday = !isLocal && !isMailboxProposal;
17
24
  // Floriday-orders: gerichte, snelle per-order fetch.
18
25
  const detailQuery = useSalesOrderDetailQuery(isFloriday ? salesOrderId : null);
19
- // Lokale/mailbox-orders: nog even via de lijst-hook (kleine dataset lokaal).
26
+ // Lijst-query gebruiken we altijd: voor lokale/mailbox-orders als bron,
27
+ // en voor Floriday-orders om buffer-siblings te vinden.
20
28
  const listQuery = useSalesOrdersQuery();
21
- const listQueryEnabled = !isFloriday;
22
29
  const isLoading = isFloriday ? detailQuery.isLoading : listQuery.isLoading;
23
30
  const error = isFloriday ? detailQuery.error : listQuery.error;
24
- const order = isFloriday
31
+ const primary = isFloriday
25
32
  ? (detailQuery.data?.order ?? null)
26
- : (listQueryEnabled ? listQuery.data?.orders.find((o) => o.id === salesOrderId) ?? null : null);
33
+ : (listQuery.data?.orders.find((o) => o.id === salesOrderId) ?? null);
27
34
  const sellerFallback = isFloriday
28
35
  ? (detailQuery.data?.sellerFallback ?? null)
29
36
  : (listQuery.data?.sellerFallback ?? null);
37
+ // Buffer-siblings: voor Floriday-orders levert de detailQuery zelf al de
38
+ // volledige buffer op (server-side gejoined via de orders-tabel), zodat we
39
+ // niet meer afhankelijk zijn van de tragere/gefilterde lijst-query.
40
+ const bufferId = useMemo(() => {
41
+ if (isFloriday) {
42
+ return detailQuery.data?.bufferId ?? primary?.bufferId ?? null;
43
+ }
44
+ if (primary?.bufferId)
45
+ return primary.bufferId;
46
+ const all = listQuery.data?.orders ?? [];
47
+ return all.find((o) => o.id === salesOrderId)?.bufferId ?? null;
48
+ }, [isFloriday, detailQuery.data?.bufferId, primary, listQuery.data?.orders, salesOrderId]);
49
+ const bufferOrders = useMemo(() => {
50
+ if (!bufferId)
51
+ return primary ? [primary] : [];
52
+ const head = primary;
53
+ if (isFloriday) {
54
+ const siblings = detailQuery.data?.bufferSiblings ?? [];
55
+ const rest = siblings
56
+ .filter((o) => o.id !== (primary?.id ?? salesOrderId))
57
+ .sort((a, b) => (b.orderedAt || "").localeCompare(a.orderedAt || ""));
58
+ return head ? [head, ...rest] : rest;
59
+ }
60
+ const all = listQuery.data?.orders ?? [];
61
+ const siblings = all.filter((o) => o.bufferId === bufferId);
62
+ if (siblings.length === 0)
63
+ return primary ? [primary] : [];
64
+ const rest = siblings
65
+ .filter((o) => o.id !== (primary?.id ?? salesOrderId))
66
+ .sort((a, b) => (b.orderedAt || "").localeCompare(a.orderedAt || ""));
67
+ const localHead = primary ?? siblings.find((o) => o.id === salesOrderId) ?? siblings[0];
68
+ return [localHead, ...rest];
69
+ }, [primary, bufferId, isFloriday, detailQuery.data?.bufferSiblings, listQuery.data?.orders, salesOrderId]);
70
+ const isBuffer = bufferOrders.length > 1;
71
+ const totals = useMemo(() => {
72
+ const revenue = bufferOrders.reduce((s, o) => s + (o.revenue ?? 0), 0);
73
+ const karren = bufferOrders.reduce((s, o) => s + (o.karCount ?? 0), 0);
74
+ const fusten = bufferOrders.reduce((s, o) => s + (o.fustCount ?? 0), 0);
75
+ return { revenue, karren, fusten };
76
+ }, [bufferOrders]);
30
77
  const [editOpen, setEditOpen] = useState(false);
31
78
  const rawLocalId = isLocal && salesOrderId.startsWith("local:")
32
79
  ? salesOrderId.slice("local:".length)
33
80
  : null;
34
- return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/floriday-verkoop/verkooporders", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar verkooporders"] }) }), rawLocalId && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Bestelling bewerken"] }))] }), isLoading && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Laden\u2026" })), error && (_jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: error.message })), !isLoading && !error && !order && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Order niet gevonden." })), order && (_jsx(SalesOrderDetailView, { order: order, sellerFallback: sellerFallback })), rawLocalId && (_jsx(EditLocalOrderDialog, { orderId: rawLocalId, open: editOpen, onOpenChange: setEditOpen }))] }));
81
+ return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/floriday-verkoop/verkooporders", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar verkooporders"] }) }), rawLocalId && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Bestelling bewerken"] }))] }), isLoading && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Laden\u2026" })), error && (_jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: error.message })), !isLoading && !error && !primary && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Order niet gevonden." })), primary && isBuffer && (_jsxs("div", { className: "rounded-lg border border-primary/30 bg-primary/5 p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsx(Layers, { className: "h-4 w-4 text-primary" }), _jsx("span", { className: "text-sm font-semibold text-foreground", children: "Orderbuffer" }), _jsxs(Badge, { variant: "outline", className: "border-primary/40 text-primary", children: [bufferOrders.length, " orders samengevoegd"] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3 text-sm md:grid-cols-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground", children: "Totale omzet" }), _jsx("div", { className: "font-semibold text-foreground", children: eur.format(totals.revenue) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground", children: "Karren" }), _jsx("div", { className: "font-semibold text-foreground", children: totals.karren })] }), _jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground", children: "Fusten" }), _jsx("div", { className: "font-semibold text-foreground", children: totals.fusten })] }), _jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground", children: "Klant" }), _jsx("div", { className: "font-semibold text-foreground", children: primary.customer.name })] })] }), _jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: bufferOrders.map((o) => (_jsx(Badge, { variant: "outline", className: "text-xs", children: o.orderNumber || o.id.slice(0, 8) }, o.id))) })] })), primary && !isBuffer && (_jsx(SalesOrderDetailView, { order: primary, sellerFallback: sellerFallback })), primary && isBuffer && (_jsxs("div", { className: "grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "text-sm font-semibold text-foreground", children: ["Orders in deze buffer (", bufferOrders.length, ")"] }), bufferOrders.map((o) => (_jsx(BufferOrderRow, { order: o, sellerFallback: sellerFallback }, o.id)))] }), _jsxs("div", { className: "space-y-3 xl:sticky xl:top-4 xl:self-start", children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: "Gecombineerde karindeling" }), _jsx(OrderKarindelingCard, { order: primary })] })] })), rawLocalId && (_jsx(EditLocalOrderDialog, { orderId: rawLocalId, open: editOpen, onOpenChange: setEditOpen }))] }));
82
+ }
83
+ function BufferOrderRow({ order, sellerFallback, }) {
84
+ const [open, setOpen] = useState(false);
85
+ return (_jsxs("div", { className: "overflow-hidden rounded-lg border border-border bg-card", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center gap-3 p-3 text-left hover:bg-muted/40", children: [open ? (_jsx(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "h-4 w-4 shrink-0 text-muted-foreground" })), _jsx("div", { className: "flex h-10 w-10 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-5 w-5 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-semibold text-foreground", children: order.productName }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground", children: [order.orderNumber && _jsx("span", { children: order.orderNumber }), _jsxs("span", { children: [order.karCount, " karren"] }), _jsxs("span", { children: [order.fustCount, " fusten"] }), _jsx("span", { children: eur.format(order.revenue ?? 0) })] })] }), order.isLocal ? (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: "BVO" })) : (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: "FO" }))] }), open && (_jsx("div", { className: "border-t border-border bg-background p-4", children: _jsx(SalesOrderDetailView, { order: order, sellerFallback: sellerFallback ?? null, hideKarindeling: true }) }))] }));
35
86
  }
@@ -1,6 +1,7 @@
1
1
  import type { SalesOrderDTO } from "../../lib/salesorders-mapping";
2
- export declare function SalesOrderDetailView({ order, sellerFallback, }: {
2
+ export declare function SalesOrderDetailView({ order, sellerFallback, hideKarindeling, }: {
3
3
  order: SalesOrderDTO;
4
4
  sellerFallback?: string | null;
5
+ hideKarindeling?: boolean;
5
6
  }): import("react/jsx-runtime").JSX.Element;
6
7
  //# sourceMappingURL=SalesOrderDetailView.d.ts.map
@@ -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;AAmInE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,2CAsSA"}
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;AAmInE,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,2CAuSA"}