@flowselections/floriday-voorraad 1.0.48 → 1.0.50

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 (47) hide show
  1. package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelCompositionCard.js +79 -6
  3. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/ArtikelWizard.js +14 -2
  5. package/dist-lib/components/voorraad/CollapsibleText.d.ts +9 -0
  6. package/dist-lib/components/voorraad/CollapsibleText.d.ts.map +1 -0
  7. package/dist-lib/components/voorraad/CollapsibleText.js +35 -0
  8. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
  9. package/dist-lib/components/voorraad/StockMutationHistoryCard.js +18 -16
  10. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +1 -1
  11. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  12. package/dist-lib/components/voorraad/VoorraadStockCard.js +29 -42
  13. package/dist-lib/hooks/useTradeItemStock.d.ts +3 -1
  14. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  15. package/dist-lib/hooks/useTradeItemStock.js +85 -32
  16. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  17. package/dist-lib/hooks/useVoorraadData.js +3 -2
  18. package/dist-lib/integrations/supabase/auth-middleware.d.ts +700 -0
  19. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/client.d.ts +2 -30340
  21. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/client.js +4 -27
  23. package/dist-lib/integrations/supabase/client.server.d.ts +700 -0
  24. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/types.d.ts +720 -0
  26. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  27. package/dist-lib/lib/floricode-required-features.functions.d.ts +700 -0
  28. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +700 -0
  30. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/floriday-env.functions.d.ts +1400 -0
  32. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/floriday-gateway.functions.d.ts +4200 -0
  34. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +1400 -0
  36. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/floriday-warehouse.functions.d.ts +2100 -0
  38. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/floriday-writes.functions.d.ts +1400 -0
  40. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/inventory-settings.functions.d.ts +1400 -0
  42. package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -1
  43. package/dist-lib/lib/product-packing.d.ts.map +1 -1
  44. package/dist-lib/lib/product-packing.js +10 -4
  45. package/dist-lib/lib/stock-sync.functions.d.ts +1400 -0
  46. package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelCompositionCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelCompositionCard.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAqFD,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,KAAK,sCAgNtE"}
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
- setLoading(true);
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 resolveNames(uuidIds, [...uuidIds, ...nonUuidIds]);
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,+BAsqEL"}
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 { data: product, error } = await supabase
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("id,product,barcode,article_number,quantity,image_url,plants_per_crate,crates_per_layer,layers_per_cart,floriday_packing_confirmed_at,floriday_packing_source,trade_item_key,floriday_trade_item_id,custom_fields")
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)
@@ -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,+BA8DpE"}
@@ -0,0 +1,35 @@
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 { useLayoutEffect, 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 containerRef = useRef(null);
11
+ const measureRef = useRef(null);
12
+ const [expanded, setExpanded] = useState(false);
13
+ const [overflowing, setOverflowing] = useState(false);
14
+ useLayoutEffect(() => {
15
+ const container = containerRef.current;
16
+ const measure = measureRef.current;
17
+ if (!container || !measure)
18
+ return;
19
+ const check = () => {
20
+ const lineHeight = Number.parseFloat(getComputedStyle(measure).lineHeight);
21
+ if (!Number.isFinite(lineHeight))
22
+ return;
23
+ setOverflowing(measure.scrollHeight > lineHeight * lines + 1);
24
+ };
25
+ check();
26
+ if (typeof ResizeObserver !== "undefined") {
27
+ const ro = new ResizeObserver(check);
28
+ ro.observe(container);
29
+ return () => ro.disconnect();
30
+ }
31
+ }, [value, lines]);
32
+ if (!value)
33
+ return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
34
+ return (_jsxs("div", { ref: containerRef, className: `relative min-w-0 ${className ?? ""}`, children: [_jsx("p", { ref: measureRef, "aria-hidden": "true", className: "pointer-events-none invisible absolute inset-x-0 top-0 break-words", children: value }), _jsx("p", { 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"] })) }))] }));
35
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAUA,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,+BAiM1E"}
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,+BAyN1E"}
@@ -1,13 +1,14 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Afboekgeschiedenis: wanneer is er voorraad bij- of afgeboekt en waarom.
4
4
  * Leest public.stock_mutation_log op de sleutel van dit artikel (inclusief
5
5
  * de groepssleutel wanneer het artikel via een groep wordt afgeboekt).
6
6
  */
7
7
  import { useEffect, useState } from "react";
8
- import { Card, CardContent, Badge } from "@flowselections/core";
9
- import { Loader2, History } from "lucide-react";
8
+ import { Card, CardContent, Badge, Button } from "@flowselections/core";
9
+ import { ChevronDown, ChevronUp, 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" },
@@ -41,6 +42,7 @@ function formatMoment(v) {
41
42
  export function StockMutationHistoryCard({ tradeItemKey, productId }) {
42
43
  const [rows, setRows] = useState(null);
43
44
  const [error, setError] = useState(null);
45
+ const [expanded, setExpanded] = useState(false);
44
46
  useEffect(() => {
45
47
  let cancelled = false;
46
48
  const load = async () => {
@@ -118,17 +120,17 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
118
120
  supabase.removeChannel(channel);
119
121
  };
120
122
  }, [tradeItemKey, productId]);
121
- return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon voorraadmutaties niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Mutaties laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen voorraadmutaties voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Wanneer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Soort" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Aantal" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Van \u2192 naar" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Order" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: rows.map((r) => {
122
- const meta = TYPE_LABEL[r.mutation_type] ?? {
123
- label: r.mutation_type || "Onbekend",
124
- tone: "bg-slate-100 text-slate-700 border-slate-200",
125
- };
126
- const physChanged = r.physical_before !== null &&
127
- r.physical_after !== null &&
128
- r.physical_before !== r.physical_after;
129
- const resChanged = r.reserved_before !== null &&
130
- r.reserved_after !== null &&
131
- 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 ?? "—" })] }, r.id));
133
- }) })] }) }))] })] }));
123
+ return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon voorraadmutaties niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Mutaties laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen voorraadmutaties voor dit artikel." })), rows && rows.length > 0 && (_jsxs("div", { className: "overflow-x-auto rounded-md border", children: [_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Wanneer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Soort" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Aantal" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Van \u2192 naar" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Order" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: (expanded ? rows : rows.slice(0, 5)).map((r) => {
124
+ const meta = TYPE_LABEL[r.mutation_type] ?? {
125
+ label: r.mutation_type || "Onbekend",
126
+ tone: "bg-slate-100 text-slate-700 border-slate-200",
127
+ };
128
+ const physChanged = r.physical_before !== null &&
129
+ r.physical_after !== null &&
130
+ r.physical_before !== r.physical_after;
131
+ const resChanged = r.reserved_before !== null &&
132
+ r.reserved_after !== null &&
133
+ r.reserved_before !== r.reserved_after;
134
+ 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));
135
+ }) })] }), rows.length > 5 && (_jsx("div", { className: "flex justify-center border-t bg-muted/20 px-3 py-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setExpanded((current) => !current), className: "gap-1.5", children: expanded ? (_jsxs(_Fragment, { children: [_jsx(ChevronUp, { className: "h-4 w-4" }), " Inklappen"] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDown, { className: "h-4 w-4" }), " Meer tonen (", rows.length - 5, ")"] })) }) }))] }))] })] }));
134
136
  }
@@ -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":"AAeA,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,+BAkQzF"}
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: tradeItemId ?? productKeys.floridayId,
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
- const floridayPieces = useFloridayContinuousStock(connectionId, tradeItemId ?? null);
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
- // Floriday mag "Liggend" alleen bijwerken zolang de gebruiker het veld niet
77
- // zelf heeft aangepast — anders zou een handmatige invoer stil verdwijnen.
78
- useEffect(() => {
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;;;mBA4ElB,iBAAiB;EAoEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
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
- if (!connectionId || keys.length === 0)
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.(${keys.map((k) => `"${k}"`).join(",")})`;
133
+ const filter = `trade_item_key=in.(${realtimeKeys.map((k) => `"${k}"`).join(",")})`;
104
134
  const channel = supabase
105
- .channel(`stock-${connectionId}-${primaryKey}`)
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
- if (!connectionId || !primaryKey) {
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: primaryKey,
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: primaryKey,
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;;;;;EAsV1D"}
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"}