@flowselections/floriday-voorraad 1.0.28 → 1.0.29

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 (107) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts +2 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  3. package/dist-lib/components/voorraad/ArtikelWizard.js +555 -316
  4. package/dist-lib/components/voorraad/EditArtikelPage.d.ts +2 -1
  5. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/EditArtikelPage.js +3 -3
  7. package/dist-lib/components/voorraad/FustenPage.d.ts +2 -0
  8. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -0
  9. package/dist-lib/components/voorraad/FustenPage.js +97 -0
  10. package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts +2 -0
  11. package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts.map +1 -0
  12. package/dist-lib/components/voorraad/LogistiekTakenCard.js +54 -0
  13. package/dist-lib/components/voorraad/PublicatiesPage.d.ts +2 -0
  14. package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -0
  15. package/dist-lib/components/voorraad/PublicatiesPage.js +60 -0
  16. package/dist-lib/components/voorraad/PublishProgressCard.d.ts +12 -0
  17. package/dist-lib/components/voorraad/PublishProgressCard.d.ts.map +1 -0
  18. package/dist-lib/components/voorraad/PublishProgressCard.js +30 -0
  19. package/dist-lib/components/voorraad/PublishReadinessCard.d.ts +10 -0
  20. package/dist-lib/components/voorraad/PublishReadinessCard.d.ts.map +1 -0
  21. package/dist-lib/components/voorraad/PublishReadinessCard.js +12 -0
  22. package/dist-lib/components/voorraad/StockSyncStatus.d.ts +2 -0
  23. package/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -0
  24. package/dist-lib/components/voorraad/StockSyncStatus.js +23 -0
  25. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  26. package/dist-lib/components/voorraad/VoorraadPage.js +5 -1
  27. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  28. package/dist-lib/components/voorraad/VoorraadTable.js +1 -1
  29. package/dist-lib/hooks/useFusten.d.ts +17 -0
  30. package/dist-lib/hooks/useFusten.d.ts.map +1 -0
  31. package/dist-lib/hooks/useFusten.js +81 -0
  32. package/dist-lib/hooks/useLocalTradeItems.d.ts +14 -4
  33. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  34. package/dist-lib/hooks/useLocalTradeItems.js +83 -5
  35. package/dist-lib/hooks/useLogisticsTasks.d.ts +13 -0
  36. package/dist-lib/hooks/useLogisticsTasks.d.ts.map +1 -0
  37. package/dist-lib/hooks/useLogisticsTasks.js +155 -0
  38. package/dist-lib/hooks/usePublishJob.d.ts +13 -0
  39. package/dist-lib/hooks/usePublishJob.d.ts.map +1 -0
  40. package/dist-lib/hooks/usePublishJob.js +67 -0
  41. package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts +2 -0
  42. package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts.map +1 -0
  43. package/dist-lib/hooks/useRegisterModuleBaseUrl.js +31 -0
  44. package/dist-lib/hooks/useStockSyncHealth.d.ts +8 -0
  45. package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -0
  46. package/dist-lib/hooks/useStockSyncHealth.js +42 -0
  47. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  48. package/dist-lib/hooks/useTradeItemStock.js +37 -1
  49. package/dist-lib/index.d.ts.map +1 -1
  50. package/dist-lib/index.js +5 -1
  51. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1849 -50
  52. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  53. package/dist-lib/integrations/supabase/client.d.ts +1849 -50
  54. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  55. package/dist-lib/integrations/supabase/client.server.d.ts +1849 -50
  56. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  57. package/dist-lib/integrations/supabase/types.d.ts +1870 -41
  58. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  59. package/dist-lib/lib/floricode-required-features.functions.d.ts +1849 -50
  60. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floriday-client.d.ts +1 -1
  62. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  63. package/dist-lib/lib/floriday-client.js +1 -2
  64. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +1849 -50
  65. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  66. package/dist-lib/lib/floriday-customer-offer.functions.js +14 -19
  67. package/dist-lib/lib/floriday-env.functions.d.ts +35712 -0
  68. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -0
  69. package/dist-lib/lib/floriday-env.functions.js +16 -0
  70. package/dist-lib/lib/floriday-env.server.d.ts +12 -0
  71. package/dist-lib/lib/floriday-env.server.d.ts.map +1 -1
  72. package/dist-lib/lib/floriday-env.server.js +15 -2
  73. package/dist-lib/lib/floriday-gateway.functions.d.ts +26934 -82
  74. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  75. package/dist-lib/lib/floriday-gateway.functions.js +12 -3
  76. package/dist-lib/lib/floriday-readiness.server.d.ts +11 -0
  77. package/dist-lib/lib/floriday-readiness.server.d.ts.map +1 -0
  78. package/dist-lib/lib/floriday-readiness.server.js +43 -0
  79. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +3698 -100
  80. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  81. package/dist-lib/lib/floriday-warehouse.functions.d.ts +5442 -45
  82. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  83. package/dist-lib/lib/floriday-warehouse.functions.js +3 -0
  84. package/dist-lib/lib/floriday-writes.functions.d.ts +3698 -100
  85. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  86. package/dist-lib/lib/fusten.d.ts +87 -0
  87. package/dist-lib/lib/fusten.d.ts.map +1 -0
  88. package/dist-lib/lib/fusten.js +389 -0
  89. package/dist-lib/lib/logistics-engine.d.ts +63 -0
  90. package/dist-lib/lib/logistics-engine.d.ts.map +1 -0
  91. package/dist-lib/lib/logistics-engine.js +57 -0
  92. package/dist-lib/lib/logistics-events.d.ts +18 -0
  93. package/dist-lib/lib/logistics-events.d.ts.map +1 -0
  94. package/dist-lib/lib/logistics-events.js +61 -0
  95. package/dist-lib/lib/module-runtime-config.functions.d.ts +14 -0
  96. package/dist-lib/lib/module-runtime-config.functions.d.ts.map +1 -0
  97. package/dist-lib/lib/module-runtime-config.functions.js +107 -0
  98. package/dist-lib/lib/publish-errors.d.ts +37 -0
  99. package/dist-lib/lib/publish-errors.d.ts.map +1 -0
  100. package/dist-lib/lib/publish-errors.js +183 -0
  101. package/dist-lib/lib/publish-pipeline.d.ts +91 -0
  102. package/dist-lib/lib/publish-pipeline.d.ts.map +1 -0
  103. package/dist-lib/lib/publish-pipeline.js +212 -0
  104. package/dist-lib/lib/publish-readiness.d.ts +42 -0
  105. package/dist-lib/lib/publish-readiness.d.ts.map +1 -0
  106. package/dist-lib/lib/publish-readiness.js +102 -0
  107. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
