@flowselections/floriday-voorraad 1.0.101 → 1.0.105

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 (33) hide show
  1. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts +12 -0
  2. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts.map +1 -0
  3. package/dist-lib/components/voorraad/FloridayConnectionNotice.js +16 -0
  4. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
  5. package/dist-lib/components/voorraad/NieuwArtikelPage.js +22 -10
  6. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  7. package/dist-lib/components/voorraad/StockBulkButtons.js +2 -1
  8. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  9. package/dist-lib/components/voorraad/VoorraadPage.js +5 -3
  10. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  11. package/dist-lib/components/voorraad/VoorraadStockCard.js +5 -2
  12. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  13. package/dist-lib/components/voorraad/VoorraadTable.js +5 -1
  14. package/dist-lib/hooks/useFloridayConnection.d.ts +7 -1
  15. package/dist-lib/hooks/useFloridayConnection.d.ts.map +1 -1
  16. package/dist-lib/hooks/useFloridayConnection.js +51 -18
  17. package/dist-lib/hooks/useTradeItemStock.d.ts +1 -1
  18. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  19. package/dist-lib/hooks/useTradeItemStock.js +18 -7
  20. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  21. package/dist-lib/hooks/useVoorraadData.js +12 -21
  22. package/dist-lib/integrations/supabase/types.d.ts +892 -0
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/lib/__tests__/stock-scope.test.d.ts +2 -0
  25. package/dist-lib/lib/__tests__/stock-scope.test.d.ts.map +1 -0
  26. package/dist-lib/lib/__tests__/stock-scope.test.js +44 -0
  27. package/dist-lib/lib/floriday-client.d.ts +2 -0
  28. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  29. package/dist-lib/lib/stock-scope.d.ts +25 -0
  30. package/dist-lib/lib/stock-scope.d.ts.map +1 -0
  31. package/dist-lib/lib/stock-scope.js +37 -0
  32. package/package.json +1 -1
  33. package/sql/floriday-voorraad-schema.sql +5 -4
@@ -0,0 +1,12 @@
1
+ import { type ConnectionStatus } from "../../lib/stock-scope.js";
2
+ /**
3
+ * Meldt in gewone taal waarom Floriday-acties ontbreken. Geen link: de route
4
+ * naar de Floriday-koppeling hoort bij de Shell/Floriday-auth en is hier niet
5
+ * aantoonbaar bekend.
6
+ */
7
+ export declare function FloridayConnectionNotice({ status, environment, error, }: {
8
+ status: ConnectionStatus;
9
+ environment: string | null;
10
+ error: string | null;
11
+ }): import("react").JSX.Element | null;
12
+ //# sourceMappingURL=FloridayConnectionNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloridayConnectionNotice.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayConnectionNotice.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,WAAW,EACX,KAAK,GACN,EAAE;IACD,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,sCAsBA"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangle } from "lucide-react";
3
+ import { noConnectionText } from "../../lib/stock-scope.js";
4
+ /**
5
+ * Meldt in gewone taal waarom Floriday-acties ontbreken. Geen link: de route
6
+ * naar de Floriday-koppeling hoort bij de Shell/Floriday-auth en is hier niet
7
+ * aantoonbaar bekend.
8
+ */
9
+ export function FloridayConnectionNotice({ status, environment, error, }) {
10
+ if (status === "fout") {
11
+ return (_jsxs("div", { role: "alert", className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Floriday-koppeling laden mislukt \u2013 alleen eigen artikelen mogelijk." }), _jsxs("p", { className: "mt-1 break-words", children: ["Melding: ", error] })] })] }));
12
+ }
13
+ if (status !== "geen_koppeling")
14
+ return null;
15
+ return (_jsxs("div", { role: "status", className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 p-3 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: noConnectionText(environment) }), _jsx("p", { className: "mt-1", children: "Een Floriday-koppeling kies je bij de Floriday-instellingen van de Shell." })] })] }));
16
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,gCAkM/B"}
1
+ {"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,gBAAgB,gCAgO/B"}
@@ -12,9 +12,16 @@ import { ArrowLeft, Check, Loader2, Send } from "lucide-react";
12
12
  import { supabase } from "../../integrations/supabase/client.js";
