@flowselections/floriday-voorraad 1.0.33 → 1.0.34

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 (62) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +11 -5
  3. package/dist-lib/components/voorraad/ModuleHealthCard.d.ts +2 -0
  4. package/dist-lib/components/voorraad/ModuleHealthCard.d.ts.map +1 -0
  5. package/dist-lib/components/voorraad/ModuleHealthCard.js +40 -0
  6. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  7. package/dist-lib/components/voorraad/StockBulkButtons.js +5 -2
  8. package/dist-lib/components/voorraad/StockPushMonitor.d.ts +4 -0
  9. package/dist-lib/components/voorraad/StockPushMonitor.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/StockPushMonitor.js +110 -0
  11. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  12. package/dist-lib/components/voorraad/VoorraadPage.js +2 -1
  13. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  14. package/dist-lib/components/voorraad/VoorraadStockCard.js +10 -5
  15. package/dist-lib/hooks/useStockPushJobs.d.ts +23 -0
  16. package/dist-lib/hooks/useStockPushJobs.d.ts.map +1 -0
  17. package/dist-lib/hooks/useStockPushJobs.js +86 -0
  18. package/dist-lib/hooks/useTradeItemStock.d.ts +12 -0
  19. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  20. package/dist-lib/hooks/useTradeItemStock.js +22 -0
  21. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  22. package/dist-lib/hooks/useVoorraadData.js +11 -3
  23. package/dist-lib/index.d.ts +1 -0
  24. package/dist-lib/index.d.ts.map +1 -1
  25. package/dist-lib/index.js +6 -1
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts +410 -4
  27. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.d.ts +410 -4
  29. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/client.server.d.ts +410 -4
  31. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.d.ts +411 -5
  33. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  34. package/dist-lib/integrations/supabase/types.js +12 -1
  35. package/dist-lib/lib/centralSecrets.server.js +1 -1
  36. package/dist-lib/lib/floricode-required-features.functions.d.ts +410 -4
  37. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +410 -4
  39. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-env.functions.d.ts +820 -8
  41. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-gateway.functions.d.ts +2460 -24
  43. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-gateway.server.d.ts +14 -0
  45. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-gateway.server.js +62 -10
  47. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +820 -8
  48. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  49. package/dist-lib/lib/floriday-warehouse.functions.d.ts +1230 -12
  50. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/floriday-writes.functions.d.ts +820 -8
  52. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  53. package/dist-lib/lib/module-health.functions.d.ts +41725 -0
  54. package/dist-lib/lib/module-health.functions.d.ts.map +1 -0
  55. package/dist-lib/lib/module-health.functions.js +173 -0
  56. package/dist-lib/lib/module-runtime.server.d.ts +8 -1
  57. package/dist-lib/lib/module-runtime.server.d.ts.map +1 -1
  58. package/dist-lib/lib/module-runtime.server.js +103 -12
  59. package/dist-lib/lib/stock-sync.functions.d.ts +21302 -31
  60. package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/stock-sync.functions.js +46 -3
  62. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,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,+BA26DL"}
