@flowselections/floriday-voorraad 1.0.27 → 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 (127) 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 +576 -324
  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 +5 -4
  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/ProductCustomFieldsCard.d.ts +5 -0
  14. package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -0
  15. package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +65 -0
  16. package/dist-lib/components/voorraad/PublicatiesPage.d.ts +2 -0
  17. package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -0
  18. package/dist-lib/components/voorraad/PublicatiesPage.js +60 -0
  19. package/dist-lib/components/voorraad/PublishProgressCard.d.ts +12 -0
  20. package/dist-lib/components/voorraad/PublishProgressCard.d.ts.map +1 -0
  21. package/dist-lib/components/voorraad/PublishProgressCard.js +30 -0
  22. package/dist-lib/components/voorraad/PublishReadinessCard.d.ts +10 -0
  23. package/dist-lib/components/voorraad/PublishReadinessCard.d.ts.map +1 -0
  24. package/dist-lib/components/voorraad/PublishReadinessCard.js +12 -0
  25. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  26. package/dist-lib/components/voorraad/StockBulkButtons.js +65 -2
  27. package/dist-lib/components/voorraad/StockSyncStatus.d.ts +2 -0
  28. package/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -0
  29. package/dist-lib/components/voorraad/StockSyncStatus.js +23 -0
  30. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  31. package/dist-lib/components/voorraad/ValidationOverviewTable.js +8 -2
  32. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -1
  33. package/dist-lib/components/voorraad/VoorraadDashboard.js +4 -1
  34. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  35. package/dist-lib/components/voorraad/VoorraadPage.js +5 -1
  36. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  37. package/dist-lib/components/voorraad/VoorraadTable.js +10 -2
  38. package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts +2 -0
  39. package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts.map +1 -0
  40. package/dist-lib/components/voorraad/VoorraadVeldenCard.js +97 -0
  41. package/dist-lib/hooks/useFusten.d.ts +17 -0
  42. package/dist-lib/hooks/useFusten.d.ts.map +1 -0
  43. package/dist-lib/hooks/useFusten.js +81 -0
  44. package/dist-lib/hooks/useLocalTradeItems.d.ts +17 -7
  45. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  46. package/dist-lib/hooks/useLocalTradeItems.js +139 -37
  47. package/dist-lib/hooks/useLogisticsTasks.d.ts +13 -0
  48. package/dist-lib/hooks/useLogisticsTasks.d.ts.map +1 -0
  49. package/dist-lib/hooks/useLogisticsTasks.js +155 -0
  50. package/dist-lib/hooks/usePublishJob.d.ts +13 -0
  51. package/dist-lib/hooks/usePublishJob.d.ts.map +1 -0
  52. package/dist-lib/hooks/usePublishJob.js +67 -0
  53. package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts +2 -0
  54. package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts.map +1 -0
  55. package/dist-lib/hooks/useRegisterModuleBaseUrl.js +31 -0
  56. package/dist-lib/hooks/useStockSyncHealth.d.ts +8 -0
  57. package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -0
  58. package/dist-lib/hooks/useStockSyncHealth.js +42 -0
  59. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  60. package/dist-lib/hooks/useTradeItemStock.js +37 -1
  61. package/dist-lib/hooks/useVoorraadCustomFields.d.ts +9 -0
  62. package/dist-lib/hooks/useVoorraadCustomFields.d.ts.map +1 -0
  63. package/dist-lib/hooks/useVoorraadCustomFields.js +26 -0
  64. package/dist-lib/index.d.ts +2 -0
  65. package/dist-lib/index.d.ts.map +1 -1
  66. package/dist-lib/index.js +9 -2
  67. package/dist-lib/integrations/supabase/auth-middleware.d.ts +8467 -4487
  68. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  69. package/dist-lib/integrations/supabase/client.d.ts +8467 -4487
  70. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  71. package/dist-lib/integrations/supabase/client.server.d.ts +8467 -4487
  72. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  73. package/dist-lib/integrations/supabase/types.d.ts +8714 -4631
  74. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  75. package/dist-lib/integrations/supabase/types.js +49 -0
  76. package/dist-lib/lib/floricode-required-features.functions.d.ts +8467 -4487
  77. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday-client.d.ts +1 -1
  79. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  80. package/dist-lib/lib/floriday-client.js +1 -2
  81. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +8467 -4487
  82. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  83. package/dist-lib/lib/floriday-customer-offer.functions.js +14 -19
  84. package/dist-lib/lib/floriday-env.functions.d.ts +35712 -0
  85. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -0
  86. package/dist-lib/lib/floriday-env.functions.js +16 -0
  87. package/dist-lib/lib/floriday-env.server.d.ts +12 -0
  88. package/dist-lib/lib/floriday-env.server.d.ts.map +1 -1
  89. package/dist-lib/lib/floriday-env.server.js +15 -2
  90. package/dist-lib/lib/floriday-gateway.functions.d.ts +77902 -40145
  91. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  92. package/dist-lib/lib/floriday-gateway.functions.js +12 -3
  93. package/dist-lib/lib/floriday-readiness.server.d.ts +11 -0
  94. package/dist-lib/lib/floriday-readiness.server.d.ts.map +1 -0
  95. package/dist-lib/lib/floriday-readiness.server.js +43 -0
  96. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +23115 -15155
  97. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  98. package/dist-lib/lib/floriday-warehouse.functions.d.ts +31362 -19422
  99. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  100. package/dist-lib/lib/floriday-warehouse.functions.js +3 -0
  101. package/dist-lib/lib/floriday-writes.functions.d.ts +23191 -15231
  102. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  103. package/dist-lib/lib/fusten.d.ts +87 -0
  104. package/dist-lib/lib/fusten.d.ts.map +1 -0
  105. package/dist-lib/lib/fusten.js +389 -0
  106. package/dist-lib/lib/logistics-engine.d.ts +63 -0
  107. package/dist-lib/lib/logistics-engine.d.ts.map +1 -0
  108. package/dist-lib/lib/logistics-engine.js +57 -0
  109. package/dist-lib/lib/logistics-events.d.ts +18 -0
  110. package/dist-lib/lib/logistics-events.d.ts.map +1 -0
  111. package/dist-lib/lib/logistics-events.js +61 -0
  112. package/dist-lib/lib/module-runtime-config.functions.d.ts +14 -0
  113. package/dist-lib/lib/module-runtime-config.functions.d.ts.map +1 -0
  114. package/dist-lib/lib/module-runtime-config.functions.js +107 -0
  115. package/dist-lib/lib/publish-errors.d.ts +37 -0
  116. package/dist-lib/lib/publish-errors.d.ts.map +1 -0
  117. package/dist-lib/lib/publish-errors.js +183 -0
  118. package/dist-lib/lib/publish-pipeline.d.ts +91 -0
  119. package/dist-lib/lib/publish-pipeline.d.ts.map +1 -0
  120. package/dist-lib/lib/publish-pipeline.js +212 -0
  121. package/dist-lib/lib/publish-readiness.d.ts +42 -0
  122. package/dist-lib/lib/publish-readiness.d.ts.map +1 -0
  123. package/dist-lib/lib/publish-readiness.js +102 -0
  124. package/dist-lib/lib/voorraad-custom-fields.d.ts +22 -0
  125. package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -0
  126. package/dist-lib/lib/voorraad-custom-fields.js +71 -0
  127. 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":"AAWA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BA4ErD"}
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"}
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Card } from "@flowselections/core";
4
4
  import { Loader2 } from "lucide-react";