13
13
  import { validateDraftForPublish } from "../../lib/floriday-import-validator.js";
14
14
  import { ArtikelWizard } from "./ArtikelWizard.js";
15
+ import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
16
+ import { STOCK_WRITE_NEEDS_CONNECTION } from "../../lib/stock-scope.js";
17
+ import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
15
18
  const STEPS = ["Naam en code", "Voorraad", "Prijs"];
16
19
  export function NieuwArtikelPage() {
17
20
  const navigate = useNavigate();
21
+ const conn = useFloridayConnection();
22
+ // Zonder actieve koppeling kan set_product_stock (nog) niet opslaan: het
23
+ // voorraadveld staat dan uit en er wordt geen voorraad verstuurd.
24
+ const canSaveStock = conn.status === "actief";
18
25
  const [full, setFull] = useState(false);
19
26
  const [step, setStep] = useState(0);
20
27
  const [name, setName] = useState("");
@@ -29,7 +36,7 @@ export function NieuwArtikelPage() {
29
36
  const insertedIdRef = useRef(null);
30
37
  if (full)
31
38
  return _jsx(ArtikelWizard, {});
32
- const canNext = step === 0 ? name.trim().length > 0 : step === 1 ? Number(stock) >= 0 && stock !== "" : true;
39
+ const canNext = step === 0 ? name.trim().length > 0 : step === 1 ? !canSaveStock || (Number(stock) >= 0 && stock !== "") : true;
33
40
  const save = async () => {
34
41
  if (savingRef.current)
35
42
  return;
@@ -59,16 +66,21 @@ export function NieuwArtikelPage() {
59
66
  id = data.id;
60
67
  insertedIdRef.current = id;
61
68
  }
62
- const { error: stockErr } = await supabase.rpc("set_product_stock", {
63
- p_product_id: id,
64
- p_physical_stock: Math.trunc(Number(stock) || 0),
65
- p_notes: "Nieuw artikel",
66
- p_change_source: "NEW_ARTICLE",
67
- });
69
+ const { error: stockErr } = canSaveStock
70
+ ? await supabase.rpc("set_product_stock", {
71
+ p_product_id: id,
72
+ p_physical_stock: Math.trunc(Number(stock) || 0),
73
+ // Nieuw artikel: minimum en inkomend bewust 0.
74
+ p_minimum_stock: 0,
75
+ p_incoming_stock: 0,
76
+ p_notes: "Nieuw artikel",
77
+ p_change_source: "NEW_ARTICLE",
78
+ })
79
+ : { error: null };
68
80
  if (stockErr) {
69
81
  throw new Error(`Artikel is aangemaakt, maar de voorraad is niet opgeslagen: ${stockErr.message}. Klik opnieuw op "Artikel opslaan" om alleen de voorraad nog eens te proberen.`);
70
82
  }
71
- toast.success("Artikel aangemaakt");
83
+ toast.success(canSaveStock ? "Artikel aangemaakt" : "Artikel aangemaakt (zonder voorraad)");
72
84
  setCreatedId(id);
73
85
  }
74
86
  catch (e) {
@@ -84,7 +96,7 @@ export function NieuwArtikelPage() {
84
96
  if (createdId) {
85
97
  const missing = validateDraftForPublish({}).missing;
86
98
  const hasPrice = Number(price.replace(",", ".")) > 0;
87
- return (_jsx("main", { className: "p-6 space-y-4 max-w-2xl", children: _jsxs(Card, { className: "p-5 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(Check, { className: "h-5 w-5" }), _jsxs("p", { className: "font-medium", children: [name, " is aangemaakt."] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] }), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
99
+ return (_jsx("main", { className: "p-6 space-y-4 max-w-2xl", children: _jsxs(Card, { className: "p-5 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(Check, { className: "h-5 w-5" }), _jsxs("p", { className: "font-medium", children: [name, " is aangemaakt."] })] }), canSaveStock ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] })] })) : (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Er is geen voorraad opgeslagen. ", STOCK_WRITE_NEEDS_CONNECTION] })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [canSaveStock && (_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] })), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
88
100
  }
89
- return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", 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("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: stock, onChange: (e) => setStock(e.target.value), autoFocus: true })] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim(), onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), _jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" })] }));
101
+ return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", 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("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: conn.status === "laden" ? "Koppeling wordt gecontroleerd…" : STOCK_WRITE_NEEDS_CONNECTION }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim(), onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), canSaveStock && (_jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" }))] }));
90
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAgH3D,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,+BA4qBA"}
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;AAgH3D,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,+BAorBA"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { STOCK_WRITE_NEEDS_CONNECTION } from "../../lib/stock-scope.js";
2
3
  import { positiveIntOrNull, saveOwnPackings } from "../../lib/product-packings.js";
3
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
5
  import * as XLSX from "xlsx";
@@ -577,7 +578,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
577
578
  : banner?.kind === "success"
578
579
  ? "border-primary/40 bg-primary/5"
579
580
  : "bg-muted/30";
580
- return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplate, disabled: items.length === 0, children: [_jsx(FileDown, { className: "mr-2 h-4 w-4" }), "Template downloaden"] }), _jsxs(Button, { variant: "outline", onClick: () => setOpen(true), children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), "Voorraad importeren"] }), _jsx(Dialog, { open: open, onOpenChange: (o) => !busy && setOpen(o), children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraad importeren" }), _jsxs(DialogDescription, { children: ["Upload een Excel-bestand (op basis van de template). Bestaande artikelen worden gematcht op", " ", _jsx("strong", { children: "Artikelcode" }), "; liggende, minimum- en inkomende voorraad worden bijgewerkt. Gereserveerde voorraad wordt nooit ge\u00EFmporteerd. Nieuwe artikelcodes worden als concept aangemaakt en niet naar Floriday gepubliceerd."] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx("input", { ref: fileRef, type: "file", accept: ".xlsx,.xls", className: "block w-full text-sm file:mr-3 file:rounded-md file:border-0 file:bg-primary file:px-3 file:py-2 file:text-primary-foreground hover:file:bg-primary/90 disabled:opacity-50", disabled: !!busy, onChange: (e) => {
581
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplate, disabled: items.length === 0, children: [_jsx(FileDown, { className: "mr-2 h-4 w-4" }), "Template downloaden"] }), _jsxs(Button, { variant: "outline", onClick: () => setOpen(true), disabled: !activeConnectionId, title: activeConnectionId ? undefined : STOCK_WRITE_NEEDS_CONNECTION, children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), "Voorraad importeren"] }), !activeConnectionId && (_jsx("span", { className: "text-xs text-muted-foreground", children: STOCK_WRITE_NEEDS_CONNECTION })), _jsx(Dialog, { open: open, onOpenChange: (o) => !busy && setOpen(o), children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraad importeren" }), _jsxs(DialogDescription, { children: ["Upload een Excel-bestand (op basis van de template). Bestaande artikelen worden gematcht op", " ", _jsx("strong", { children: "Artikelcode" }), "; liggende, minimum- en inkomende voorraad worden bijgewerkt. Gereserveerde voorraad wordt nooit ge\u00EFmporteerd. Nieuwe artikelcodes worden als concept aangemaakt en niet naar Floriday gepubliceerd."] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx("input", { ref: fileRef, type: "file", accept: ".xlsx,.xls", className: "block w-full text-sm file:mr-3 file:rounded-md file:border-0 file:bg-primary file:px-3 file:py-2 file:text-primary-foreground hover:file:bg-primary/90 disabled:opacity-50", disabled: !!busy, onChange: (e) => {
581
582
  const f = e.target.files?.[0];
582
583
  if (f)
583
584
  void handleFile(f);
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,YAAY,gCAqX3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA2GA,wBAAgB,YAAY,gCAsX3B"}
@@ -4,6 +4,7 @@ import { Link } from "@tanstack/react-router";
4
4
  import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, useAuthState, } from "@flowselections/core";
5
5
  import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
7
+ import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
7
8
  import { supabase } from "../../integrations/supabase/client.js";
8
9
  import { useVoorraadData } from "../../hooks/useVoorraadData.js";
9
10
  import { StockSyncStatus } from "./StockSyncStatus.js";
@@ -43,7 +44,8 @@ function availableOf(i) {
43
44
  }
44
45
  export function VoorraadPage() {
45
46
  const auth = useAuthState();
46
- const { activeId, error: connError } = useFloridayConnection();
47
+ const conn = useFloridayConnection();
48
+ const { activeId, error: connError } = conn;
47
49
  const { items, loading, error, reload } = useVoorraadData(activeId);
48
50
  const { byKey: settingsByKey, reload: reloadSettings } = useInventorySettings(activeId);
49
51
  const [search, setSearch] = useState("");
@@ -160,13 +162,13 @@ export function VoorraadPage() {
160
162
  { id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
161
163
  { id: "below_min", label: "Onder minimum", count: counts.belowMin },
162
164
  ];
163
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _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(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap 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", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? " staat" : "en staan", " niet in Floriday zonder geldige prijs:", " ", [
165
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap 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", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? " staat" : "en staan", " niet in Floriday zonder geldige prijs:", " ", [
164
166
  noPriceBreakdown.empty > 0 ? `${noPriceBreakdown.empty} zonder verkoopprijs` : null,
165
167
  noPriceBreakdown.zero > 0 ? `${noPriceBreakdown.zero} met prijs € 0,00` : null,
166
168
  noPriceBreakdown.unit > 0 ? `${noPriceBreakdown.unit} met een andere eenheid dan stuks` : null,
167
169
  ]
168
170
  .filter(Boolean)
169
- .join(", "), "."] }), " ", "Kies \"Toon deze artikelen\", open een artikel en vul bij Artikel regulier de verkoopprijs per stuk (excl. btw) in. De voorraadsturing van elk artikel heeft ook een link \"Prijs invullen\"."] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setNoPriceOnly((v) => !v), children: noPriceOnly ? "Alle artikelen tonen" : "Toon deze artikelen" })] })), _jsx(FloridayLeadingSummary, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(FloridayTakeoverPanel, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-koppeling laden mislukt. ", _jsxs("details", { className: "inline", children: [_jsx("summary", { className: "cursor-pointer inline", children: "Details voor beheer" }), 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) => {
171
+ .join(", "), "."] }), " ", "Kies \"Toon deze artikelen\", open een artikel en vul bij Artikel regulier de verkoopprijs per stuk (excl. btw) in. De voorraadsturing van elk artikel heeft ook een link \"Prijs invullen\"."] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setNoPriceOnly((v) => !v), children: noPriceOnly ? "Alle artikelen tonen" : "Toon deze artikelen" })] })), _jsx(FloridayLeadingSummary, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(FloridayTakeoverPanel, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: 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) => {
170
172
  const active = stockFilter === t.id;
171
173
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
172
174
  ? "text-foreground"
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAeA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,sCAoMzF"}
1
+ {"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAgBA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,sCA0MzF"}
@@ -5,6 +5,7 @@ import { Loader2, Package, Check } from "lucide-react";
5
5
  import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock.js";
6
6
  import { supabase } from "../../integrations/supabase/client.js";
7
7
  import { FloridaySentCard } from "./FloridaySentCard.js";
8
+ import { STOCK_WRITE_NEEDS_CONNECTION } from "../../lib/stock-scope.js";
8
9
  export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
9
10
  const keys = stockKeysFor({
10
11
  tradeItemId,
@@ -89,6 +90,8 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
89
90
  }
90
91
  };
91
92
  const handleSave = async () => {
93
+ if (!connectionId)
94
+ return;
92
95
  setSaving(true);
93
96
  try {
94
97
  await save(values);
@@ -101,9 +104,9 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
101
104
  setSaving(false);
102
105
  }
103
106
  };
104
- const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, value: String(values[id] ?? 0), onChange: (e) => {
107
+ const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, disabled: !connectionId, value: String(values[id] ?? 0), onChange: (e) => {
105
108
  setTouched(true);
106
109
  setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
107
110
  } })] }));