1
+ {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,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,+BAs7DL"}
@@ -405,12 +405,18 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
405
405
  // met 0).
406
406
  let physical = 0;
407
407
  try {
408
- const { data: stockRow } = await supabase
408
+ const stockKeys = [
409
+ product.floriday_trade_item_id,
410
+ product.trade_item_key,
411
+ `product:${product.id}`,
412
+ ].filter((k) => Boolean(k));
413
+ const { data: stockRows } = await supabase
409
414
  .from("trade_item_stock")
410
- .select("physical_stock")
411
- .eq("trade_item_key", `product:${product.id}`)
412
- .maybeSingle();
413
- physical = Number(stockRow?.physical_stock ?? 0);
415
+ .select("trade_item_key,physical_stock")
416
+ .in("trade_item_key", stockKeys);
417
+ const rows = (stockRows ?? []);
418
+ const hit = stockKeys.map((k) => rows.find((r) => r.trade_item_key === k)).find(Boolean) ?? null;
419
+ physical = Number(hit?.physical_stock ?? 0);
414
420
  }
415
421
  catch { /* stock niet gevonden = 0 */ }
416
422
  setData({
@@ -0,0 +1,2 @@
1
+ export declare function ModuleHealthCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=ModuleHealthCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModuleHealthCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ModuleHealthCard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,gBAAgB,gCA8D/B"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Statuskaart in de instellingen: laat in gewone taal zien of de module in
4
+ * déze omgeving volledig werkt, en biedt één knop om vastgelopen
5
+ * voorraadwijzigingen weer op gang te helpen.
6
+ */
7
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
+ import { useServerFn } from "@tanstack/react-start";
9
+ import { AlertTriangle, CheckCircle2, RotateCw, XCircle } from "lucide-react";
10
+ import { toast } from "sonner";
11
+ import { getModuleHealth, recoverStockQueue, } from "../../lib/module-health.functions";
12
+ function StatusIcon({ status }) {
13
+ if (status === "ok")
14
+ return _jsx(CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 text-emerald-600" });
15
+ if (status === "warn")
16
+ return _jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" });
17
+ return _jsx(XCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-destructive" });
18
+ }
19
+ export function ModuleHealthCard() {
20
+ const fetchHealth = useServerFn(getModuleHealth);
21
+ const recover = useServerFn(recoverStockQueue);
22
+ const queryClient = useQueryClient();
23
+ const { data, isLoading } = useQuery({
24
+ queryKey: ["module-health"],
25
+ queryFn: () => fetchHealth({}),
26
+ refetchInterval: 60000,
27
+ });
28
+ const mutation = useMutation({
29
+ mutationFn: () => recover({}),
30
+ onSuccess: (res) => {
31
+ toast.success(res?.restored
32
+ ? `${res.restored} voorraadwijziging${res.restored === 1 ? "" : "en"} opnieuw ingepland.`
33
+ : "Er stond niets vast.");
34
+ void queryClient.invalidateQueries({ queryKey: ["module-health"] });
35
+ void queryClient.invalidateQueries({ queryKey: ["stock-sync-health"] });
36
+ },
37
+ onError: (e) => toast.error(e?.message ?? "Herstellen is niet gelukt."),
38
+ });
39
+ return (_jsxs("section", { className: "rounded-lg border border-border bg-card p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Status van de voorraadmodule" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Controleert de koppeling, de achtergrondtaken en de synchronisatie naar Floriday." })] }), _jsxs("button", { type: "button", className: "inline-flex shrink-0 items-center rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:opacity-50", disabled: mutation.isPending, onClick: () => mutation.mutate(), children: [_jsx(RotateCw, { className: `mr-1.5 h-3.5 w-3.5 ${mutation.isPending ? "animate-spin" : ""}` }), "Vastgelopen taken hervatten"] })] }), _jsxs("ul", { className: "mt-4 space-y-2.5", children: [isLoading && _jsx("li", { className: "text-sm text-muted-foreground", children: "Bezig met controleren\u2026" }), data?.checks.map((check) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx(StatusIcon, { status: check.status }), _jsxs("div", { className: "min-w-0", children: [_jsxs("span", { className: "font-medium text-foreground", children: [check.label, ": "] }), _jsx("span", { className: "text-muted-foreground break-words", children: check.detail }), check.hint && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground/80 break-words", children: check.hint }))] })] }, check.id)))] })] }));
40
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsF3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+BAmoBA"}
1
+ {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAyF3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+BAmoBA"}
@@ -24,10 +24,13 @@ const COLUMNS = [
24
24
  "Inkomende voorraad",
25
25
  ];
26
26
  /**
27
- * Bepaalt de sleutel in `trade_item_stock`. Sinds de dedup-migratie is dat altijd
28
- * `product:<uuid>`; oude `draft:` / `floriday:` waarden zijn ge-repoint.
27
+ * Bepaalt de canonieke sleutel in `trade_item_stock`: de kale Floriday-UUID
28
+ * zodra het artikel gekoppeld is (die sleutel activeert de push naar Floriday),
29
+ * anders `product:<uuid>`.
29
30
  */
30
31
  function toKey(item) {
32
+ if (item.floridayId)
33
+ return item.floridayId;
31
34
  if (item.tradeItemKey)
32
35
  return item.tradeItemKey;
33
36
  if (item.productId)
@@ -0,0 +1,4 @@
1
+ export declare function StockPushMonitor({ connectionId }: {
2
+ connectionId: string | null;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=StockPushMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"AAiFA,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+BAyJjF"}
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Testpaneel voor de voorraadpush naar Floriday.
4
+ *
5
+ * De push zelf gebeurt niet in dit scherm: elke voorraadwijziging schrijft een
6
+ * opdracht in de gedeelde wachtrij, die de Floriday-Authenticatiemodule elke
7
+ * minuut verwerkt. Dit paneel maakt die keten zichtbaar en laat je een testpush
8
+ * inplannen zonder de voorraad te wijzigen.
9
+ */
10
+ import { useEffect, useMemo, useState } from "react";
11
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
12
+ import { useServerFn } from "@tanstack/react-start";
13
+ import { toast } from "sonner";
14
+ import { ChevronDown, ChevronRight, RefreshCw, Send } from "lucide-react";
15
+ import { supabase } from "../../integrations/supabase/client";
16
+ import { useStockPushJobs, useCentralAuthBlockade } from "../../hooks/useStockPushJobs";
17
+ import { enqueueTestStockPush } from "../../lib/stock-sync.functions";
18
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19
+ const STATUS_LABEL = {
20
+ pending: "In wachtrij",
21
+ processing: "Bezig",
22
+ completed: "Verstuurd",
23
+ failed: "Mislukt",
24
+ superseded: "Vervangen",
25
+ };
26
+ function statusClass(status) {
27
+ if (status === "completed")
28
+ return "bg-emerald-500/15 text-emerald-700";
29
+ if (status === "failed")
30
+ return "bg-destructive/15 text-destructive";
31
+ if (status === "superseded")
32
+ return "bg-muted text-muted-foreground";
33
+ return "bg-amber-500/15 text-amber-700";
34
+ }
35
+ function timeLabel(iso) {
36
+ if (!iso)
37
+ return "—";
38
+ const d = new Date(iso);
39
+ return Number.isNaN(d.getTime()) ? "—" : d.toLocaleTimeString("nl-NL");
40
+ }
41
+ function useLinkedTradeItems(connectionId) {
42
+ return useQuery({
43
+ queryKey: ["linked-trade-items", connectionId],
44
+ enabled: !!connectionId,
45
+ queryFn: async () => {
46
+ const { data, error } = await supabase
47
+ .from("trade_item_stock")
48
+ .select("trade_item_key, physical_stock")
49
+ .eq("connection_id", connectionId)
50
+ .limit(1000);
51
+ if (error)
52
+ throw error;
53
+ const rows = (data ?? []).filter((r) => UUID_RE.test(String(r.trade_item_key)));
54
+ if (rows.length === 0)
55
+ return [];
56
+ const ids = rows.map((r) => String(r.trade_item_key));
57
+ const { data: cached } = await supabase
58
+ .from("floriday_trade_items_cache")
59
+ .select("floriday_id, data")
60
+ .eq("connection_id", connectionId)
61
+ .in("floriday_id", ids);
62
+ const names = new Map();
63
+ for (const row of (cached ?? [])) {
64
+ const d = row.data;
65
+ const label = d?.tradeItemName ?? d?.name ?? d?.productName ?? null;
66
+ if (label)
67
+ names.set(String(row.floriday_id), String(label));
68
+ }
69
+ return rows
70
+ .filter((r) => names.has(String(r.trade_item_key)))
71
+ .map((r) => ({
72
+ id: String(r.trade_item_key),
73
+ name: names.get(String(r.trade_item_key)),
74
+ stock: Number(r.physical_stock ?? 0),
75
+ }))
76
+ .sort((a, b) => a.name.localeCompare(b.name, "nl"));
77
+ },
78
+ });
79
+ }
80
+ export function StockPushMonitor({ connectionId }) {
81
+ const [open, setOpen] = useState(false);
82
+ const [selected, setSelected] = useState("");
83
+ const queryClient = useQueryClient();
84
+ const jobs = useStockPushJobs(10);
85
+ const blockade = useCentralAuthBlockade();
86
+ const items = useLinkedTradeItems(open ? connectionId : null);
87
+ const testPush = useServerFn(enqueueTestStockPush);
88
+ useEffect(() => {
89
+ if (!selected && items.data && items.data.length > 0) {
90
+ setSelected(items.data[0].id);
91
+ }
92
+ }, [items.data, selected]);
93
+ const mutation = useMutation({
94
+ mutationFn: (tradeItemId) => testPush({ data: { tradeItemId } }),
95
+ onSuccess: (res) => {
96
+ toast.success(`Testpush ingepland (${res?.numberOfPieces ?? 0} stuks). Binnen een minuut verwerkt.`);
97
+ void queryClient.invalidateQueries({ queryKey: ["stock-push-jobs"] });
98
+ void queryClient.invalidateQueries({ queryKey: ["stock-sync-health"] });
99
+ },
100
+ onError: (e) => toast.error(e?.message ?? "Testpush inplannen is niet gelukt."),
101
+ });
102
+ const rows = jobs.data ?? [];
103
+ const summary = useMemo(() => {
104
+ if (rows.length === 0)
105
+ return "nog geen pushes";
106
+ const last = rows[0];
107
+ return `laatste: ${STATUS_LABEL[last.status] ?? last.status} om ${timeLabel(last.createdAt)}`;
108
+ }, [rows]);
109
+ return (_jsxs("section", { className: "rounded-lg border bg-card", children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", onClick: () => setOpen((v) => !v), children: [open ? (_jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })), "Voorraadsync naar Floriday testen", _jsx("span", { className: "ml-auto text-xs font-normal text-muted-foreground", children: summary })] }), open && (_jsxs("div", { className: "space-y-4 border-t px-4 py-4 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "Elke voorraadwijziging van een gekoppeld artikel zet automatisch een opdracht in de wachtrij. De Floriday-Authenticatiemodule verwerkt die elke minuut en stuurt dan het aantal stuks en de beschikbaarheid naar Floriday. Hieronder plan je een testopdracht in met de huidige voorraadstand, zonder iets te wijzigen." }), blockade.data?.blocked && (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-amber-800", children: [_jsx("span", { className: "font-medium", children: "Let op: " }), "de tokenaanvraag bij Central Auth mislukt op dit moment, dus elke push loopt daarop vast. Dit ligt buiten deze module.", blockade.data.message && (_jsxs("span", { className: "block opacity-80", children: ["Melding: ", blockade.data.message] }))] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { className: "h-9 min-w-[16rem] rounded-md border bg-background px-2 text-sm", value: selected, onChange: (e) => setSelected(e.target.value), disabled: !connectionId || items.isLoading || (items.data ?? []).length === 0, children: (items.data ?? []).length === 0 ? (_jsx("option", { value: "", children: items.isLoading ? "Artikelen laden…" : "Geen gekoppelde artikelen gevonden" })) : ((items.data ?? []).map((i) => (_jsxs("option", { value: i.id, children: [i.name, " \u2014 ", i.stock, " stuks"] }, i.id)))) }), _jsxs("button", { type: "button", className: "inline-flex h-9 items-center rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50", disabled: !selected || mutation.isPending, onClick: () => selected && mutation.mutate(selected), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Testpush uitvoeren"] }), _jsxs("button", { type: "button", className: "inline-flex h-9 items-center rounded-md border px-3 text-sm font-medium hover:bg-muted disabled:opacity-50", disabled: jobs.isFetching, onClick: () => void jobs.refetch(), children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${jobs.isFetching ? "animate-spin" : ""}` }), "Ververs"] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left text-xs", children: [_jsx("thead", { className: "text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "py-1 pr-3 font-medium", children: "Artikel" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Stuks" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Status" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Pogingen" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Aangemaakt" }), _jsx("th", { className: "py-1 font-medium", children: "Melding" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 6, className: "py-3 text-muted-foreground", children: "Er staan nog geen push-opdrachten in de wachtrij." }) })), rows.map((r) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "py-1.5 pr-3", children: r.name }), _jsx("td", { className: "py-1.5 pr-3 tabular-nums", children: r.numberOfPieces }), _jsx("td", { className: "py-1.5 pr-3", children: _jsx("span", { className: `rounded px-1.5 py-0.5 ${statusClass(r.status)}`, children: STATUS_LABEL[r.status] ?? r.status }) }), _jsx("td", { className: "py-1.5 pr-3 tabular-nums", children: r.attempts }), _jsx("td", { className: "py-1.5 pr-3 whitespace-nowrap", children: timeLabel(r.createdAt) }), _jsx("td", { className: "py-1.5 max-w-[24rem] truncate text-muted-foreground", title: r.lastError ?? "", children: r.lastError ?? "—" })] }, r.id)))] })] }) })] }))] }));
110
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAgGA,wBAAgB,YAAY,gCA6O3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAiGA,wBAAgB,YAAY,gCA+O3B"}
@@ -6,6 +6,7 @@ import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection";
7
7
  import { useVoorraadData } from "../../hooks/useVoorraadData";
8
8
  import { StockSyncStatus } from "./StockSyncStatus";
9
+ import { StockPushMonitor } from "./StockPushMonitor";
9
10
  import { ExcelExportButton } from "./ExcelExportButton";
10
11
  import { StockBulkButtons } from "./StockBulkButtons";
11
12
  import { VoorraadTable } from "./VoorraadTable";
@@ -93,7 +94,7 @@ export function VoorraadPage() {
93
94
  { id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
94
95
  { id: "below_min", label: "Onder minimum", count: counts.belowMin },
95
96
  ];
96
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockSyncStatus, {}), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-connectie laden mislukt: ", connError] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
97
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockSyncStatus, {}), _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-connectie laden mislukt: ", connError] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
97
98
  const active = stockFilter === t.id;
98
99
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
99
100
  ? "text-foreground"
@@ -1 +1 @@
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,+BAsHzF"}
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,+BA2HzF"}
@@ -10,19 +10,21 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
10
10
  const { row, loading, save } = useTradeItemStock(connectionId, keys);
11
11
  const [values, setValues] = useState(row);
12
12
  const [saving, setSaving] = useState(false);
13
+ const [touched, setTouched] = useState(false);
13
14
  const floridayPieces = useFloridayContinuousStock(connectionId, tradeItemId ?? null);
14
15
  useEffect(() => {
15
16
  setValues(row);
17
+ setTouched(false);
16
18
  }, [row.updated_at, row.physical_stock, row.minimum_stock, row.reserved_stock, row.incoming_stock, row.notes]);
17
- // Silent sync: als Floriday een nieuwe voorraad terugmeldt, overschrijven
18
- // we het "Liggend"-veld zonder extra UI-signaal.
19
+ // Floriday mag "Liggend" alleen bijwerken zolang de gebruiker het veld niet
20
+ // zelf heeft aangepast — anders zou een handmatige invoer stil verdwijnen.
19
21
  useEffect(() => {
20
- if (floridayPieces === undefined)
22
+ if (floridayPieces === undefined || touched)
21
23
  return;
22
24
  setValues((v) => v.physical_stock === floridayPieces
23
25
  ? v
24
26
  : { ...v, physical_stock: floridayPieces });
25
- }, [floridayPieces]);
27
+ }, [floridayPieces, touched]);
26
28
  if (!key) {
27
29
  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." }));
28
30
  }
@@ -40,6 +42,9 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
40
42
  setSaving(false);
41
43
  }
42
44
  };
43
- const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, value: String(values[id] ?? 0), onChange: (e) => setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 })) })] }));
45
+ const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, value: String(values[id] ?? 0), onChange: (e) => {
46
+ setTouched(true);
47
+ setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
48
+ } })] }));
44
49
  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(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), numField("reserved_stock", "Gereserveerd"), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: handleSave, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] }) })] })) })] }));
45
50
  }
@@ -0,0 +1,23 @@
1
+ export interface StockPushJob {
2
+ id: string;
3
+ tradeItemId: string;
4
+ name: string;
5
+ numberOfPieces: number;
6
+ desiredAvailable: boolean | null;
7
+ status: string;
8
+ attempts: number;
9
+ lastError: string | null;
10
+ createdAt: string;
11
+ updatedAt: string | null;
12
+ sentAt: string | null;
13
+ }
14
+ export declare function useStockPushJobs(limit?: number): import("@tanstack/react-query").UseQueryResult<NoInfer<StockPushJob[]>, Error>;
15
+ /**
16
+ * Signaleert of de tokenaanvraag bij Central Auth structureel faalt. Zolang dat
17
+ * zo is, mislukt elke push — ongeacht wat deze module doet.
18
+ */
19
+ export declare function useCentralAuthBlockade(): import("@tanstack/react-query").UseQueryResult<NoInfer<{
20
+ blocked: boolean;
21
+ message: string | null;
22
+ }>, Error>;
23
+ //# sourceMappingURL=useStockPushJobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStockPushJobs.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockPushJobs.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAaD,wBAAgB,gBAAgB,CAAC,KAAK,SAAK,kFA4C1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;aACT,OAAO;aAAW,MAAM,GAAG,IAAI;WAoB3D"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Leest de laatste push-opdrachten richting Floriday uit de gedeelde wachtrij,
3
+ * inclusief de artikelnaam uit de Floriday-cache. Bedoeld om een testpush te
4
+ * kunnen volgen zonder databasekennis.
5
+ */
6
+ import { useQuery } from "@tanstack/react-query";
7
+ import { supabase } from "../integrations/supabase/client";
8
+ function nameFromCache(data) {
9
+ if (!data || typeof data !== "object")
10
+ return null;
11
+ return (data.tradeItemName ??
12
+ data.name ??
13
+ data.productName ??
14
+ data.vbnProductName ??
15
+ null);
16
+ }
17
+ export function useStockPushJobs(limit = 10) {
18
+ return useQuery({
19
+ queryKey: ["stock-push-jobs", limit],
20
+ refetchInterval: 15000,
21
+ queryFn: async () => {
22
+ const { data, error } = await supabase
23
+ .from("floriday_continuous_stock_queue")
24
+ .select("id, trade_item_id, number_of_pieces, desired_available, status, attempts, last_error, created_at, updated_at, sent_at")
25
+ .order("created_at", { ascending: false })
26
+ .limit(limit);
27
+ if (error)
28
+ throw error;
29
+ const rows = (data ?? []);
30
+ const ids = Array.from(new Set(rows.map((r) => r.trade_item_id).filter(Boolean)));
31
+ const names = new Map();
32
+ if (ids.length > 0) {
33
+ const { data: cached } = await supabase
34
+ .from("floriday_trade_items_cache")
35
+ .select("floriday_id, data")
36
+ .in("floriday_id", ids);
37
+ for (const row of (cached ?? [])) {
38
+ const label = nameFromCache(row.data);
39
+ if (label)
40
+ names.set(String(row.floriday_id), String(label));
41
+ }
42
+ }
43
+ return rows.map((r) => ({
44
+ id: String(r.id),
45
+ tradeItemId: String(r.trade_item_id ?? ""),
46
+ name: names.get(String(r.trade_item_id)) ?? String(r.trade_item_id ?? "").slice(0, 8),
47
+ numberOfPieces: Number(r.number_of_pieces ?? 0),
48
+ desiredAvailable: r.desired_available ?? null,
49
+ status: String(r.status ?? ""),
50
+ attempts: Number(r.attempts ?? 0),
51
+ lastError: r.last_error ?? null,
52
+ createdAt: String(r.created_at),
53
+ updatedAt: r.updated_at ?? null,
54
+ sentAt: r.sent_at ?? null,
55
+ }));
56
+ },
57
+ });
58
+ }
59
+ /**
60
+ * Signaleert of de tokenaanvraag bij Central Auth structureel faalt. Zolang dat
61
+ * zo is, mislukt elke push — ongeacht wat deze module doet.
62
+ */
63
+ export function useCentralAuthBlockade() {
64
+ return useQuery({
65
+ queryKey: ["central-auth-blockade"],
66
+ refetchInterval: 60000,
67
+ queryFn: async () => {
68
+ const since = new Date(Date.now() - 30 * 60000).toISOString();
69
+ const { data, error } = await supabase
70
+ .from("floriday_token_refresh_log")
71
+ .select("ok, error, attempted_at")
72
+ .gte("attempted_at", since)
73
+ .order("attempted_at", { ascending: false })
74
+ .limit(5);
75
+ if (error)
76
+ return { blocked: false, message: null };
77
+ const rows = (data ?? []);
78
+ if (rows.length === 0)
79
+ return { blocked: false, message: null };
80
+ const latest = rows[0];
81
+ if (latest.ok)
82
+ return { blocked: false, message: null };
83
+ return { blocked: true, message: latest.error ?? null };
84
+ },
85
+ });
86
+ }
@@ -23,6 +23,18 @@ export declare function stockKeysFor(params: {
23
23
  draftId?: string | null;
24
24
  productId?: string | null;
25
25
  }): string[];
26
+ /**
27
+ * Zoekt de voorraadrij van één artikel op in een reeds geladen map. Sinds de
28
+ * canonicalisatie is er nog maar één rij per artikel (de Floriday-UUID wanneer
29
+ * het artikel gekoppeld is, anders `product:<uuid>`), maar we kijken bewust
30
+ * naar alle bekende varianten zodat oude data nooit als "0" wordt getoond.
31
+ */
32
+ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params: {
33
+ tradeItemKey?: string | null;
34
+ floridayId?: string | null;
35
+ productId?: string | null;
36
+ draftId?: string | null;
37
+ }): TradeItemStockRow | undefined;
26
38
  export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null): {
27
39
  row: TradeItemStockRow;
28
40
  loading: boolean;
@@ -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,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAKhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAMX;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;;;mBA4ElB,iBAAiB;EAmEnC;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,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAKhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAMX;AAED;;;;;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;EAmEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
@@ -33,6 +33,28 @@ export function stockKeysFor(params) {
33
33
  keys.push(`draft:${params.draftId}`);
34
34
  return keys;
35
35
  }
36
+ /**
37
+ * Zoekt de voorraadrij van één artikel op in een reeds geladen map. Sinds de
38
+ * canonicalisatie is er nog maar één rij per artikel (de Floriday-UUID wanneer
39
+ * het artikel gekoppeld is, anders `product:<uuid>`), maar we kijken bewust
40
+ * naar alle bekende varianten zodat oude data nooit als "0" wordt getoond.
41
+ */
42
+ export function lookupStock(map, params) {
43
+ const candidates = [
44
+ params.floridayId ?? null,
45
+ params.tradeItemKey ?? null,
46
+ params.productId ? `product:${params.productId}` : null,
47
+ params.draftId ? `draft:${params.draftId}` : null,
48
+ ];
49
+ for (const key of candidates) {
50
+ if (!key)
51
+ continue;
52
+ const hit = map.get(key);
53
+ if (hit)
54
+ return hit;
55
+ }
56
+ return undefined;
57
+ }
36
58
  export function useTradeItemStock(connectionId, keyOrKeys) {
37
59
  const keysKey = Array.isArray(keyOrKeys)
38
60
  ? keyOrKeys.filter(Boolean).join("|")
@@ -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;;;;;EA+T1D"}
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"}
@@ -3,7 +3,7 @@ import { supabase } from "../integrations/supabase/client";
3
3
  import { normaliseTradeItem } from "../lib/floriday-client";
4
4
  import { floridayInfoFromCache } from "../lib/floriday-info";
5
5
  import { useLocalTradeItems } from "./useLocalTradeItems";
6
- import { fetchStockMap } from "./useTradeItemStock";
6
+ import { fetchStockMap, lookupStock } from "./useTradeItemStock";
7
7
  import { useFloridayContinuousStockMap } from "./useFloridayContinuousStock";
8
8
  function applyStock(item, s) {
9
9
  if (!s)
@@ -283,10 +283,18 @@ export function useVoorraadData(connectionId) {
283
283
  return { ...i, floriday: info };
284
284
  };
285
285
  const merged = [
286
- ...localItems.map((i) => applyProductGroupReservation(classify(attachFloriday(applyStock(i, stockMap.get(i.tradeItemKey ?? "")))))),
286
+ ...localItems.map((i) => applyProductGroupReservation(classify(attachFloriday(applyStock(i, lookupStock(stockMap, {
287
+ tradeItemKey: i.tradeItemKey ?? null,
288
+ floridayId: i.floridayId ?? null,
289
+ productId: i.productId ?? null,
290
+ })))))),
287
291
  ...publishedItems
288
292
  .filter((i) => !i.id || !localFloridayIds.has(i.id))
289
- .map((i) => applyProductGroupReservation(classify(applyFloridayOverride(applyStock(i, stockMap.get(i.id)))))),
293
+ .map((i) => applyProductGroupReservation(classify(applyFloridayOverride(applyStock(i, lookupStock(stockMap, {
294
+ floridayId: i.id,
295
+ tradeItemKey: i.tradeItemKey ?? null,
296
+ productId: i.productId ?? null,
297
+ })))))),
290
298
  ];
291
299
  return {
292
300
  items: merged,
@@ -8,5 +8,6 @@ export { ArtikelWizard } from './components/voorraad/ArtikelWizard';
8
8
  export { ProductLinkCard } from './components/voorraad/ProductLinkCard';
9
9
  export { VoorraadVeldenCard } from './components/voorraad/VoorraadVeldenCard';
10
10
  export { ProductCustomFieldsCard } from './components/voorraad/ProductCustomFieldsCard';
11
+ export { ModuleHealthCard } from './components/voorraad/ModuleHealthCard';
11
12
  export declare const floridayVoorraadModule: FlowModule;
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAIxF,eAAO,MAAM,sBAAsB,EAAE,UAcpC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAKxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAE1E,eAAO,MAAM,sBAAsB,EAAE,UAiBpC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -12,6 +12,8 @@ export { ProductLinkCard } from './components/voorraad/ProductLinkCard';
12
12
  export { VoorraadVeldenCard } from './components/voorraad/VoorraadVeldenCard';
13
13
  export { ProductCustomFieldsCard } from './components/voorraad/ProductCustomFieldsCard';
14
14
  import { VoorraadVeldenCard } from './components/voorraad/VoorraadVeldenCard';
15
+ import { ModuleHealthCard } from './components/voorraad/ModuleHealthCard';
16
+ export { ModuleHealthCard } from './components/voorraad/ModuleHealthCard';
15
17
  export const floridayVoorraadModule = {
16
18
  id: "voorraad",
17
19
  name: "Voorraad",
@@ -25,5 +27,8 @@ export const floridayVoorraadModule = {
25
27
  { label: "Fusten", href: "/voorraad/fusten", icon: Boxes },
26
28
  ],
27
29
  },
28
- settingsCards: [{ component: VoorraadVeldenCard, order: 50 }],
30
+ settingsCards: [
31
+ { component: VoorraadVeldenCard, order: 50 },
32
+ { component: ModuleHealthCard, order: 60 },
33
+ ],
29
34
  };