- export declare function EditArtikelPage({ id }: {
1
+ export declare function EditArtikelPage({ id, resumeJobId, }: {
2
2
  id: string;
3
+ resumeJobId?: string;
3
4
  }): import("react").JSX.Element;
4
5
  //# sourceMappingURL=EditArtikelPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAmFrD"}
1
+ {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,WAAW,GACZ,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAmFA"}
@@ -5,7 +5,7 @@ import { Loader2 } from "lucide-react";
5
5
  import { ArtikelWizard } from "./ArtikelWizard";
6
6
  import { ProductCustomFieldsCard } from "./ProductCustomFieldsCard";
7
7
  import { supabase } from "../../integrations/supabase/client";
8
- export function EditArtikelPage({ id }) {
8
+ export function EditArtikelPage({ id, resumeJobId, }) {
9
9
  const [resolved, setResolved] = useState(null);
10
10
  useEffect(() => {
11
11
  let cancelled = false;
@@ -63,7 +63,7 @@ export function EditArtikelPage({ id }) {
63
63
  return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 text-sm space-y-1", children: [_jsx("div", { children: "Artikel niet gevonden." }), resolved.reason && (_jsx("div", { className: "text-muted-foreground", children: resolved.reason }))] }) }));
64
64
  }
65
65
  if (resolved.kind === "product") {
66
- return (_jsxs(_Fragment, { children: [_jsx(ArtikelWizard, { productId: resolved.id }), _jsx("div", { className: "p-6 pt-0", children: _jsx(ProductCustomFieldsCard, { productId: resolved.id }) })] }));
66
+ return (_jsxs(_Fragment, { children: [_jsx(ArtikelWizard, { productId: resolved.id, resumeJobId: resumeJobId }), _jsx("div", { className: "p-6 pt-0", children: _jsx(ProductCustomFieldsCard, { productId: resolved.id }) })] }));
67
67
  }
68
- return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
68
+ return _jsx(ArtikelWizard, { tradeItemId: resolved.id, resumeJobId: resumeJobId });
69
69
  }
@@ -0,0 +1,2 @@
1
+ export declare function FustenPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=FustenPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AA2JA,wBAAgB,UAAU,gCAkMzB"}
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Boxes, Minus, Plus, RefreshCw, Search } from "lucide-react";
4
+ import { Button, Card, Input, Badge } from "@flowselections/core";
5
+ import { useFloridayConnection } from "../../hooks/useFloridayConnection";
6
+ import { useFusten } from "../../hooks/useFusten";
7
+ import { fetchFustMutations, formatFustDimensions, } from "../../lib/fusten";
8
+ /** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
9
+ function DimensionCell({ fust, onSave, }) {
10
+ const [editing, setEditing] = useState(false);
11
+ const [draft, setDraft] = useState({
12
+ length_mm: fust.length_mm?.toString() ?? "",
13
+ width_mm: fust.width_mm?.toString() ?? "",
14
+ height_mm: fust.height_mm?.toString() ?? "",
15
+ });
16
+ const label = formatFustDimensions(fust);
17
+ const toNum = (v) => {
18
+ const n = Math.trunc(Number(v));
19
+ return Number.isFinite(n) && n > 0 ? n : null;
20
+ };
21
+ if (!editing) {
22
+ return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
23
+ setDraft({
24
+ length_mm: fust.length_mm?.toString() ?? "",
25
+ width_mm: fust.width_mm?.toString() ?? "",
26
+ height_mm: fust.height_mm?.toString() ?? "",
27
+ });
28
+ setEditing(true);
29
+ }, children: label ? "Wijzigen" : "Afmeting toevoegen" })] }));
30
+ }
31
+ return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1", children: [["length_mm", "width_mm", "height_mm"].map((k, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [i > 0 && _jsx("span", { className: "text-muted-foreground", children: "\u00D7" }), _jsx(Input, { type: "number", className: "h-8 w-20 text-right", placeholder: ["L", "B", "H"][i], value: draft[k], onChange: (e) => setDraft((d) => ({ ...d, [k]: e.target.value })) })] }, k))), _jsx("span", { className: "text-xs text-muted-foreground", children: "mm" })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", className: "h-7", onClick: async () => {
32
+ await onSave({
33
+ length_mm: toNum(draft.length_mm),
34
+ width_mm: toNum(draft.width_mm),
35
+ height_mm: toNum(draft.height_mm),
36
+ });
37
+ setEditing(false);
38
+ }, children: "Opslaan" }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-7", onClick: () => setEditing(false), children: "Annuleren" })] })] }));
39
+ }
40
+ function MutationHistory({ fust }) {
41
+ const [open, setOpen] = useState(false);
42
+ const [rows, setRows] = useState(null);
43
+ const toggle = async () => {
44
+ const next = !open;
45
+ setOpen(next);
46
+ if (next && rows === null) {
47
+ try {
48
+ setRows(await fetchFustMutations(fust.id));
49
+ }
50
+ catch {
51
+ setRows([]);
52
+ }
53
+ }
54
+ };
55
+ return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggle, className: "text-xs text-primary hover:underline", children: open ? "Historie verbergen" : "Historie" }), open && (_jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/30 p-2 text-xs", children: [rows === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), rows !== null && rows.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen mutaties." })), rows !== null &&
56
+ rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(m.created_at).toLocaleString("nl-NL") }), _jsxs("span", { className: "flex-1 truncate", children: [m.reason === "sale" ? "Verkoop" : "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
57
+ }
58
+ export function FustenPage() {
59
+ const { activeId } = useFloridayConnection();
60
+ const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
61
+ const [search, setSearch] = useState("");
62
+ const [busyId, setBusyId] = useState(null);
63
+ const [amounts, setAmounts] = useState({});
64
+ const filtered = useMemo(() => {
65
+ const q = search.trim().toLowerCase();
66
+ if (!q)
67
+ return items;
68
+ return items.filter((f) => f.name.toLowerCase().includes(q) ||
69
+ String(f.vbn_code ?? "").includes(q) ||
70
+ f.fust_key.toLowerCase().includes(q));
71
+ }, [items, search]);
72
+ const totals = useMemo(() => ({
73
+ types: items.length,
74
+ stock: items.reduce((sum, f) => sum + f.stock, 0),
75
+ belowMin: items.filter((f) => f.minimum_stock > 0 && f.stock < f.minimum_stock).length,
76
+ }), [items]);
77
+ const mutate = async (f, sign) => {
78
+ const step = Math.trunc(Number(amounts[f.id]) || 0);
79
+ if (step <= 0)
80
+ return;
81
+ setBusyId(f.id);
82
+ setAmounts((a) => ({ ...a, [f.id]: "" }));
83
+ try {
84
+ await adjust(f.id, sign * step, sign > 0 ? "Handmatig bijgeboekt" : "Handmatig afgeboekt");
85
+ }
86
+ finally {
87
+ setBusyId(null);
88
+ }
89
+ };
90
+ 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: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: _jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
91
+ ? "text-rose-600"
92
+ : ""}`, children: f.stock }) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
93
+ const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
94
+ if (v !== f.minimum_stock)
95
+ void patch(f.id, { minimum_stock: v });
96
+ } }) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, -1), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { className: "h-8 w-28 text-center", type: "number", placeholder: "Aantal", value: amounts[f.id] ?? "", onChange: (e) => setAmounts((a) => ({ ...a, [f.id]: e.target.value })) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] }) })] }, f.id))) })] }) })) })] }));
97
+ }
@@ -0,0 +1,2 @@
1
+ export declare function LogistiekTakenCard(): import("react").JSX.Element | null;
2
+ //# sourceMappingURL=LogistiekTakenCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogistiekTakenCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/LogistiekTakenCard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,kBAAkB,uCAoIjC"}
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Toont de taken die de Logistics Engine voor de voorraadmodule plande
4
+ * (public.logistics_tasks, owner_module = "voorraad").
5
+ *
6
+ * Deze module beslist niets zelf: ze voert `stock.reserve` uit en meldt de
7
+ * taak af. In schaduwmodus wordt alleen vergeleken, nooit geschreven.
8
+ */
9
+ import { useState } from "react";
10
+ import { Button, Card } from "@flowselections/core";
11
+ import { AlertTriangle, Eye, Play, RefreshCw } from "lucide-react";
12
+ import { runStockReserveTask, useLogisticsTasks, } from "../../hooks/useLogisticsTasks";
13
+ import { useFloridayConnection } from "../../hooks/useFloridayConnection";
14
+ import { describeLoadCarriers, parseStockReservePlan, } from "../../lib/logistics-engine";
15
+ export function LogistiekTakenCard() {
16
+ const { tasks, loading, error, refetch } = useLogisticsTasks([
17
+ "planned",
18
+ "ready",
19
+ ]);
20
+ const { activeId } = useFloridayConnection();
21
+ const [busyId, setBusyId] = useState(null);
22
+ const [results, setResults] = useState({});
23
+ const reserveTasks = tasks.filter((t) => t.task_type === "stock.reserve");
24
+ if (loading)
25
+ return null;
26
+ if (!error && reserveTasks.length === 0)
27
+ return null;
28
+ async function handleRun(taskId) {
29
+ const task = reserveTasks.find((t) => t.id === taskId);
30
+ if (!task)
31
+ return;
32
+ setBusyId(taskId);
33
+ try {
34
+ const result = await runStockReserveTask(task, activeId);
35
+ setResults((prev) => ({ ...prev, [taskId]: result }));
36
+ }
37
+ catch (e) {
38
+ setResults((prev) => ({ ...prev, [taskId]: e?.message ?? String(e) }));
39
+ }
40
+ finally {
41
+ setBusyId(null);
42
+ refetch();
43
+ }
44
+ }
45
+ return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold", children: "Logistics Engine \u2014 taken" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "De Engine plant; deze module voert alleen uit. Taken met schaduwmodus worden uitsluitend vergeleken." })] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => refetch(), children: _jsx(RefreshCw, { className: "h-4 w-4" }) })] }), error && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("span", { children: ["Taken niet leesbaar: ", error] })] })), _jsx("ul", { className: "space-y-2", children: reserveTasks.map((task) => {
46
+ const plan = parseStockReservePlan(task.payload);
47
+ const result = results[task.id];
48
+ return (_jsxs("li", { className: "rounded-md border border-border p-3 text-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium", children: [task.task_type, " \u00B7 ", task.order_ref, task.shadow && (_jsx("span", { className: "ml-2 rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground", children: "schaduw" }))] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [describeLoadCarriers(plan.loadCarriers), " \u00B7", " ", plan.lines.length, " regel(s) \u00B7 status ", task.status, task.rule_version != null
49
+ ? ` · rule v${task.rule_version}`
50
+ : "", task.planned_start
51
+ ? ` · ${new Date(task.planned_start).toLocaleString("nl-NL")}`
52
+ : ""] })] }), _jsx(Button, { size: "sm", variant: task.shadow ? "outline" : "default", onClick: () => handleRun(task.id), disabled: busyId === task.id, children: task.shadow ? (_jsxs(_Fragment, { children: [_jsx(Eye, { className: "mr-1 h-4 w-4" }), " Vergelijk"] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "mr-1 h-4 w-4" }), " Uitvoeren"] })) })] }), result && (_jsx("div", { className: "mt-2 rounded bg-muted/50 p-2 text-xs", children: typeof result === "string" ? (_jsx("span", { className: "text-destructive", children: result })) : (_jsxs(_Fragment, { children: [_jsx("div", { children: result.message }), result.diff.length > 0 && (_jsx("ul", { className: "mt-1 space-y-0.5", children: result.diff.map((d) => (_jsxs("li", { children: [d.tradeItemKey, ": ", d.currentReserved, " \u2192", " ", d.engineReserved, " (", d.delta >= 0 ? "+" : "", d.delta, ")"] }, d.tradeItemKey))) }))] })) }))] }, task.id));
53
+ }) })] }));
54
+ }
@@ -0,0 +1,2 @@
1
+ export declare function PublicatiesPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=PublicatiesPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,eAAe,gCAuI9B"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Link } from "@tanstack/react-router";
3
+ import { Card, Button } from "@flowselections/core";
4
+ import { ArrowLeft, Check, AlertTriangle, Loader2, RefreshCw } from "lucide-react";
5
+ import { useEffect, useState } from "react";
6
+ import { usePublishJobs } from "../../hooks/usePublishJob";
7
+ import { supabase } from "../../integrations/supabase/client";
8
+ const STATUS_LABEL = {
9
+ running: "Bezig",
10
+ succeeded: "Gepubliceerd",
11
+ succeeded_with_warnings: "Aandachtspunten",
12
+ failed: "Actie nodig",
13
+ };
14
+ function StatusChip({ status }) {
15
+ const tone = status === "succeeded"
16
+ ? "bg-emerald-100 text-emerald-800"
17
+ : status === "failed"
18
+ ? "bg-destructive/10 text-destructive"
19
+ : status === "running"
20
+ ? "bg-muted text-muted-foreground"
21
+ : "bg-amber-100 text-amber-800";
22
+ return (_jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${tone}`, children: STATUS_LABEL[status] ?? status }));
23
+ }
24
+ export function PublicatiesPage() {
25
+ const { jobs, loading, reload } = usePublishJobs(50);
26
+ const [stepsByJob, setStepsByJob] = useState({});
27
+ useEffect(() => {
28
+ if (jobs.length === 0)
29
+ return;
30
+ let cancelled = false;
31
+ (async () => {
32
+ var _a;
33
+ const { data } = await supabase
34
+ .from("voorraad_publish_steps")
35
+ .select("*")
36
+ .in("job_id", jobs.map((j) => j.id))
37
+ .order("position");
38
+ if (cancelled)
39
+ return;
40
+ const grouped = {};
41
+ for (const row of (data ?? [])) {
42
+ (grouped[_a = row.job_id] ?? (grouped[_a] = [])).push(row);
43
+ }
44
+ setStepsByJob(grouped);
45
+ })();
46
+ return () => {
47
+ cancelled = true;
48
+ };
49
+ }, [jobs]);
50
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-1 h-3 w-3" }), "Vernieuwen"] })] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Publicaties" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle producten die je hebt gepubliceerd, met wat er eventueel nog moet gebeuren." })] }), loading ? (_jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Laden\u2026"] })) : jobs.length === 0 ? (_jsx(Card, { className: "p-6 text-sm text-muted-foreground", children: "Er zijn nog geen publicaties." })) : (_jsx("div", { className: "space-y-3", children: jobs.map((job) => {
51
+ const steps = stepsByJob[job.id] ?? [];
52
+ const failed = steps.filter((s) => s.status === "failed");
53
+ const target = job.product_id ?? job.trade_item_id;
54
+ return (_jsxs(Card, { className: "p-4 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium", children: job.title || "Product" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [new Date(job.created_at).toLocaleString("nl-NL"), " \u00B7 referentie ", job.reference_code, job.environment !== "live" ? " · testomgeving" : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StatusChip, { status: job.status }), target && failed.length > 0 && (_jsx(Button, { asChild: true, size: "sm", variant: "outline", children: _jsx(Link, { to: "/voorraad/$id", params: { id: String(target) }, search: { hervat: job.id }, children: "Afronden" }) }))] })] }), steps.length > 0 && (_jsx("ul", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs", children: steps
55
+ .filter((s) => s.status !== "skipped")
56
+ .map((s) => (_jsxs("li", { className: "flex items-center gap-1", children: [s.status === "done" ? (_jsx(Check, { className: "h-3 w-3 text-emerald-600" })) : s.status === "failed" ? (_jsx(AlertTriangle, { className: "h-3 w-3 text-destructive" })) : (_jsx(Loader2, { className: "h-3 w-3 text-muted-foreground" })), _jsx("span", { className: s.status === "failed"
57
+ ? "text-destructive"
58
+ : "text-muted-foreground", children: s.label })] }, s.id))) })), failed.map((s) => (_jsxs("p", { className: "text-xs text-destructive", children: [s.label, ": ", s.error_message] }, `err-${s.id}`)))] }, job.id));
59
+ }) }))] }));
60
+ }
@@ -0,0 +1,12 @@
1
+ import type { PublishJobRow, PublishStepRow } from "../../lib/publish-pipeline";
2
+ /**
3
+ * Toont de voortgang van een publicatie in deelstappen.
4
+ */
5
+ export declare function PublishProgressCard({ job, steps, onRetry, retrying, onFixStep, }: {
6
+ job: PublishJobRow | null;
7
+ steps: PublishStepRow[];
8
+ onRetry?: () => void;
9
+ retrying?: boolean;
10
+ onFixStep?: (stepKey: string) => void;
11
+ }): import("react").JSX.Element | null;
12
+ //# sourceMappingURL=PublishProgressCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublishProgressCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishProgressCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAoBhF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE;IACD,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,sCAyDA"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Button } from "@flowselections/core";
3
+ import { Check, AlertTriangle, Loader2, Circle, MinusCircle, RefreshCw } from "lucide-react";
4
+ function StatusIcon({ status }) {
5
+ if (status === "done")
6
+ return _jsx(Check, { className: "h-4 w-4 text-emerald-600" });
7
+ if (status === "running")
8
+ return _jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" });
9
+ if (status === "failed")
10
+ return _jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" });
11
+ if (status === "skipped")
12
+ return _jsx(MinusCircle, { className: "h-4 w-4 text-muted-foreground/60" });
13
+ return _jsx(Circle, { className: "h-4 w-4 text-muted-foreground/40" });
14
+ }
15
+ const JOB_TITLE = {
16
+ running: "Bezig met publiceren…",
17
+ succeeded: "Gepubliceerd",
18
+ succeeded_with_warnings: "Gepubliceerd, met aandachtspunten",
19
+ failed: "Publiceren niet gelukt",
20
+ };
21
+ /**
22
+ * Toont de voortgang van een publicatie in deelstappen.
23
+ */
24
+ export function PublishProgressCard({ job, steps, onRetry, retrying, onFixStep, }) {
25
+ if (!job)
26
+ return null;
27
+ const visible = steps.filter((s) => s.status !== "skipped");
28
+ const failed = visible.filter((s) => s.status === "failed");
29
+ return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: JOB_TITLE[job.status] ?? "Publicatie" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Referentie ", job.reference_code] })] }), job.status !== "running" && failed.length > 0 && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onRetry, disabled: retrying, children: [retrying ? (_jsx(Loader2, { className: "mr-1 h-3 w-3 animate-spin" })) : (_jsx(RefreshCw, { className: "mr-1 h-3 w-3" })), "Opnieuw proberen"] }))] }), _jsx("ul", { className: "space-y-2", children: visible.map((s) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx("span", { className: "mt-0.5", children: _jsx(StatusIcon, { status: s.status }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: s.status === "done" ? "text-muted-foreground" : "", children: s.label }), s.status === "failed" && s.error_message && (_jsx("div", { className: "text-xs text-destructive", children: s.error_message }))] }), s.status === "failed" && onFixStep && (_jsx(Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onFixStep(s.step_key), children: "Oplossen" }))] }, s.id))) })] }));
30
+ }
@@ -0,0 +1,10 @@
1
+ import type { ReadinessReport } from "../../lib/publish-readiness";
2
+ /**
3
+ * Korte checklist die laat zien of het product klaar is om te publiceren.
4
+ */
5
+ export declare function PublishReadinessCard({ report, loading, onGoToStep, }: {
6
+ report: ReadinessReport;
7
+ loading?: boolean;
8
+ onGoToStep?: (stepKey: string) => void;
9
+ }): import("react").JSX.Element;
10
+ //# sourceMappingURL=PublishReadinessCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublishReadinessCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishReadinessCard.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,OAAO,EACP,UAAU,GACX,EAAE;IACD,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,+BAuEA"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Button } from "@flowselections/core";
3
+ import { Check, AlertTriangle, Loader2, ArrowRight } from "lucide-react";
4
+ import { Link } from "@tanstack/react-router";
5
+ /**
6
+ * Korte checklist die laat zien of het product klaar is om te publiceren.
7
+ */
8
+ export function PublishReadinessCard({ report, loading, onGoToStep, }) {
9
+ return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : report.ok ? (_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-100 text-emerald-700", children: _jsx(Check, { className: "h-3 w-3" }) })) : (_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-amber-100 text-amber-700", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) })), _jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: report.ok ? "Klaar om te publiceren" : "Nog even dit afmaken" }), _jsx("p", { className: "text-xs text-muted-foreground", children: report.ok
10
+ ? "Alles is ingevuld. Je kunt het product nu publiceren."
11
+ : `${report.blockers.length} ding(en) moeten nog geregeld worden.` })] })] }), _jsx("ul", { className: "space-y-1.5", children: report.items.map((item) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [item.ok ? (_jsx(Check, { className: "mt-0.5 h-4 w-4 shrink-0 text-emerald-600" })) : (_jsx(AlertTriangle, { className: `mt-0.5 h-4 w-4 shrink-0 ${item.blocking ? "text-amber-600" : "text-muted-foreground"}` })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("span", { className: item.ok ? "text-muted-foreground" : "", children: item.label }), !item.ok && item.hint && (_jsx("div", { className: "text-xs text-muted-foreground", children: item.hint }))] }), !item.ok && item.stepKey && onGoToStep && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-6 shrink-0 px-2 text-xs", onClick: () => onGoToStep(item.stepKey), children: ["Invullen", _jsx(ArrowRight, { className: "ml-1 h-3 w-3" })] })), !item.ok && item.key === "connection" && (_jsx(Link, { to: "/instellingen", className: "shrink-0 text-xs text-primary underline", children: "Instellingen" }))] }, item.key))) })] }));
12
+ }
@@ -0,0 +1,2 @@
1
+ export declare function StockSyncStatus(): import("react").JSX.Element | null;
2
+ //# sourceMappingURL=StockSyncStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StockSyncStatus.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockSyncStatus.tsx"],"names":[],"mappings":"AAOA,wBAAgB,eAAe,uCAgC9B"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Subtiele melding op de voorraadpagina wanneer de push naar Floriday
4
+ * vastloopt of stilstaat.
5
+ */
6
+ import { AlertTriangle } from "lucide-react";
7
+ import { useStockSyncHealth } from "../../hooks/useStockSyncHealth";
8
+ export function StockSyncStatus() {
9
+ const { data } = useStockSyncHealth();
10
+ if (!data)
11
+ return null;
12
+ const { failed, stalePending, oldestPendingAt } = data;
13
+ if (failed === 0 && stalePending === 0)
14
+ return null;
15
+ const parts = [];
16
+ if (failed > 0) {
17
+ parts.push(`${failed} voorraadwijziging${failed === 1 ? "" : "en"} mislukt`);
18
+ }
19
+ if (stalePending > 0) {
20
+ parts.push(`${stalePending} wacht${stalePending === 1 ? "" : "en"} langer dan 10 minuten`);
21
+ }
22
+ return (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Voorraadsync naar Floriday: " }), parts.join(" · "), ".", oldestPendingAt && (_jsxs("span", { className: "opacity-80", children: [" ", "Oudste wachtende regel: ", new Date(oldestPendingAt).toLocaleString("nl-NL"), "."] }))] })] }));
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,YAAY,gCAkO3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAkGA,wBAAgB,YAAY,gCA+O3B"}
@@ -4,7 +4,10 @@ import { Link } from "@tanstack/react-router";
4
4
  import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
5
5
  import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection";
7
+ import { useRegisterModuleBaseUrl } from "../../hooks/useRegisterModuleBaseUrl";
7
8
  import { useVoorraadData } from "../../hooks/useVoorraadData";
9
+ import { StockSyncStatus } from "./StockSyncStatus";
10
+ import { LogistiekTakenCard } from "./LogistiekTakenCard";
8
11
  import { ExcelExportButton } from "./ExcelExportButton";
9
12
  import { StockBulkButtons } from "./StockBulkButtons";
10
13
  import { VoorraadTable } from "./VoorraadTable";
@@ -33,6 +36,7 @@ function availableOf(i) {
33
36
  return i.availableQuantity ?? i.quantity ?? 0;
34
37
  }
35
38
  export function VoorraadPage() {
39
+ useRegisterModuleBaseUrl();
36
40
  const { activeId, error: connError } = useFloridayConnection();
37
41
  const { items, loading, error, reload } = useVoorraadData(activeId);
38
42
  const [search, setSearch] = useState("");
@@ -92,7 +96,7 @@ export function VoorraadPage() {
92
96
  { id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
93
97
  { id: "below_min", label: "Onder minimum", count: counts.belowMin },
94
98
  ];
95
- 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." })] }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", className: "shrink-0", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] }), 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) => {
99
+ 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(LogistiekTakenCard, {}), 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) => {
96
100
  const active = stockFilter === t.id;
97
101
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
98
102
  ? "text-foreground"
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmB3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,+BAyJA"}
1
+ {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmB3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,+BAmKA"}
@@ -49,6 +49,6 @@ export function VoorraadTable({ items, loading, }) {
49
49
  : "Niet online in Floriday", title: floriday
50
50
  ? "Online in Floriday"
51
51
  : "Niet online in Floriday", className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-0.5 text-[11px] font-medium leading-none text-muted-foreground", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full " +
52
- (floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key))), extraColumns.map((f) => (_jsx("td", { className: "p-3 text-muted-foreground whitespace-nowrap", children: formatCustomValue(customValue(item, f.field_key)) }, f.id)))] }, item.id));
52
+ (floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), Boolean(customValue(item, "floriday_sync")?.dirty) && (_jsx("div", { className: "mt-1 inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium leading-none text-amber-800", title: "Opgeslagen in dit systeem, wacht nog op doorgifte naar Floriday", children: "Wacht op Floriday" })), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key))), extraColumns.map((f) => (_jsx("td", { className: "p-3 text-muted-foreground whitespace-nowrap", children: formatCustomValue(customValue(item, f.field_key)) }, f.id)))] }, item.id));
53
53
  }) })] }) }));
54
54
  }
@@ -0,0 +1,17 @@
1
+ import { type FustType } from "../lib/fusten";
2
+ /**
3
+ * Fustenvoorraad, altijd afgeleid van de beladingen van de producten.
4
+ * Bij het openen én bij elke wijziging aan producten/beladingen wordt
5
+ * automatisch gesynchroniseerd.
6
+ */
7
+ export declare function useFusten(connectionId: string | null): {
8
+ items: FustType[];
9
+ loading: boolean;
10
+ syncing: boolean;
11
+ error: string | null;
12
+ reload: () => Promise<void>;
13
+ sync: () => Promise<void>;
14
+ adjust: (fustTypeId: string, delta: number, note?: string) => Promise<void>;
15
+ patch: (id: string, values: Partial<Pick<FustType, "name" | "minimum_stock" | "notes" | "length_mm" | "width_mm" | "height_mm">>) => Promise<void>;
16
+ };
17
+ //# sourceMappingURL=useFusten.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFusten.d.ts","sourceRoot":"","sources":["../../src/hooks/useFusten.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;;;yBAwE9B,MAAM,SAAS,MAAM,SAAS,MAAM;gBAQ5C,MAAM,UAAU,OAAO,CAChC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC,CAC5F;EAQJ"}
@@ -0,0 +1,81 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { supabase } from "../integrations/supabase/client";
3
+ import { adjustFustStock, fetchFustTypes, syncFustTypes, updateFustType, } from "../lib/fusten";
4
+ /**
5
+ * Fustenvoorraad, altijd afgeleid van de beladingen van de producten.
6
+ * Bij het openen én bij elke wijziging aan producten/beladingen wordt
7
+ * automatisch gesynchroniseerd.
8
+ */
9
+ export function useFusten(connectionId) {
10
+ const [items, setItems] = useState([]);
11
+ const [loading, setLoading] = useState(true);
12
+ const [syncing, setSyncing] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ const syncingRef = useRef(false);
15
+ const load = useCallback(async () => {
16
+ try {
17
+ setItems(await fetchFustTypes(connectionId));
18
+ setError(null);
19
+ }
20
+ catch (e) {
21
+ setError(e.message);
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ }, [connectionId]);
27
+ const sync = useCallback(async () => {
28
+ if (syncingRef.current)
29
+ return;
30
+ syncingRef.current = true;
31
+ setSyncing(true);
32
+ try {
33
+ await syncFustTypes(connectionId);
34
+ setError(null);
35
+ }
36
+ catch (e) {
37
+ setError(e.message);
38
+ }
39
+ finally {
40
+ syncingRef.current = false;
41
+ setSyncing(false);
42
+ await load();
43
+ }
44
+ }, [connectionId, load]);
45
+ useEffect(() => {
46
+ setLoading(true);
47
+ void (async () => {
48
+ await load();
49
+ await sync();
50
+ })();
51
+ }, [load, sync]);
52
+ useEffect(() => {
53
+ const channel = supabase
54
+ .channel(`fusten-${connectionId ?? "none"}`)
55
+ .on("postgres_changes", { event: "*", schema: "public", table: "fust_types" }, () => {
56
+ void load();
57
+ })
58
+ .on("postgres_changes", { event: "*", schema: "public", table: "fust_stock_mutations" }, () => {
59
+ void load();
60
+ })
61
+ .on("postgres_changes", { event: "*", schema: "public", table: "products" }, () => {
62
+ void sync();
63
+ })
64
+ .on("postgres_changes", { event: "*", schema: "public", table: "floriday_trade_items_cache" }, () => {
65
+ void sync();
66
+ })
67
+ .subscribe();
68
+ return () => {
69
+ supabase.removeChannel(channel);
70
+ };
71
+ }, [connectionId, load, sync]);
72
+ const adjust = useCallback(async (fustTypeId, delta, note) => {
73
+ await adjustFustStock({ fustTypeId, delta, note: note ?? null });
74
+ await load();
75
+ }, [load]);
76
+ const patch = useCallback(async (id, values) => {
77
+ await updateFustType(id, values);
78
+ await load();
79
+ }, [load]);
80
+ return { items, loading, syncing, error, reload: load, sync, adjust, patch };
81
+ }
@@ -37,16 +37,26 @@ export interface DraftMeta {
37
37
  lastFloridaySyncAt?: string | null;
38
38
  source?: "manual" | "excel_import";
39
39
  }
40
- /**
41
- * Slaat Floriday-velden op in `products.custom_fields.floriday_payload`. Als `id` ontbreekt maken we een
42
- * nieuw product aan (met minimale ERP-velden uit de payload). Nooit een concept-rij.
43
- */
40
+ /** Status van de doorgifte naar Floriday, opgeslagen in `custom_fields.floriday_sync`. */
41
+ export interface LocalSyncStatus {
42
+ /** true = lokaal opgeslagen maar (nog) niet bevestigd door Floriday. */
43
+ dirty: boolean;
44
+ last_attempt_at?: string | null;
45
+ last_error?: string | null;
46
+ last_synced_at?: string | null;
47
+ }
48
+ export declare function readLocalSyncStatus(customFields: unknown): LocalSyncStatus | null;
44
49
  export declare function upsertLocalTradeItem(params: {
45
50
  id?: string;
46
51
  connectionId: string;
47
52
  data: WizardData;
48
53
  meta?: DraftMeta;
54
+ /** Koppel/maak de lokale rij voor een artikel dat alleen in Floriday bestaat. */
55
+ floridayTradeItemId?: string | null;
56
+ sync?: LocalSyncStatus;
49
57
  }): Promise<string>;
58
+ /** Zet alleen de sync-status bij, zonder de payload te herschrijven. */
59
+ export declare function markLocalSyncStatus(productRowId: string, status: LocalSyncStatus): Promise<void>;
50
60
  /**
51
61
  * Legacy API. Alleen meta-velden die vandaag nog op `products` bestaan (`floriday_trade_item_id`
52
62
  * en `custom_fields.floriday_id`) worden gepersisteerd; overige velden worden stilzwijgend genegeerd