@flowselections/floriday-voorraad 1.0.47 → 1.0.49
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/voorraad/ArtikelCompositionCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelCompositionCard.js +79 -6
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +16 -4
- package/dist-lib/components/voorraad/CollapsibleText.d.ts +9 -0
- package/dist-lib/components/voorraad/CollapsibleText.d.ts.map +1 -0
- package/dist-lib/components/voorraad/CollapsibleText.js +32 -0
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +2 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +29 -42
- package/dist-lib/hooks/useTradeItemStock.d.ts +3 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +85 -32
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +3 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +340 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +340 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +340 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +348 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +340 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +340 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +2040 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +1020 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/inventory-settings.functions.d.ts +680 -0
- package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/product-packing.d.ts.map +1 -1
- package/dist-lib/lib/product-packing.js +10 -4
- package/dist-lib/lib/stock-sync.functions.d.ts +680 -0
- package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelCompositionCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelCompositionCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ArtikelCompositionCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelCompositionCard.tsx"],"names":[],"mappings":"AAMA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAkHD,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,KAAK,sCA6RtE"}
|
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
3
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from "@flowselections/core";
|
|
4
|
-
import { Loader2, Package } from "lucide-react";
|
|
5
|
+
import { ArrowDown, ArrowUp, Loader2, Package } from "lucide-react";
|
|
5
6
|
import { supabase } from "../../integrations/supabase/client";
|
|
7
|
+
async function loadStock(productIds, floridayIds) {
|
|
8
|
+
const out = new Map();
|
|
9
|
+
const idToProduct = new Map();
|
|
10
|
+
productIds.forEach((id) => idToProduct.set(id, id));
|
|
11
|
+
if (floridayIds.length) {
|
|
12
|
+
const { data } = await supabase
|
|
13
|
+
.from("products")
|
|
14
|
+
.select("id,floriday_trade_item_id")
|
|
15
|
+
.in("floriday_trade_item_id", floridayIds);
|
|
16
|
+
(Array.isArray(data) ? data : []).forEach((p) => {
|
|
17
|
+
if (p.floriday_trade_item_id)
|
|
18
|
+
idToProduct.set(String(p.floriday_trade_item_id), String(p.id));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
await Promise.all([...idToProduct.entries()].map(async ([ref, pid]) => {
|
|
22
|
+
const { data, error } = await supabase.rpc("get_product_stock", { p_product_id: pid });
|
|
23
|
+
if (error)
|
|
24
|
+
return;
|
|
25
|
+
const st = Array.isArray(data) ? data[0] : null;
|
|
26
|
+
if (!st) {
|
|
27
|
+
if (!out.has(ref))
|
|
28
|
+
out.set(ref, null);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const physical = Number(st.physical_stock ?? 0);
|
|
32
|
+
const reserved = Number(st.reserved_stock ?? 0);
|
|
33
|
+
out.set(ref, { physical, reserved, available: physical - reserved });
|
|
34
|
+
}));
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
6
37
|
async function resolveNames(productIds, floridayIds) {
|
|
7
38
|
const resolved = new Map();
|
|
8
39
|
const uuidLike = [...productIds, ...floridayIds].filter((v) => /^[0-9a-f-]{36}$/i.test(v));
|
|
@@ -57,15 +88,20 @@ async function resolveNames(productIds, floridayIds) {
|
|
|
57
88
|
export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
58
89
|
const [loading, setLoading] = useState(true);
|
|
59
90
|
const [sections, setSections] = useState([]);
|
|
91
|
+
const [reloadTick, setReloadTick] = useState(0);
|
|
92
|
+
const [savingOrder, setSavingOrder] = useState(false);
|
|
60
93
|
useEffect(() => {
|
|
61
94
|
let cancelled = false;
|
|
62
95
|
(async () => {
|
|
63
|
-
|
|
96
|
+
if (reloadTick === 0)
|
|
97
|
+
setLoading(true);
|
|
64
98
|
const out = [];
|
|
65
99
|
// ---- Productgroep ----
|
|
66
100
|
let groupQ = supabase
|
|
67
101
|
.from("product_groups")
|
|
68
102
|
.select("id,name,description")
|
|
103
|
+
.eq("is_active", true)
|
|
104
|
+
.order("updated_at", { ascending: false })
|
|
69
105
|
.limit(1);
|
|
70
106
|
if (productId && floridayId) {
|
|
71
107
|
groupQ = groupQ.or(`head_product_id.eq.${productId},head_trade_item_floriday_id.eq.${floridayId}`);
|
|
@@ -92,7 +128,10 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
92
128
|
const uuidIds = tradeIds.filter((v) => /^[0-9a-f-]{36}$/i.test(v));
|
|
93
129
|
const nonUuidIds = tradeIds.filter((v) => !/^[0-9a-f-]{36}$/i.test(v));
|
|
94
130
|
// uuid → could be either product uuid or a floriday uuid. Look both up.
|
|
95
|
-
const { resolved } = await
|
|
131
|
+
const [{ resolved }, stockMap] = await Promise.all([
|
|
132
|
+
resolveNames(uuidIds, [...uuidIds, ...nonUuidIds]),
|
|
133
|
+
loadStock(uuidIds, [...uuidIds, ...nonUuidIds]),
|
|
134
|
+
]);
|
|
96
135
|
const rows = items.map((i) => {
|
|
97
136
|
const id = String(i.trade_item_id);
|
|
98
137
|
const r = resolved.get(`floriday:${id}`) ??
|
|
@@ -105,9 +144,11 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
105
144
|
code: r?.code,
|
|
106
145
|
quantity: i.quantity ?? undefined,
|
|
107
146
|
kind: "artikel",
|
|
147
|
+
stock: stockMap.has(id) ? stockMap.get(id) : null,
|
|
108
148
|
};
|
|
109
149
|
});
|
|
110
150
|
out.push({
|
|
151
|
+
groupId: String(group.id),
|
|
111
152
|
kind: "productgroep",
|
|
112
153
|
title: `Productgroep: ${group.name}`,
|
|
113
154
|
description: group.description || `${rows.length} artikelen in deze productgroep`,
|
|
@@ -184,7 +225,39 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
184
225
|
return () => {
|
|
185
226
|
cancelled = true;
|
|
186
227
|
};
|
|
187
|
-
}, [productId, floridayId]);
|
|
228
|
+
}, [productId, floridayId, reloadTick]);
|
|
229
|
+
const groupId = sections.find((x) => x.kind === "productgroep")?.groupId;
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!groupId)
|
|
232
|
+
return;
|
|
233
|
+
const channel = supabase
|
|
234
|
+
.channel(`pgi-${groupId}`)
|
|
235
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "product_group_items", filter: `product_group_id=eq.${groupId}` }, () => setReloadTick((t) => t + 1))
|
|
236
|
+
.subscribe();
|
|
237
|
+
return () => {
|
|
238
|
+
supabase.removeChannel(channel);
|
|
239
|
+
};
|
|
240
|
+
}, [groupId]);
|
|
241
|
+
const move = useCallback(async (index, dir) => {
|
|
242
|
+
const sec = sections.find((x) => x.kind === "productgroep");
|
|
243
|
+
if (!sec)
|
|
244
|
+
return;
|
|
245
|
+
const target = index + dir;
|
|
246
|
+
if (target < 0 || target >= sec.rows.length)
|
|
247
|
+
return;
|
|
248
|
+
const prev = sections;
|
|
249
|
+
const rows = [...sec.rows];
|
|
250
|
+
[rows[index], rows[target]] = [rows[target], rows[index]];
|
|
251
|
+
setSections(sections.map((x) => (x === sec ? { ...x, rows } : x)));
|
|
252
|
+
setSavingOrder(true);
|
|
253
|
+
const results = await Promise.all(rows.map((r, i) => supabase.from("product_group_items").update({ sort_order: i }).eq("id", r.key)));
|
|
254
|
+
setSavingOrder(false);
|
|
255
|
+
const failed = results.find((r) => r.error);
|
|
256
|
+
if (failed?.error) {
|
|
257
|
+
setSections(prev);
|
|
258
|
+
toast.error(`Volgorde opslaan mislukt: ${failed.error.message}`);
|
|
259
|
+
}
|
|
260
|
+
}, [sections]);
|
|
188
261
|
if (loading) {
|
|
189
262
|
return (_jsx(Card, { children: _jsxs(CardContent, { className: "flex items-center gap-2 py-4 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Samenstelling laden\u2026"] }) }));
|
|
190
263
|
}
|
|
@@ -192,5 +265,5 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
192
265
|
return null;
|
|
193
266
|
return (_jsx(_Fragment, { children: sections.map((s) => (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(CardTitle, { children: s.title }), _jsx(Badge, { className: s.kind === "productgroep"
|
|
194
267
|
? "bg-indigo-500/15 text-indigo-700 border-indigo-500/30"
|
|
195
|
-
: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: s.kind === "productgroep" ? "Productgroep" : "Samenstelling" })] }), _jsx(CardDescription, { children: s.description })] }), _jsx(CardContent, { children: s.rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: "Nog geen onderdelen toegevoegd." })) : (_jsx("ul", { className: "divide-y", children: s.rows.map((r) => (_jsxs("li", { className: "flex items-center gap-3 py-2", children: [_jsx("div", { className: "h-8 w-8 rounded bg-muted flex items-center justify-center shrink-0", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: r.name }), r.code && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.code })), r.detail && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.detail }))] }), r.quantity !== undefined && r.quantity !== null && (_jsxs("div", { className: "text-sm tabular-nums text-muted-foreground shrink-0", children: [r.quantity, r.unit ? ` ${r.unit}` : ""] }))] }, r.key))) })) })] }, s.kind))) }));
|
|
268
|
+
: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: s.kind === "productgroep" ? "Productgroep" : "Samenstelling" })] }), _jsx(CardDescription, { children: s.description })] }), _jsx(CardContent, { children: s.rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: "Nog geen onderdelen toegevoegd." })) : (_jsx("ul", { className: "divide-y", children: s.rows.map((r, idx) => (_jsxs("li", { className: "flex items-center gap-3 py-2", children: [s.kind === "productgroep" && (_jsxs("div", { className: "flex flex-col shrink-0", children: [_jsx("button", { type: "button", "aria-label": "Omhoog", disabled: idx === 0 || savingOrder, onClick: () => move(idx, -1), className: "p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30", children: _jsx(ArrowUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", "aria-label": "Omlaag", disabled: idx === s.rows.length - 1 || savingOrder, onClick: () => move(idx, 1), className: "p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30", children: _jsx(ArrowDown, { className: "h-3.5 w-3.5" }) })] })), _jsx("div", { className: "h-8 w-8 rounded bg-muted flex items-center justify-center shrink-0", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: r.name }), r.code && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.code })), r.detail && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.detail }))] }), s.kind === "productgroep" && (_jsx("div", { className: "text-right shrink-0 w-40", children: r.stock ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-sm font-medium tabular-nums", children: ["Beschikbaar: ", r.stock.available] }), _jsxs("div", { className: "text-xs text-muted-foreground tabular-nums", children: ["fysiek ", r.stock.physical, " \u00B7 gereserveerd ", r.stock.reserved] })] })) : (_jsx("div", { className: "text-xs text-muted-foreground", children: "Nog geen voorraad" })) })), r.quantity !== undefined && r.quantity !== null && (_jsxs("div", { className: "text-sm tabular-nums text-muted-foreground shrink-0", title: s.kind === "productgroep" ? "Aantal in groep" : undefined, children: [r.quantity, r.unit ? ` ${r.unit}` : ""] }))] }, r.key))) })) })] }, s.kind))) }));
|
|
196
269
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA4MA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,+
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA4MA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,+BAkrEL"}
|
|
@@ -290,11 +290,23 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
290
290
|
setProductLoading(true);
|
|
291
291
|
(async () => {
|
|
292
292
|
try {
|
|
293
|
-
const
|
|
293
|
+
const baseCols = "id,product,barcode,article_number,quantity,image_url,plants_per_crate,crates_per_layer,layers_per_cart,trade_item_key,floriday_trade_item_id,custom_fields";
|
|
294
|
+
let { data: product, error } = await supabase
|
|
294
295
|
.from("products")
|
|
295
|
-
.select(
|
|
296
|
+
.select(`${baseCols},floriday_packing_confirmed_at,floriday_packing_source`)
|
|
296
297
|
.eq("id", productId)
|
|
297
298
|
.maybeSingle();
|
|
299
|
+
// Andere omgevingen hebben de beladingskolommen mogelijk nog niet:
|
|
300
|
+
// dan zonder die kolommen laden (belading wordt dan "onbekend").
|
|
301
|
+
if (error && /floriday_packing_/.test(error.message ?? "")) {
|
|
302
|
+
const retry = await supabase
|
|
303
|
+
.from("products")
|
|
304
|
+
.select(baseCols)
|
|
305
|
+
.eq("id", productId)
|
|
306
|
+
.maybeSingle();
|
|
307
|
+
product = retry.data;
|
|
308
|
+
error = retry.error;
|
|
309
|
+
}
|
|
298
310
|
if (cancelled)
|
|
299
311
|
return;
|
|
300
312
|
if (error)
|
|
@@ -1690,11 +1702,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1690
1702
|
setDeleteOpen(o);
|
|
1691
1703
|
if (!o)
|
|
1692
1704
|
setDeleteAck(false);
|
|
1693
|
-
}, children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: retrySync, children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(ArtikelCompositionCard, { productId: linkedProductId, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1705
|
+
}, children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: retrySync, children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(ArtikelCompositionCard, { productId: linkedProductId ?? productId ?? null, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1694
1706
|
name: data.name || data.vbnName,
|
|
1695
1707
|
articleNumber: data.articleCode,
|
|
1696
1708
|
gtin: data.gtin,
|
|
1697
|
-
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1709
|
+
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId ?? productId ?? null }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? productId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? productId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1698
1710
|
const isOpen = i === step;
|
|
1699
1711
|
const isValid = stepValidity[i];
|
|
1700
1712
|
const isVisited = i < step || isEditing;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
text: string | null | undefined;
|
|
3
|
+
/** Maximum zichtbare regels wanneer ingeklapt (standaard 5). */
|
|
4
|
+
lines?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function CollapsibleText({ text, lines, className }: Props): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=CollapsibleText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapsibleText.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/CollapsibleText.tsx"],"names":[],"mappings":"AAOA,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,KAAS,EAAE,SAAS,EAAE,EAAE,KAAK,+BAmDpE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Lange tekstvelden die meer dan ~5 regels beslaan kunnen worden ingeklapt.
|
|
4
|
+
* Kortere tekst wordt gewoon volledig getoond, zonder knop.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
8
|
+
export function CollapsibleText({ text, lines = 5, className }) {
|
|
9
|
+
const value = (text ?? "").trim();
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
const [expanded, setExpanded] = useState(false);
|
|
12
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const el = ref.current;
|
|
15
|
+
if (!el)
|
|
16
|
+
return;
|
|
17
|
+
const check = () => {
|
|
18
|
+
// Alleen meten zolang de tekst ingeklapt is; uitgeklapt past hij immers.
|
|
19
|
+
if (!expanded)
|
|
20
|
+
setOverflowing(el.scrollHeight > el.clientHeight + 1);
|
|
21
|
+
};
|
|
22
|
+
check();
|
|
23
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
24
|
+
const ro = new ResizeObserver(check);
|
|
25
|
+
ro.observe(el);
|
|
26
|
+
return () => ro.disconnect();
|
|
27
|
+
}
|
|
28
|
+
}, [value, expanded]);
|
|
29
|
+
if (!value)
|
|
30
|
+
return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
31
|
+
return (_jsxs("div", { className: className, children: [_jsx("p", { ref: ref, className: "break-words", style: expanded ? undefined : { WebkitLineClamp: lines, display: "-webkit-box", WebkitBoxOrient: "vertical", overflow: "hidden" }, children: value }), (overflowing || expanded) && (_jsx("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "mt-1 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline", children: expanded ? (_jsxs(_Fragment, { children: [_jsx(ChevronUp, { className: "h-3.5 w-3.5" }), " Inklappen"] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDown, { className: "h-3.5 w-3.5" }), " Meer tonen"] })) }))] }));
|
|
32
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAWA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA8CD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,+BAmM1E"}
|
|
@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
|
|
8
8
|
import { Card, CardContent, Badge } from "@flowselections/core";
|
|
9
9
|
import { Loader2, History } from "lucide-react";
|
|
10
10
|
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
+
import { CollapsibleText } from "./CollapsibleText";
|
|
11
12
|
const TYPE_LABEL = {
|
|
12
13
|
consumption: { label: "Afboeking (verkoop)", tone: "bg-rose-100 text-rose-800 border-rose-200" },
|
|
13
14
|
consumption_blocked: { label: "Afboeking geblokkeerd", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
@@ -129,6 +130,6 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
129
130
|
const resChanged = r.reserved_before !== null &&
|
|
130
131
|
r.reserved_after !== null &&
|
|
131
132
|
r.reserved_before !== r.reserved_after;
|
|
132
|
-
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: r.reason
|
|
133
|
+
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: _jsx(CollapsibleText, { text: r.reason }) })] }, r.id));
|
|
133
134
|
}) })] }) }))] })] }));
|
|
134
135
|
}
|
|
@@ -4,6 +4,6 @@ interface Props {
|
|
|
4
4
|
draftId?: string | null;
|
|
5
5
|
productId?: string | null;
|
|
6
6
|
}
|
|
7
|
-
export declare function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }: Props): import("react").JSX.Element;
|
|
7
|
+
export declare function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }: Props): import("react").JSX.Element | null;
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=VoorraadStockCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAcA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,sCAgPzF"}
|
|
@@ -3,44 +3,15 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { Button, Card, CardContent, Input, Label, Textarea, toast, } from "@flowselections/core";
|
|
4
4
|
import { Loader2, Package, Check, Factory } from "lucide-react";
|
|
5
5
|
import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock";
|
|
6
|
-
import { useFloridayContinuousStock } from "../../hooks/useFloridayContinuousStock";
|
|
7
6
|
import { supabase } from "../../integrations/supabase/client";
|
|
8
7
|
export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
|
|
9
|
-
// De canonieke sleutel van een gekoppeld product staat in de producttabel
|
|
10
|
-
// (Floriday-UUID of `trade_item_key`). Zonder die sleutel zou de kaart op
|
|
11
|
-
// `product:<id>` schrijven terwijl het overzicht de kale sleutel leest.
|
|
12
|
-
const [productKeys, setProductKeys] = useState({ floridayId: null, tradeItemKey: null });
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (!productId) {
|
|
15
|
-
setProductKeys({ floridayId: null, tradeItemKey: null });
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
let cancelled = false;
|
|
19
|
-
(async () => {
|
|
20
|
-
const { data } = await supabase
|
|
21
|
-
.from("products")
|
|
22
|
-
.select("trade_item_key,floriday_trade_item_id")
|
|
23
|
-
.eq("id", productId)
|
|
24
|
-
.maybeSingle();
|
|
25
|
-
if (cancelled)
|
|
26
|
-
return;
|
|
27
|
-
setProductKeys({
|
|
28
|
-
floridayId: data?.floriday_trade_item_id ?? null,
|
|
29
|
-
tradeItemKey: data?.trade_item_key ?? null,
|
|
30
|
-
});
|
|
31
|
-
})();
|
|
32
|
-
return () => {
|
|
33
|
-
cancelled = true;
|
|
34
|
-
};
|
|
35
|
-
}, [productId]);
|
|
36
8
|
const keys = stockKeysFor({
|
|
37
|
-
tradeItemId
|
|
38
|
-
tradeItemKey: productKeys.tradeItemKey,
|
|
9
|
+
tradeItemId,
|
|
39
10
|
draftId,
|
|
40
11
|
productId,
|
|
41
12
|
});
|
|
42
13
|
const key = keys[0] ?? null;
|
|
43
|
-
const { row, loading, save } = useTradeItemStock(connectionId, keys);
|
|
14
|
+
const { row, loading, save, error: stockError } = useTradeItemStock(connectionId, keys, productId);
|
|
44
15
|
const [values, setValues] = useState(row);
|
|
45
16
|
const [saving, setSaving] = useState(false);
|
|
46
17
|
const [touched, setTouched] = useState(false);
|
|
@@ -68,23 +39,39 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
68
39
|
cancelled = true;
|
|
69
40
|
};
|
|
70
41
|
}, [productId, tradeItemId]);
|
|
71
|
-
|
|
42
|
+
// Is dit artikel de kop van een productgroep? Dan geen voorraad invoeren.
|
|
43
|
+
const [isGroupHead, setIsGroupHead] = useState(false);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let cancelled = false;
|
|
46
|
+
(async () => {
|
|
47
|
+
if (!productId && !tradeItemId) {
|
|
48
|
+
setIsGroupHead(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const filters = [
|
|
52
|
+
productId ? `head_product_id.eq.${productId}` : null,
|
|
53
|
+
tradeItemId ? `head_trade_item_floriday_id.eq.${tradeItemId}` : null,
|
|
54
|
+
].filter(Boolean).join(",");
|
|
55
|
+
const { data } = await supabase.from("product_groups").select("id").or(filters).limit(1);
|
|
56
|
+
if (!cancelled)
|
|
57
|
+
setIsGroupHead(Array.isArray(data) && data.length > 0);
|
|
58
|
+
})();
|
|
59
|
+
return () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
};
|
|
62
|
+
}, [productId, tradeItemId]);
|
|
72
63
|
useEffect(() => {
|
|
73
64
|
setValues(row);
|
|
74
65
|
setTouched(false);
|
|
75
66
|
}, [row.updated_at, row.physical_stock, row.minimum_stock, row.reserved_stock, row.incoming_stock, row.notes]);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (floridayPieces === undefined || touched)
|
|
80
|
-
return;
|
|
81
|
-
setValues((v) => v.physical_stock === floridayPieces
|
|
82
|
-
? v
|
|
83
|
-
: { ...v, physical_stock: floridayPieces });
|
|
84
|
-
}, [floridayPieces, touched]);
|
|
85
|
-
if (!key) {
|
|
67
|
+
if (isGroupHead)
|
|
68
|
+
return null;
|
|
69
|
+
if (!productId && !key) {
|
|
86
70
|
return (_jsx(Card, { className: "p-4 text-sm text-muted-foreground", children: "Voorraad kan pas worden ingesteld nadat het artikel voor het eerst is opgeslagen." }));
|
|
87
71
|
}
|
|
72
|
+
if (stockError) {
|
|
73
|
+
return (_jsxs(Card, { className: "p-4 text-sm text-destructive", children: ["Voorraad laden lukt niet. Controleer of je bent ingelogd en of dit artikel aan een voorraadverbinding is gekoppeld.", _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: ["Details: ", String(stockError)] })] }));
|
|
74
|
+
}
|
|
88
75
|
const available = Math.max(0, values.physical_stock - values.reserved_stock);
|
|
89
76
|
// Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
|
|
90
77
|
// er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
|
|
@@ -37,10 +37,12 @@ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params:
|
|
|
37
37
|
productId?: string | null;
|
|
38
38
|
draftId?: string | null;
|
|
39
39
|
}): TradeItemStockRow | undefined;
|
|
40
|
-
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null): {
|
|
40
|
+
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null, productId?: string | null): {
|
|
41
41
|
row: TradeItemStockRow;
|
|
42
42
|
loading: boolean;
|
|
43
43
|
save: (values: TradeItemStockRow) => Promise<void>;
|
|
44
|
+
error: string | null;
|
|
45
|
+
resolvedKey: string | null;
|
|
44
46
|
};
|
|
45
47
|
export declare function fetchStockMap(connectionId: string): Promise<Map<string, TradeItemStockRow>>;
|
|
46
48
|
//# sourceMappingURL=useTradeItemStock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;;;
|
|
1
|
+
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;mBA0GR,iBAAiB;;;EAyFnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
|
|
@@ -51,7 +51,7 @@ export function lookupStock(map, params) {
|
|
|
51
51
|
}
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
|
-
export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
54
|
+
export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
55
55
|
const keysKey = Array.isArray(keyOrKeys)
|
|
56
56
|
? keyOrKeys.filter(Boolean).join("|")
|
|
57
57
|
: keyOrKeys ?? "";
|
|
@@ -59,7 +59,33 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
59
59
|
const primaryKey = keys[0] ?? null;
|
|
60
60
|
const [row, setRow] = useState(null);
|
|
61
61
|
const [loading, setLoading] = useState(false);
|
|
62
|
+
const [resolvedConnectionId, setResolvedConnectionId] = useState(connectionId);
|
|
63
|
+
const [resolvedKey, setResolvedKey] = useState(primaryKey);
|
|
64
|
+
const [error, setError] = useState(null);
|
|
62
65
|
const refetch = useCallback(async () => {
|
|
66
|
+
setError(null);
|
|
67
|
+
if (productId) {
|
|
68
|
+
const { data, error: rpcError } = await supabase.rpc("get_product_stock", {
|
|
69
|
+
p_product_id: productId,
|
|
70
|
+
});
|
|
71
|
+
if (rpcError) {
|
|
72
|
+
setError(rpcError.message);
|
|
73
|
+
setRow({ ...EMPTY });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const stock = Array.isArray(data) ? data[0] : null;
|
|
77
|
+
setResolvedConnectionId(stock?.connection_id ?? null);
|
|
78
|
+
setResolvedKey(stock?.trade_item_key ?? `product:${productId}`);
|
|
79
|
+
setRow(stock ? {
|
|
80
|
+
physical_stock: Number(stock.physical_stock ?? 0),
|
|
81
|
+
minimum_stock: Number(stock.minimum_stock ?? 0),
|
|
82
|
+
reserved_stock: Number(stock.reserved_stock ?? 0),
|
|
83
|
+
incoming_stock: Number(stock.incoming_stock ?? 0),
|
|
84
|
+
notes: stock.notes ?? null,
|
|
85
|
+
updated_at: stock.updated_at ?? undefined,
|
|
86
|
+
} : { ...EMPTY });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
63
89
|
if (!connectionId || keys.length === 0)
|
|
64
90
|
return;
|
|
65
91
|
const { data } = await supabase
|
|
@@ -77,10 +103,12 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
77
103
|
break;
|
|
78
104
|
}
|
|
79
105
|
}
|
|
106
|
+
setResolvedConnectionId(connectionId);
|
|
107
|
+
setResolvedKey(primaryKey);
|
|
80
108
|
setRow(picked ?? { ...EMPTY });
|
|
81
|
-
}, [connectionId, keys]);
|
|
109
|
+
}, [connectionId, keys, primaryKey, productId]);
|
|
82
110
|
useEffect(() => {
|
|
83
|
-
if (!connectionId || keys.length === 0) {
|
|
111
|
+
if (!productId && (!connectionId || keys.length === 0)) {
|
|
84
112
|
setRow(null);
|
|
85
113
|
return;
|
|
86
114
|
}
|
|
@@ -94,15 +122,17 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
94
122
|
return () => {
|
|
95
123
|
cancelled = true;
|
|
96
124
|
};
|
|
97
|
-
}, [connectionId, keys, refetch]);
|
|
125
|
+
}, [connectionId, keys, productId, refetch]);
|
|
98
126
|
// Realtime: reflecteer elke externe mutatie (order-reservering,
|
|
99
127
|
// completion, bulk-import, wizard-save vanuit ander tabblad).
|
|
100
128
|
useEffect(() => {
|
|
101
|
-
|
|
129
|
+
const realtimeConnectionId = resolvedConnectionId ?? connectionId;
|
|
130
|
+
const realtimeKeys = resolvedKey ? [resolvedKey] : keys;
|
|
131
|
+
if (!realtimeConnectionId || realtimeKeys.length === 0)
|
|
102
132
|
return;
|
|
103
|
-
const filter = `trade_item_key=in.(${
|
|
133
|
+
const filter = `trade_item_key=in.(${realtimeKeys.map((k) => `"${k}"`).join(",")})`;
|
|
104
134
|
const channel = supabase
|
|
105
|
-
.channel(`stock-${
|
|
135
|
+
.channel(`stock-${realtimeConnectionId}-${resolvedKey ?? primaryKey}`)
|
|
106
136
|
.on("postgres_changes", {
|
|
107
137
|
event: "*",
|
|
108
138
|
schema: "public",
|
|
@@ -115,29 +145,52 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
115
145
|
return () => {
|
|
116
146
|
supabase.removeChannel(channel);
|
|
117
147
|
};
|
|
118
|
-
}, [connectionId, keys, primaryKey, refetch]);
|
|
148
|
+
}, [connectionId, keys, primaryKey, refetch, resolvedConnectionId, resolvedKey]);
|
|
119
149
|
const save = useCallback(async (values) => {
|
|
120
|
-
|
|
150
|
+
let savedKey = resolvedKey ?? primaryKey;
|
|
151
|
+
let savedConnectionId = resolvedConnectionId ?? connectionId;
|
|
152
|
+
if (productId) {
|
|
153
|
+
const { data, error: rpcError } = await supabase.rpc("set_product_stock", {
|
|
154
|
+
p_product_id: productId,
|
|
155
|
+
p_physical_stock: values.physical_stock,
|
|
156
|
+
p_minimum_stock: values.minimum_stock,
|
|
157
|
+
p_incoming_stock: values.incoming_stock,
|
|
158
|
+
p_notes: values.notes ?? undefined,
|
|
159
|
+
});
|
|
160
|
+
if (rpcError)
|
|
161
|
+
throw new Error(rpcError.message);
|
|
162
|
+
const saved = Array.isArray(data) ? data[0] : null;
|
|
163
|
+
savedKey = saved?.trade_item_key ?? savedKey;
|
|
164
|
+
savedConnectionId = saved?.connection_id ?? savedConnectionId;
|
|
165
|
+
setResolvedKey(savedKey);
|
|
166
|
+
setResolvedConnectionId(savedConnectionId);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (!connectionId || !primaryKey) {
|
|
170
|
+
throw new Error("Ontbrekende connectie of artikel-key");
|
|
171
|
+
}
|
|
172
|
+
const userRes = await supabase.auth.getUser();
|
|
173
|
+
const userId = userRes.data.user?.id;
|
|
174
|
+
if (!userId)
|
|
175
|
+
throw new Error("Niet ingelogd");
|
|
176
|
+
const { error: upsertError } = await supabase
|
|
177
|
+
.from("trade_item_stock")
|
|
178
|
+
.upsert({
|
|
179
|
+
user_id: userId,
|
|
180
|
+
connection_id: connectionId,
|
|
181
|
+
trade_item_key: primaryKey,
|
|
182
|
+
physical_stock: values.physical_stock,
|
|
183
|
+
minimum_stock: values.minimum_stock,
|
|
184
|
+
reserved_stock: values.reserved_stock,
|
|
185
|
+
incoming_stock: values.incoming_stock,
|
|
186
|
+
notes: values.notes,
|
|
187
|
+
}, { onConflict: "connection_id,trade_item_key" });
|
|
188
|
+
if (upsertError)
|
|
189
|
+
throw new Error(upsertError.message);
|
|
190
|
+
}
|
|
191
|
+
if (!savedConnectionId || !savedKey) {
|
|
121
192
|
throw new Error("Ontbrekende connectie of artikel-key");
|
|
122
193
|
}
|
|
123
|
-
const userRes = await supabase.auth.getUser();
|
|
124
|
-
const userId = userRes.data.user?.id;
|
|
125
|
-
if (!userId)
|
|
126
|
-
throw new Error("Niet ingelogd");
|
|
127
|
-
const { error } = await supabase
|
|
128
|
-
.from("trade_item_stock")
|
|
129
|
-
.upsert({
|
|
130
|
-
user_id: userId,
|
|
131
|
-
connection_id: connectionId,
|
|
132
|
-
trade_item_key: primaryKey,
|
|
133
|
-
physical_stock: values.physical_stock,
|
|
134
|
-
minimum_stock: values.minimum_stock,
|
|
135
|
-
reserved_stock: values.reserved_stock,
|
|
136
|
-
incoming_stock: values.incoming_stock,
|
|
137
|
-
notes: values.notes,
|
|
138
|
-
}, { onConflict: "connection_id,trade_item_key" });
|
|
139
|
-
if (error)
|
|
140
|
-
throw new Error(error.message);
|
|
141
194
|
const previousPhysical = row?.physical_stock ?? 0;
|
|
142
195
|
setRow(values);
|
|
143
196
|
// Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
|
|
@@ -149,8 +202,8 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
149
202
|
try {
|
|
150
203
|
const { registerSaleFustUsage } = await import("../lib/fusten");
|
|
151
204
|
await registerSaleFustUsage({
|
|
152
|
-
connectionId,
|
|
153
|
-
tradeItemKey:
|
|
205
|
+
connectionId: savedConnectionId,
|
|
206
|
+
tradeItemKey: savedKey,
|
|
154
207
|
piecesSold: sold,
|
|
155
208
|
reason: "manual_sale",
|
|
156
209
|
});
|
|
@@ -167,7 +220,7 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
167
220
|
try {
|
|
168
221
|
const { emitStockChangedEvent } = await import("../lib/logistics-events");
|
|
169
222
|
await emitStockChangedEvent({
|
|
170
|
-
tradeItemKey:
|
|
223
|
+
tradeItemKey: savedKey,
|
|
171
224
|
physicalStock: values.physical_stock,
|
|
172
225
|
reservedStock: values.reserved_stock,
|
|
173
226
|
});
|
|
@@ -176,8 +229,8 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
176
229
|
// Engine-tabel niet bereikbaar; voorraad is lokaal wel opgeslagen.
|
|
177
230
|
}
|
|
178
231
|
})();
|
|
179
|
-
}, [connectionId, primaryKey, row]);
|
|
180
|
-
return { row: row ?? EMPTY, loading, save };
|
|
232
|
+
}, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
|
|
233
|
+
return { row: row ?? EMPTY, loading, save, error, resolvedKey };
|
|
181
234
|
}
|
|
182
235
|
export async function fetchStockMap(connectionId) {
|
|
183
236
|
const { data } = await supabase
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;
|
|
1
|
+
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EAuV1D"}
|