108
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), numField("reserved_stock", "Gereserveerd"), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: handleSave, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] }) })] })) })] }), _jsx(FloridaySentCard, { connectionId: connectionId, productId: productId, tradeItemId: tradeItemId, productType: isGroupHead ? "GROUP" : hasBom ? "SET" : null })] }));
111
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), numField("reserved_stock", "Gereserveerd"), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !connectionId, onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsxs("div", { className: "flex justify-end", children: [!connectionId && (_jsx("span", { className: "mr-auto self-center text-xs text-muted-foreground", children: STOCK_WRITE_NEEDS_CONNECTION })), _jsxs(Button, { onClick: handleSave, disabled: saving || !connectionId, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] })] })] })) })] }), _jsx(FloridaySentCard, { connectionId: connectionId, productId: productId, tradeItemId: tradeItemId, productType: isGroupHead ? "GROUP" : hasBom ? "SET" : null })] }));
109
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA8EzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BAwTA"}
1
+ {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA8EzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BA4TA"}
@@ -158,7 +158,11 @@ export function VoorraadTable({ items, loading, settingsByKey, selected, onToggl
158
158
  (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" })), overReserved && (_jsx("div", { className: "text-xs text-amber-700", title: "Er is meer gereserveerd voor orders dan er ligt. Controleer open orders.", children: "gereserveerd groter dan liggend" }))] }), _jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InventoryStatusBadge, { setting: setting }), settingProductId && onOpenSettings && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => onOpenSettings(settingProductId), "aria-label": `Voorraadsturing van ${item.name}`, children: _jsx(Settings2, { className: "h-4 w-4" }) }))] }) }), showType && _jsx("td", { className: "p-3", children: (() => {
159
159
  const t = toStructureType(setting?.product_type, item.itemType);
160
160
  return (_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx(Badge, { variant: t === "NORMAL" ? "secondary" : undefined, className: PRODUCT_TYPE_CLASSES[t], title: PRODUCT_TYPE_HELP[t], children: PRODUCT_TYPE_LABELS[t] }), setting?.action_reason === "GROUP_MEMBER_OFF" && (_jsx("span", { className: "text-[11px] text-muted-foreground", title: "Dit artikel is lid van een actieve productgroep; alleen de groep staat in Floriday.", children: "Lid van groep \u00B7 niet los in Floriday" })), setting?.action_reason === "SET_AWAITS_LOGISTICS" && (item.floridayId ? (_jsx("span", { className: "text-[11px] text-amber-700", title: "Set: dit artikel staat in Floriday. De voorraad (berekend uit de onderdelen) gaat pas naar Floriday na akkoord, als de Logistieke engine onderdelen reserveert.", children: "In Floriday \u00B7 voorraad van de samenstelling volgt" })) : (_jsx("span", { className: "text-[11px] text-amber-700", title: "Berekend uit de onderdelen; nog niet gekoppeld aan een Floriday-artikel.", children: "Berekend \u00B7 nog niet gekoppeld aan Floriday" })))] }));
161
- })() }), showArticle && _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), shownColumns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key))), showPrice && (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground whitespace-nowrap", children: (() => {
161
+ })() }), showArticle && _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), shownColumns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: item.stockMissing && item.itemType !== "productgroep"
162
+ ? c.key === "quantity"
163
+ ? _jsx("span", { className: "text-xs", children: "Geen voorraad ingevoerd" })
164
+ : "—"
165
+ : Number(item[c.key] ?? 0) }, c.key))), showPrice && (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground whitespace-nowrap", children: (() => {
162
166
  const p = item.productId ? prices.get(item.productId) : undefined;
163
167
  if (p == null)
164
168
  return "—";
@@ -1,15 +1,21 @@
1
1
  import type { FloridayActiveConnection } from "../lib/floriday-client.js";
2
+ /** Laat alle schermen in Voorraad de koppeling opnieuw laden (na wisselen). */
3
+ export declare function notifyFloridayConnectionChanged(): void;
2
4
  /**
3
5
  * Reads the single active Floriday connection for the currently selected
4
6
  * environment (staging/live) via the Auth-module's
5
7
  * `get_active_floriday_connection_public` RPC. No tokens or secrets are
6
- * exposed.
8
+ * exposed. Herlaadt bij terugkeren naar het venster en na
9
+ * `notifyFloridayConnectionChanged()`; geen realtime op tabellen van Floriday-auth.
7
10
  */
8
11
  export declare function useFloridayConnection(): {
9
12
  activeConnection: FloridayActiveConnection | null;
10
13
  activeId: string | null;
11
14
  environment: string | null;
15
+ selectedEnvironment: string | null;
16
+ status: import("../lib/stock-scope.js").ConnectionStatus;
12
17
  loading: boolean;
13
18
  error: string | null;
19
+ reload: () => Promise<void>;
14
20
  };
15
21
  //# sourceMappingURL=useFloridayConnection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFloridayConnection.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloridayConnection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,qBAAqB;;;;;;EA+BpC"}
1
+ {"version":3,"file":"useFloridayConnection.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloridayConnection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAKvE,+EAA+E;AAC/E,wBAAgB,+BAA+B,SAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB;;;;;;;;;EAwDpC"}
@@ -1,40 +1,73 @@
1
- import { useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useState } from "react";
2
2
  import { supabase } from "../integrations/supabase/client.js";
3
+ import { connectionStatusOf } from "../lib/stock-scope.js";
4
+ const CHANGED_EVENT = "flowselections:floriday-connection-changed";
5
+ /** Laat alle schermen in Voorraad de koppeling opnieuw laden (na wisselen). */
6
+ export function notifyFloridayConnectionChanged() {
7
+ if (typeof window !== "undefined")
8
+ window.dispatchEvent(new Event(CHANGED_EVENT));
9
+ }
3
10
  /**
4
11
  * Reads the single active Floriday connection for the currently selected
5
12
  * environment (staging/live) via the Auth-module's
6
13
  * `get_active_floriday_connection_public` RPC. No tokens or secrets are
7
- * exposed.
14
+ * exposed. Herlaadt bij terugkeren naar het venster en na
15
+ * `notifyFloridayConnectionChanged()`; geen realtime op tabellen van Floriday-auth.
8
16
  */
9
17
  export function useFloridayConnection() {
10
18
  const [activeConnection, setActive] = useState(null);
19
+ const [selectedEnvironment, setSelectedEnvironment] = useState(null);
11
20
  const [loading, setLoading] = useState(true);
12
21
  const [error, setError] = useState(null);
22
+ const load = useCallback(async () => {
23
+ const [{ data, error }, settings] = await Promise.all([
24
+ supabase.rpc("get_active_floriday_connection_public"),
25
+ // Alleen lezen; lukt dit niet (rechten), dan blijft de omgeving onbekend.
26
+ supabase.from("floriday_settings").select("active_environment").limit(1).maybeSingle(),
27
+ ]);
28
+ if (error) {
29
+ setError(error.message);
30
+ setActive(null);
31
+ }
32
+ else {
33
+ setError(null);
34
+ const row = (data ?? [])[0];
35
+ setActive(row && row.id ? row : null);
36
+ }
37
+ const env = settings?.error ? null : (settings?.data?.active_environment ?? null);
38
+ setSelectedEnvironment(typeof env === "string" ? env : null);
39
+ setLoading(false);
40
+ }, []);
13
41
  useEffect(() => {
14
42
  let mounted = true;
15
- (async () => {
16
- const { data, error } = await supabase.rpc("get_active_floriday_connection_public");
17
- if (!mounted)
18
- return;
19
- if (error) {
20
- setError(error.message);
21
- setActive(null);
22
- }
23
- else {
24
- const row = (data ?? [])[0];
25
- setActive(row && row.id ? row : null);
26
- }
27
- setLoading(false);
28
- })();
43
+ const run = () => {
44
+ if (mounted)
45
+ void load();
46
+ };
47
+ run();
48
+ const onVisible = () => {
49
+ if (document.visibilityState === "visible")
50
+ run();
51
+ };
52
+ window.addEventListener("focus", run);
53
+ window.addEventListener(CHANGED_EVENT, run);
54
+ document.addEventListener("visibilitychange", onVisible);
29
55
  return () => {
30
56
  mounted = false;
57
+ window.removeEventListener("focus", run);
58
+ window.removeEventListener(CHANGED_EVENT, run);
59
+ document.removeEventListener("visibilitychange", onVisible);
31
60
  };
32
- }, []);
61
+ }, [load]);
62
+ const activeId = activeConnection?.id ?? null;
33
63
  return {
34
64
  activeConnection,
35
- activeId: activeConnection?.id ?? null,
65
+ activeId,
36
66
  environment: activeConnection?.active_environment ?? null,
67
+ selectedEnvironment: activeConnection?.active_environment ?? selectedEnvironment,
68
+ status: connectionStatusOf({ loading, error, connectionId: activeId }),
37
69
  loading,
38
70
  error,
71
+ reload: load,
39
72
  };