5
5
  import { ArtikelWizard } from "./ArtikelWizard";
6
+ import { ProductCustomFieldsCard } from "./ProductCustomFieldsCard";
6
7
  import { supabase } from "../../integrations/supabase/client";
7
- export function EditArtikelPage({ id }) {
8
+ export function EditArtikelPage({ id, resumeJobId, }) {
8
9
  const [resolved, setResolved] = useState(null);
9
10
  useEffect(() => {
10
11
  let cancelled = false;
@@ -62,7 +63,7 @@ export function EditArtikelPage({ id }) {
62
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 }))] }) }));
63
64
  }
64
65
  if (resolved.kind === "product") {
65
- return _jsx(ArtikelWizard, { 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 }) })] }));
66
67
  }
67
- return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
68
+ return _jsx(ArtikelWizard, { tradeItemId: resolved.id, resumeJobId: resumeJobId });
68
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,5 @@
1
+ /** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
2
+ export declare function ProductCustomFieldsCard({ productId }: {
3
+ productId: string;
4
+ }): import("react").JSX.Element | null;
5
+ //# sourceMappingURL=ProductCustomFieldsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductCustomFieldsCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductCustomFieldsCard.tsx"],"names":[],"mappings":"AAyBA,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCA6H3E"}
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
4
+ import { Loader2, Save } from "lucide-react";
5
+ import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields";
6
+ import { fetchProductCustomValues, saveProductCustomValues, } from "../../lib/voorraad-custom-fields";
7
+ /** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
8
+ export function ProductCustomFieldsCard({ productId }) {
9
+ const { fields, loading } = useVoorraadCustomFields();
10
+ const [values, setValues] = useState({});
11
+ const [loadingValues, setLoadingValues] = useState(true);
12
+ const [saving, setSaving] = useState(false);
13
+ useEffect(() => {
14
+ let cancelled = false;
15
+ setLoadingValues(true);
16
+ fetchProductCustomValues(productId)
17
+ .then((v) => {
18
+ if (!cancelled)
19
+ setValues(v);
20
+ })
21
+ .catch(() => {
22
+ if (!cancelled)
23
+ setValues({});
24
+ })
25
+ .finally(() => {
26
+ if (!cancelled)
27
+ setLoadingValues(false);
28
+ });
29
+ return () => {
30
+ cancelled = true;
31
+ };
32
+ }, [productId]);
33
+ const detailFields = fields.filter((f) => f.show_in_detail);
34
+ if (loading || loadingValues) {
35
+ return null;
36
+ }
37
+ if (detailFields.length === 0)
38
+ return null;
39
+ const set = (key, value) => setValues((prev) => ({ ...prev, [key]: value }));
40
+ const save = async () => {
41
+ setSaving(true);
42
+ try {
43
+ await saveProductCustomValues(productId, Object.fromEntries(detailFields.map((f) => [f.field_key, values[f.field_key] ?? null])));
44
+ toast.success("Eigen velden opgeslagen");
45
+ }
46
+ catch (e) {
47
+ toast.error(`Opslaan mislukt: ${e.message}`);
48
+ }
49
+ finally {
50
+ setSaving(false);
51
+ }
52
+ };
53
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Eigen velden" }), _jsx(CardDescription, { children: "Extra voorraadvelden zoals ingesteld bij Instellingen \u2192 Voorraadvelden." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: detailFields.map((f) => {
54
+ const v = values[f.field_key];
55
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: `cf-${f.field_key}`, children: f.field_label }), f.field_type === "boolean" ? (_jsx("div", { className: "pt-1", children: _jsx(Checkbox, { id: `cf-${f.field_key}`, checked: v === true, onCheckedChange: (c) => set(f.field_key, c === true) }) })) : f.field_type === "select" ? (_jsxs(Select, { value: typeof v === "string" ? v : "", onValueChange: (val) => set(f.field_key, val), children: [_jsx(SelectTrigger, { id: `cf-${f.field_key}`, children: _jsx(SelectValue, { placeholder: "Kies\u2026" }) }), _jsx(SelectContent, { children: f.options.map((o) => (_jsx(SelectItem, { value: o, children: o }, o))) })] })) : (_jsx(Input, { id: `cf-${f.field_key}`, type: f.field_type === "number"
56
+ ? "number"
57
+ : f.field_type === "date"
58
+ ? "date"
59
+ : "text", value: v === null || v === undefined ? "" : String(v), onChange: (e) => set(f.field_key, f.field_type === "number"
60
+ ? e.target.value === ""
61
+ ? null
62
+ : Number(e.target.value)
63
+ : e.target.value) }))] }, f.id));
64
+ }) }), _jsxs(Button, { onClick: save, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Save, { className: "mr-2 h-4 w-4" })), "Eigen velden opslaan"] })] })] }));
65
+ }
@@ -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
+ }
@@ -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;AAoF3D,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,+BAikBA"}
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"}
@@ -9,6 +9,8 @@ import { upsertLocalTradeItem, updateDraftMeta } from "../../hooks/useLocalTrade
9
9
  import { validateDraftForPublish } from "../../lib/floriday-import-validator";
10
10
  import { floriday } from "../../lib/floriday-client";
11
11
  import { ensureArticleCodes } from "../../lib/ensure-article-codes";
12
+ import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields";
13
+ import { saveProductCustomValues } from "../../lib/voorraad-custom-fields";
12
14
  const COLUMNS = [
13
15
  "Artikelcode",
14
16
  "Artikelnaam",
@@ -61,6 +63,34 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
61
63
  const [autoPublish, setAutoPublish] = useState(false);
62
64
  const [result, setResult] = useState(null);
63
65
  const [pastRuns, setPastRuns] = useState([]);
66
+ const { fields: customFields } = useVoorraadCustomFields();
67
+ const templateFields = customFields.filter((f) => f.show_in_template);
68
+ /**
69
+ * Leest de waarde van een eigen veld uit een Excel-rij. Kolomkoppen worden
70
+ * tolerant gematcht: op label óf key, hoofdletter- en spatie-ongevoelig.
71
+ */
72
+ const readCustomValues = useCallback((row) => {
73
+ const norm = (s) => s.toLowerCase().replace(/[\s_-]+/g, "");
74
+ const lookup = new Map();
75
+ for (const [k, v] of Object.entries(row))
76
+ lookup.set(norm(k), v);
77
+ const patch = {};
78
+ for (const f of customFields) {
79
+ const raw = lookup.get(norm(f.field_label)) ?? lookup.get(norm(f.field_key));
80
+ if (raw === undefined)
81
+ continue;
82
+ const str = String(raw ?? "").trim();
83
+ patch[f.field_key] =
84
+ str === ""
85
+ ? null
86
+ : f.field_type === "number"
87
+ ? Number(str.replace(",", "."))
88
+ : f.field_type === "boolean"
89
+ ? ["ja", "true", "1", "yes"].includes(str.toLowerCase())
90
+ : str;
91
+ }
92
+ return patch;
93
+ }, [customFields]);
64
94
  const loadPastRuns = useCallback(async () => {
65
95
  const { data, error } = await supabase
66
96
  .from("stock_import_runs")
@@ -115,6 +145,12 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
115
145
  const generatedCount = exportItems.filter((i, idx) => i.articleNumber !== items[idx].articleNumber).length;
116
146
  const rows = exportItems.map((i) => {
117
147
  const d = i.raw?.data;
148
+ const cf = (i.raw?.custom_fields ?? {});
149
+ const extra = {};
150
+ for (const f of templateFields) {
151
+ const v = cf[f.field_key];
152
+ extra[f.field_label] = v === null || v === undefined ? "" : v;
153
+ }
118
154
  return {
119
155
  Artikelcode: i.articleNumber,
120
156
  Artikelnaam: i.name,
@@ -126,10 +162,12 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
126
162
  "Minimum voorraad": i.minQuantity,
127
163
  "Gereserveerde voorraad": i.reservedQuantity,
128
164
  "Inkomende voorraad": i.incomingQuantity,
165
+ ...extra,
129
166
  };
130
167
  });
131
- const ws = XLSX.utils.json_to_sheet(rows, { header: [...COLUMNS] });
132
- ws["!cols"] = COLUMNS.map((c) => ({
168
+ const headers = [...COLUMNS, ...templateFields.map((f) => f.field_label)];
169
+ const ws = XLSX.utils.json_to_sheet(rows, { header: headers });
170
+ ws["!cols"] = headers.map((c) => ({
133
171
  wch: c === "Artikelnaam" ? 40 : c === "Artikelcode" ? 18 : 20,
134
172
  }));
135
173
  const wb = XLSX.utils.book_new();
@@ -257,6 +295,19 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
257
295
  oldReserved: existing.reservedQuantity,
258
296
  newReserved: applied.reserved_stock,
259
297
  });
298
+ // Eigen voorraadvelden uit de template terugschrijven
299
+ const targetProductId = existing.productId ?? existing.localId ?? null;
300
+ if (targetProductId && customFields.length > 0) {
301
+ const patch = readCustomValues(row);
302
+ if (Object.keys(patch).length > 0) {
303
+ try {
304
+ await saveProductCustomValues(targetProductId, patch);
305
+ }
306
+ catch {
307
+ /* eigen velden zijn niet blokkerend voor de voorraadimport */
308
+ }
309
+ }
310
+ }
260
311
  updated++;
261
312
  setProgress(Math.round(((idx + 1) / rows.length) * 100));
262
313
  continue;
@@ -334,6 +385,18 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
334
385
  },
335
386
  });
336
387
  created++;
388
+ // Eigen voorraadvelden ook op nieuw aangemaakte producten zetten
389
+ if (customFields.length > 0) {
390
+ const patch = readCustomValues(row);
391
+ if (Object.keys(patch).length > 0) {
392
+ try {
393
+ await saveProductCustomValues(draftId, patch);
394
+ }
395
+ catch {
396
+ /* niet blokkerend */
397
+ }
398
+ }
399
+ }
337
400
  // Stock koppelen aan het nieuwe product (draftId is nu de product-uuid)
338
401
  const anyStock = stockValues.physical_stock !== null ||
339
402
  stockValues.minimum_stock !== null ||
@@ -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":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAM3D;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BA4GA"}
1
+ {"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAM3D;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BAmHA"}