@flowselections/floriday-verkoop-module 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts +4 -0
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.js +95 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts +6 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.js +194 -0
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/CreateOrderPage.js +230 -192
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts +4 -0
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.js +44 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts +5 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.js +160 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.js +6 -3
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +3 -4
- package/dist-lib/components/verkoop/SearchableSelect.d.ts +19 -0
- package/dist-lib/components/verkoop/SearchableSelect.d.ts.map +1 -0
- package/dist-lib/components/verkoop/SearchableSelect.js +21 -0
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +39 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1381 -86
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1381 -86
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1381 -86
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1402 -79
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +11225 -0
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -0
- package/dist-lib/lib/cart-lock.functions.js +47 -0
- package/dist-lib/lib/cart-planner.d.ts +38 -0
- package/dist-lib/lib/cart-planner.d.ts.map +1 -0
- package/dist-lib/lib/cart-planner.js +217 -0
- package/dist-lib/lib/cart-replan.functions.d.ts +22467 -0
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -0
- package/dist-lib/lib/cart-replan.functions.js +203 -0
- package/dist-lib/lib/create-order.functions.d.ts +100967 -0
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -0
- package/dist-lib/lib/create-order.functions.js +343 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +33640 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.js +65 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts +21 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.js +352 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +11220 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.js +171 -0
- package/dist-lib/lib/floriday-public.functions.d.ts +9067 -16
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.js +45 -47
- package/dist-lib/lib/floriday-writes.functions.d.ts +6475 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.d.ts +2 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.js +46 -20
- package/dist-lib/lib/salesorders.functions.d.ts +4273 -388
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.js +8 -0
- package/dist-lib/lib/tradeitems.functions.d.ts +1381 -86
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +1 -0
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.js +130 -41
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutoFulfillmentStatusCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/AutoFulfillmentStatusCard.tsx"],"names":[],"mappings":"AAyDA,wBAAgB,yBAAyB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,kDA6FnF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
6
|
+
import { getAutoFulfillmentStatus, retryAutoFulfillment, getPreferredWarehouseStatus, } from "../../lib/floriday-auto-fulfillment.functions";
|
|
7
|
+
import { Button } from "@flowselections/core";
|
|
8
|
+
import { toast } from "sonner";
|
|
9
|
+
const REASON_LABELS = {
|
|
10
|
+
"no-preferred-warehouse": "Er is nog geen voorkeursmagazijn ingesteld op de Floriday-connectie.",
|
|
11
|
+
"no-warehouse": "Geen despatch-magazijn gevonden (voorkeur, salesorder, én cache leeg).",
|
|
12
|
+
"no-delivery-order": "Nog geen delivery order (met fulfillment-requests) ontvangen van Floriday voor deze salesorder.",
|
|
13
|
+
"existing-fulfillment": "Er bestaat al een fulfillment order voor deze salesorder.",
|
|
14
|
+
"sales-order-deleted": "Salesorder is verwijderd bij Floriday.",
|
|
15
|
+
"already-created": "Fulfillment order is al automatisch aangemaakt.",
|
|
16
|
+
"connection-not-found": "Floriday-connectie niet gevonden.",
|
|
17
|
+
"no-packing-fallback": "Belading ontbreekt; er is een enkele kar als fallback gebruikt.",
|
|
18
|
+
"warehouse-fallback:order": "Despatch-magazijn overgenomen uit de salesorder (geen voorkeursmagazijn ingesteld).",
|
|
19
|
+
"warehouse-fallback:cache": "Fallback-magazijn uit cache gebruikt; stel eventueel een voorkeur in bij de Authenticatiemodule.",
|
|
20
|
+
};
|
|
21
|
+
function statusStyle(status) {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case "created":
|
|
24
|
+
return "bg-emerald-50 text-emerald-800 border-emerald-200";
|
|
25
|
+
case "failed":
|
|
26
|
+
return "bg-red-50 text-red-800 border-red-200";
|
|
27
|
+
case "skipped":
|
|
28
|
+
return "bg-amber-50 text-amber-800 border-amber-200";
|
|
29
|
+
default:
|
|
30
|
+
return "bg-slate-50 text-slate-700 border-slate-200";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function statusLabel(status) {
|
|
34
|
+
switch (status) {
|
|
35
|
+
case "created":
|
|
36
|
+
return "Fulfillment order automatisch aangemaakt";
|
|
37
|
+
case "failed":
|
|
38
|
+
return "Automatische fulfillment mislukt";
|
|
39
|
+
case "skipped":
|
|
40
|
+
return "Automatische fulfillment overgeslagen";
|
|
41
|
+
default:
|
|
42
|
+
return "Fulfillment nog niet automatisch aangemaakt";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function AutoFulfillmentStatusCard({ salesOrderId }) {
|
|
46
|
+
const queryClient = useQueryClient();
|
|
47
|
+
const fetchStatus = useServerFn(getAutoFulfillmentStatus);
|
|
48
|
+
const retryFn = useServerFn(retryAutoFulfillment);
|
|
49
|
+
const queryKey = ["auto-fulfillment-status", salesOrderId];
|
|
50
|
+
const { data, isLoading } = useQuery({
|
|
51
|
+
queryKey,
|
|
52
|
+
queryFn: () => fetchStatus({ data: { salesOrderId } }),
|
|
53
|
+
});
|
|
54
|
+
const fetchWarehouse = useServerFn(getPreferredWarehouseStatus);
|
|
55
|
+
const { data: warehouseStatus } = useQuery({
|
|
56
|
+
queryKey: ["preferred-warehouse-status"],
|
|
57
|
+
queryFn: () => fetchWarehouse(),
|
|
58
|
+
staleTime: 60000,
|
|
59
|
+
});
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const channel = supabase
|
|
62
|
+
.channel(`auto-fulfillment-${salesOrderId}`)
|
|
63
|
+
.on("postgres_changes", {
|
|
64
|
+
event: "*",
|
|
65
|
+
schema: "public",
|
|
66
|
+
table: "floriday_auto_fulfillment_log",
|
|
67
|
+
filter: `sales_order_id=eq.${salesOrderId}`,
|
|
68
|
+
}, () => queryClient.invalidateQueries({ queryKey }))
|
|
69
|
+
.subscribe();
|
|
70
|
+
return () => {
|
|
71
|
+
supabase.removeChannel(channel);
|
|
72
|
+
};
|
|
73
|
+
}, [salesOrderId, queryClient]);
|
|
74
|
+
const mutation = useMutation({
|
|
75
|
+
mutationFn: () => retryFn({ data: { salesOrderId } }),
|
|
76
|
+
onSuccess: (res) => {
|
|
77
|
+
queryClient.invalidateQueries({ queryKey });
|
|
78
|
+
if (res.status === "created")
|
|
79
|
+
toast.success("Fulfillment order aangemaakt");
|
|
80
|
+
else if (res.status === "failed")
|
|
81
|
+
toast.error(`Mislukt: ${res.reason ?? "onbekend"}`);
|
|
82
|
+
else
|
|
83
|
+
toast.info(`Overgeslagen: ${res.reason ?? ""}`);
|
|
84
|
+
},
|
|
85
|
+
onError: (err) => toast.error(err.message),
|
|
86
|
+
});
|
|
87
|
+
if (isLoading)
|
|
88
|
+
return null;
|
|
89
|
+
const row = data?.row ?? null;
|
|
90
|
+
const status = row?.status ?? "pending";
|
|
91
|
+
const canRetry = status === "failed" || status === "skipped" || !row;
|
|
92
|
+
return (_jsx("div", { className: `mt-4 rounded-md border p-3 text-sm ${statusStyle(status)}`, children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: statusLabel(status) }), row?.fulfillment_order_id ? (_jsxs("div", { className: "mt-1 text-xs opacity-80", children: ["Fulfillment ID: ", _jsx("code", { children: row.fulfillment_order_id })] })) : null, row?.reason ? (_jsx("div", { className: "mt-1 text-xs opacity-80", children: REASON_LABELS[row.reason] ?? row.reason })) : null, row?.error ? (_jsxs("div", { className: "mt-1 text-xs opacity-80", children: ["Fout: ", row.error] })) : null, warehouseStatus &&
|
|
93
|
+
!warehouseStatus.hasPreferredWarehouse &&
|
|
94
|
+
(row?.reason === "no-preferred-warehouse" || row?.reason === "no-warehouse") ? (_jsx("div", { className: "mt-2 rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-900", children: "Er is nog geen voorkeursmagazijn ingesteld. Ga naar de Floriday-authenticatiemodule en kies een voorkeursmagazijn \u2014 daarna worden fulfillment orders automatisch aangemaakt." })) : null] }), canRetry ? (_jsx(Button, { size: "sm", variant: "outline", disabled: mutation.isPending, onClick: () => mutation.mutate(), children: mutation.isPending ? "Bezig..." : "Opnieuw proberen" })) : null] }) }));
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartAssignmentWizard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/CartAssignmentWizard.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EAAE,IAAI,EAAE,OAAO,GACvB,EAAE;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,2CA4ThE"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Karindeling-wizard — identiek aan de Mailbox-module.
|
|
3
|
+
// Gebruikt planAndSaveAutoCarts + saveManualCarts uit create-order.functions,
|
|
4
|
+
// zodat handmatige orders exact dezelfde logica en schermen krijgen als mailbox-orders.
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { useQuery } from "@tanstack/react-query";
|
|
7
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
8
|
+
import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
9
|
+
import { Plus, Trash2, AlertTriangle, CheckCircle2 } from "lucide-react";
|
|
10
|
+
import { planAndSaveAutoCarts, saveManualCarts } from "../../lib/create-order.functions";
|
|
11
|
+
export function CartAssignmentWizard({ orderId, open, onClose, }) {
|
|
12
|
+
const fetchPlan = useServerFn(planAndSaveAutoCarts);
|
|
13
|
+
const fetchSave = useServerFn(saveManualCarts);
|
|
14
|
+
const { data, isLoading } = useQuery({
|
|
15
|
+
queryKey: ["cart-plan", orderId],
|
|
16
|
+
enabled: !!orderId && open,
|
|
17
|
+
queryFn: async () => {
|
|
18
|
+
try {
|
|
19
|
+
return await fetchPlan({ data: { orderId: orderId } });
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
toast.error(e?.message ?? "Indelen mislukt");
|
|
23
|
+
return { autoCarts: [], remainders: [], existingManualCarts: [], nextCartNumber: 1 };
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const autoCarts = Array.isArray(data?.autoCarts) ? data.autoCarts : [];
|
|
28
|
+
const remainders = Array.isArray(data?.remainders) ? data.remainders : [];
|
|
29
|
+
const existingManualCarts = Array.isArray(data?.existingManualCarts) ? data.existingManualCarts : [];
|
|
30
|
+
const [pool, setPool] = useState({});
|
|
31
|
+
const [manual, setManual] = useState([]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const p = {};
|
|
34
|
+
remainders.forEach((r) => { p[r.order_item_id] = r.crates_remaining; });
|
|
35
|
+
setPool(p);
|
|
36
|
+
const remMeta = {};
|
|
37
|
+
remainders.forEach((r) => { remMeta[r.order_item_id] = r; });
|
|
38
|
+
setManual(existingManualCarts.map((c) => ({
|
|
39
|
+
id: crypto.randomUUID(),
|
|
40
|
+
items: c.items.map((i) => {
|
|
41
|
+
const meta = i.order_item_id ? remMeta[i.order_item_id] : null;
|
|
42
|
+
return {
|
|
43
|
+
...i,
|
|
44
|
+
crates_per_layer: meta?.crates_per_layer ?? i.crates_per_layer ?? 1,
|
|
45
|
+
layers_per_cart: meta?.layers_per_cart ?? i.layers_per_cart ?? 1,
|
|
46
|
+
};
|
|
47
|
+
}),
|
|
48
|
+
})));
|
|
49
|
+
}, [data]);
|
|
50
|
+
const remByItem = useMemo(() => {
|
|
51
|
+
const m = {};
|
|
52
|
+
remainders.forEach((r) => { m[r.order_item_id] = r; });
|
|
53
|
+
return m;
|
|
54
|
+
}, [remainders]);
|
|
55
|
+
const addCart = () => setManual((m) => [...m, { id: crypto.randomUUID(), items: [] }]);
|
|
56
|
+
const addItemToCart = (cartId, orderItemId, crates) => {
|
|
57
|
+
const rem = remByItem[orderItemId];
|
|
58
|
+
if (!rem)
|
|
59
|
+
return;
|
|
60
|
+
const available = pool[orderItemId] ?? 0;
|
|
61
|
+
const take = Math.max(0, Math.min(crates, available));
|
|
62
|
+
if (take === 0)
|
|
63
|
+
return;
|
|
64
|
+
setPool((p) => ({ ...p, [orderItemId]: available - take }));
|
|
65
|
+
setManual((carts) => carts.map((c) => {
|
|
66
|
+
if (c.id !== cartId)
|
|
67
|
+
return c;
|
|
68
|
+
const existingIdx = c.items.findIndex((i) => i.order_item_id === orderItemId);
|
|
69
|
+
if (existingIdx >= 0) {
|
|
70
|
+
const next = [...c.items];
|
|
71
|
+
next[existingIdx] = { ...next[existingIdx], crates: next[existingIdx].crates + take };
|
|
72
|
+
return { ...c, items: next };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...c,
|
|
76
|
+
items: [...c.items, {
|
|
77
|
+
order_item_id: orderItemId,
|
|
78
|
+
product_id: rem.product_id,
|
|
79
|
+
product_name: rem.product_name,
|
|
80
|
+
crates: take,
|
|
81
|
+
plants_per_crate: rem.plants_per_crate,
|
|
82
|
+
crates_per_layer: rem.crates_per_layer,
|
|
83
|
+
layers_per_cart: rem.layers_per_cart,
|
|
84
|
+
}],
|
|
85
|
+
};
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
const removeItem = (cartId, orderItemId) => {
|
|
89
|
+
setManual((carts) => carts.map((c) => {
|
|
90
|
+
if (c.id !== cartId)
|
|
91
|
+
return c;
|
|
92
|
+
const item = c.items.find((i) => i.order_item_id === orderItemId);
|
|
93
|
+
if (!item)
|
|
94
|
+
return c;
|
|
95
|
+
setPool((p) => ({ ...p, [orderItemId]: (p[orderItemId] ?? 0) + item.crates }));
|
|
96
|
+
return { ...c, items: c.items.filter((i) => i.order_item_id !== orderItemId) };
|
|
97
|
+
}));
|
|
98
|
+
};
|
|
99
|
+
const removeCart = (cartId) => {
|
|
100
|
+
setManual((carts) => {
|
|
101
|
+
const c = carts.find((x) => x.id === cartId);
|
|
102
|
+
if (c) {
|
|
103
|
+
setPool((p) => {
|
|
104
|
+
const np = { ...p };
|
|
105
|
+
c.items.forEach((i) => {
|
|
106
|
+
if (!i.order_item_id)
|
|
107
|
+
return;
|
|
108
|
+
np[i.order_item_id] = (np[i.order_item_id] ?? 0) + i.crates;
|
|
109
|
+
});
|
|
110
|
+
return np;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return carts.filter((x) => x.id !== cartId);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const cartCapacity = (cart) => cart.items.length === 0
|
|
117
|
+
? 0
|
|
118
|
+
: Math.max(...cart.items.map((i) => i.crates_per_layer * i.layers_per_cart));
|
|
119
|
+
const cartUsed = (cart) => cart.items.reduce((s, i) => s + i.crates, 0);
|
|
120
|
+
const totalRemainingInPool = Object.values(pool).reduce((s, n) => s + n, 0);
|
|
121
|
+
const handleSave = async () => {
|
|
122
|
+
if (totalRemainingInPool > 0) {
|
|
123
|
+
toast.error(`Nog ${totalRemainingInPool} fust niet ingedeeld — verdeel alles over een kar voordat je opslaat`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (manual.some((c) => c.items.length === 0)) {
|
|
127
|
+
toast.error("Lege karren niet toegestaan");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const overfull = manual.find((c) => cartUsed(c) > cartCapacity(c));
|
|
131
|
+
if (overfull) {
|
|
132
|
+
toast.error("Een kar overschrijdt de capaciteit");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await fetchSave({
|
|
137
|
+
data: {
|
|
138
|
+
orderId: orderId,
|
|
139
|
+
carts: manual.map((c) => ({
|
|
140
|
+
is_complete: cartUsed(c) >= cartCapacity(c),
|
|
141
|
+
items: c.items.map((i) => ({
|
|
142
|
+
order_item_id: i.order_item_id,
|
|
143
|
+
product_id: i.product_id,
|
|
144
|
+
product_name: i.product_name,
|
|
145
|
+
crates: Math.max(1, Math.trunc(i.crates)),
|
|
146
|
+
plants_per_crate: Math.max(1, Math.trunc(i.plants_per_crate || 1)),
|
|
147
|
+
crates_per_layer: Math.max(1, Math.trunc(i.crates_per_layer || 1)),
|
|
148
|
+
})),
|
|
149
|
+
})),
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
toast.success("Karindeling opgeslagen");
|
|
153
|
+
onClose();
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
let msg = "Opslaan mislukt";
|
|
157
|
+
if (e instanceof Response) {
|
|
158
|
+
try {
|
|
159
|
+
msg = (await e.text()) || `HTTP ${e.status}`;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
msg = `HTTP ${e.status}`;
|
|
163
|
+
}
|
|
164
|
+
if (e.status === 401)
|
|
165
|
+
msg = "Sessie verlopen — log opnieuw in";
|
|
166
|
+
}
|
|
167
|
+
else if (e?.message) {
|
|
168
|
+
msg = e.message;
|
|
169
|
+
}
|
|
170
|
+
toast.error(msg);
|
|
171
|
+
console.error("saveManualCarts failed:", e);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
return (_jsx(Dialog, { open: open, onOpenChange: (o) => { if (!o)
|
|
175
|
+
onClose(); }, children: _jsxs(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] overflow-y-auto p-6", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Karindeling" }) }), isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Karren worden berekend..." })) : (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { className: "text-base", children: "Automatisch ingedeelde karren" }), _jsxs(Badge, { variant: "secondary", children: [autoCarts.length, " kar(ren)"] })] }), _jsx(CardContent, { children: autoCarts.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen volledige karren mogelijk." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Kar #" }), _jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Planten" }), _jsx(TableHead, { className: "text-right", children: "Bezetting" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: autoCarts.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono", children: c.cart_number }), _jsx(TableCell, { children: c.product_name }), _jsx(TableCell, { className: "text-right", children: c.crates }), _jsx(TableCell, { className: "text-right", children: c.plants }), _jsx(TableCell, { className: "text-right", children: "100%" }), _jsx(TableCell, { children: _jsxs(Badge, { className: "gap-1", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), " Volledig"] }) })] }, c.cart_number))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Handmatig in te delen fusten" }) }), _jsx(CardContent, { className: "space-y-3", children: remainders.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen restant \u2014 alles past volledig op karren." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Beschikbaar (fusten)" }), _jsx(TableHead, { className: "text-right", children: "Planten/fust" }), _jsx(TableHead, { className: "text-right", children: "Capaciteit/kar" })] }) }), _jsx(TableBody, { children: remainders.map((r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: r.product_name }), _jsxs(TableCell, { className: "text-right font-mono", children: [pool[r.order_item_id] ?? 0, " / ", r.crates_remaining] }), _jsx(TableCell, { className: "text-right", children: r.plants_per_crate }), _jsx(TableCell, { className: "text-right", children: r.crates_per_layer * r.layers_per_cart })] }, r.order_item_id))) })] })) })] }), remainders.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { className: "text-base", children: "Restant-karren samenstellen" }), _jsxs(Button, { size: "sm", onClick: addCart, children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Kar toevoegen"] })] }), _jsxs(CardContent, { className: "space-y-3", children: [manual.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Voeg een kar toe om restant-fusten in te delen." })), manual.map((cart, idx) => {
|
|
176
|
+
const used = cartUsed(cart);
|
|
177
|
+
const cap = cartCapacity(cart);
|
|
178
|
+
const pct = cap > 0 ? Math.round((used / cap) * 100) : 0;
|
|
179
|
+
const over = cap > 0 && used > cap;
|
|
180
|
+
return (_jsxs(Card, { className: over ? "border-destructive" : "", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "font-medium", children: ["Restant-kar ", idx + 1] }), _jsxs(Badge, { variant: over ? "destructive" : "secondary", children: [used, " / ", cap || "?", " fusten (", pct, "%)"] }), over && (_jsxs("span", { className: "text-xs text-destructive flex items-center gap-1", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), " Capaciteit overschreden"] }))] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeCart(cart.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-2", children: [cart.items.map((i) => (_jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: i.product_name }), _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", i.crates, " fust (", i.crates * i.plants_per_crate, " planten)"] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => removeItem(cart.id, i.order_item_id), children: _jsx(Trash2, { className: "h-3 w-3" }) })] }, i.order_item_id))), _jsx(AddItemRow, { remainders: remainders, pool: pool, onAdd: (itemId, crates) => addItemToCart(cart.id, itemId, crates) })] })] }, cart.id));
|
|
181
|
+
})] })] }))] })), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, children: "Annuleren" }), _jsxs(Button, { onClick: handleSave, disabled: isLoading || totalRemainingInPool > 0, children: ["Karindeling opslaan", totalRemainingInPool > 0 && (_jsxs(Badge, { variant: "destructive", className: "ml-2", children: ["Nog ", totalRemainingInPool, " fust in te delen"] }))] })] })] }) }));
|
|
182
|
+
}
|
|
183
|
+
function AddItemRow({ remainders, pool, onAdd, }) {
|
|
184
|
+
const available = remainders.filter((r) => (pool[r.order_item_id] ?? 0) > 0);
|
|
185
|
+
const [sel, setSel] = useState("");
|
|
186
|
+
const [qty, setQty] = useState(1);
|
|
187
|
+
if (available.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
return (_jsxs("div", { className: "flex items-end gap-2 pt-2 border-t", children: [_jsxs("div", { className: "flex-1", children: [_jsx(Label, { className: "text-xs", children: "Artikel" }), _jsxs(Select, { value: sel, onValueChange: setSel, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies restant-artikel..." }) }), _jsx(SelectContent, { children: available.map((r) => (_jsxs(SelectItem, { value: r.order_item_id, children: [r.product_name, " (", pool[r.order_item_id], " fust beschikbaar)"] }, r.order_item_id))) })] })] }), _jsxs("div", { className: "w-24", children: [_jsx(Label, { className: "text-xs", children: "Fusten" }), _jsx(Input, { type: "number", min: 1, value: qty, onChange: (e) => setQty(Math.max(1, Number(e.target.value) || 1)) })] }), _jsxs(Button, { size: "sm", disabled: !sel, onClick: () => { if (sel) {
|
|
190
|
+
onAdd(sel, qty);
|
|
191
|
+
setSel("");
|
|
192
|
+
setQty(1);
|
|
193
|
+
} }, children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Toevoegen"] })] }));
|
|
194
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateOrderPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/CreateOrderPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CreateOrderPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/CreateOrderPage.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,eAAe,4CA+b9B"}
|