40
73
  }
@@ -44,5 +44,5 @@ export declare function useTradeItemStock(connectionId: string | null, keyOrKeys
44
44
  error: string | null;
45
45
  resolvedKey: string | null;
46
46
  };
47
- export declare function fetchStockMap(connectionId: string): Promise<Map<string, TradeItemStockRow>>;
47
+ export declare function fetchStockMap(connectionId: string | null): Promise<Map<string, TradeItemStockRow>>;
48
48
  //# sourceMappingURL=useTradeItemStock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;mBA0GR,iBAAiB;;;EAyFnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
1
+ {"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;mBA0GR,iBAAiB;;;EA4FnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAezC"}
@@ -1,3 +1,4 @@
1
+ import { buildScopedStockMap } from "../lib/stock-scope.js";
1
2
  import { useCallback, useEffect, useMemo, useState } from "react";
2
3
  import { supabase } from "../integrations/supabase/client.js";
3
4
  const EMPTY = {
@@ -155,7 +156,10 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
155
156
  p_physical_stock: values.physical_stock,
156
157
  p_minimum_stock: values.minimum_stock,
157
158
  p_incoming_stock: values.incoming_stock,
158
- p_notes: values.notes ?? undefined,
159
+ // Notitie altijd expliciet meegeven (huidige of nieuwe waarde); minimum
160
+ // en inkomend komen uit de geladen rij, dus nooit stil 0.
161
+ p_notes: values.notes ?? null,
162
+ p_change_source: "MANUAL_STOCK",
159
163
  });
160
164
  if (rpcError)
161
165
  throw new Error(rpcError.message);
@@ -233,13 +237,20 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
233
237
  return { row: row ?? EMPTY, loading, save, error, resolvedKey };
234
238
  }
235
239
  export async function fetchStockMap(connectionId) {
236
- const { data } = await supabase
240
+ // Eigen voorraad (connection_id leeg) telt altijd mee; met koppeling ook de
241
+ // rijen van die koppeling. Nooit rijen van andere koppelingen/omgevingen.
242
+ let query = supabase
237
243
  .from("trade_item_stock")
238
- .select("trade_item_key,physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at")
239
- .eq("connection_id", connectionId);
244
+ .select("connection_id,trade_item_key,physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at");
245
+ query = connectionId
246
+ ? query.or(`connection_id.is.null,connection_id.eq.${connectionId}`)
247
+ : query.is("connection_id", null);
248
+ const { data, error } = await query;
249
+ if (error)
250
+ throw new Error(error.message);
251
+ const scoped = buildScopedStockMap((data ?? []), connectionId);
240
252
  const map = new Map();
241
- for (const r of (data ?? [])) {
242
- map.set(r.trade_item_key, r);
243
- }
253
+ for (const [k, r] of scoped)
254
+ map.set(k, r);
244
255
  return map;
245
256
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EAuV1D"}
1
+ {"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EA+U1D"}
@@ -7,7 +7,7 @@ import { fetchStockMap, lookupStock } from "./useTradeItemStock.js";
7
7
  import { useFloridayContinuousStockMap } from "./useFloridayContinuousStock.js";
8
8
  function applyStock(item, s) {
9
9
  if (!s)
10
- return item;
10
+ return { ...item, stockMissing: true };
11
11
  const physical = s.physical_stock;
12
12
  const reserved = s.reserved_stock;
13
13
  const available = Math.max(0, physical - reserved);
@@ -45,21 +45,19 @@ export function useVoorraadData(connectionId) {
45
45
  const [error, setError] = useState(null);
46
46
  const { items: localItems, loading: localLoading, reload: reloadLocal, } = useLocalTradeItems(connectionId);
47
47
  const loadPublished = useCallback(async () => {
48
- if (!connectionId) {
49
- setPublishedItems([]);
50
- setStockMap(new Map());
51
- return;
52
- }
53
48
  setLoading(true);
54
49
  setError(null);
55
50
  try {
56
51
  const [pubRes, stock, groupsRes, bomRes, groupItemsRes] = await Promise.all([
57
- supabase
58
- .from("floriday_trade_items_cache")
59
- .select("data,floriday_id,is_deleted")
60
- .eq("connection_id", connectionId)
61
- .eq("is_deleted", false)
62
- .limit(1000),
52
+ // Zonder koppeling geen Floriday-kopie; eigen artikelen blijven zichtbaar.
53
+ connectionId
54
+ ? supabase
55
+ .from("floriday_trade_items_cache")
56
+ .select("data,floriday_id,is_deleted")
57
+ .eq("connection_id", connectionId)
58
+ .eq("is_deleted", false)
59
+ .limit(1000)
60
+ : Promise.resolve({ data: [], error: null }),
63
61
  fetchStockMap(connectionId),
64
62
  supabase
65
63
  .from("product_groups")
@@ -151,16 +149,9 @@ export function useVoorraadData(connectionId) {
151
149
  loadPublished();
152
150
  }, [loadPublished]);
153
151
  useEffect(() => {
154
- if (!connectionId)
155
- return;
156
152
  const channel = supabase
157
- .channel(`voorraad-stock-${connectionId}`)
158
- .on("postgres_changes", {
159
- event: "*",
160
- schema: "public",
161
- table: "trade_item_stock",
162
- filter: `connection_id=eq.${connectionId}`,
163
- }, () => {
153
+ .channel(`voorraad-stock-${connectionId ?? "eigen"}`)
154
+ .on("postgres_changes", { event: "*", schema: "public", table: "trade_item_stock" }, () => {
164
155
  fetchStockMap(connectionId).then(setStockMap).catch(() => { });
165
156
  })
166
157
  .on("postgres_changes", { event: "*", schema: "public", table: "products" }, () => {