@flowselections/floriday-voorraad 1.0.143 → 1.0.145

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 (27) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.js +1 -1
  2. package/dist-lib/components/voorraad/FustAdministratie.d.ts +12 -0
  3. package/dist-lib/components/voorraad/FustAdministratie.d.ts.map +1 -0
  4. package/dist-lib/components/voorraad/FustAdministratie.js +69 -0
  5. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/FustenPage.js +9 -14
  7. package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts +6 -0
  8. package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts.map +1 -0
  9. package/dist-lib/components/voorraad/KoppelingStatusPanel.js +47 -0
  10. package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -2
  11. package/dist-lib/components/voorraad/TelmomentButton.d.ts +8 -0
  12. package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/TelmomentButton.js +65 -0
  14. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
  15. package/dist-lib/components/voorraad/ValidationPage.js +2 -1
  16. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  17. package/dist-lib/components/voorraad/VoorraadPage.js +2 -1
  18. package/dist-lib/integrations/supabase/types.d.ts +525 -0
  19. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  20. package/dist-lib/lib/fust-administratie.d.ts +29 -0
  21. package/dist-lib/lib/fust-administratie.d.ts.map +1 -0
  22. package/dist-lib/lib/fust-administratie.js +63 -0
  23. package/dist-lib/lib/fusten.d.ts +4 -0
  24. package/dist-lib/lib/fusten.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/sql/AGENTS.md +3 -0
  27. package/sql/floriday-voorraad-schema.sql +910 -58
@@ -1929,7 +1929,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1929
1929
  name: data.name || data.vbnName,
1930
1930
  articleNumber: data.articleCode,
1931
1931
  gtin: data.gtin,
1932
- } }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId ?? productId ?? null }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? productId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? productId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
1932
+ } }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId ?? productId ?? null }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? productId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? productId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
1933
1933
  const isOpen = i === step;
1934
1934
  const isValid = stepValidity[i];
1935
1935
  const isVisited = i < step || isEditing;
@@ -0,0 +1,12 @@
1
+ import type { FustType } from "../../lib/fusten.js";
2
+ /** Startmoment en beginstand: boekingen vóór dit moment tellen niet mee in het saldo, maar blijven zichtbaar. */
3
+ export declare function FustStartmomentCard({ items, onSaved }: {
4
+ items: FustType[];
5
+ onSaved: () => void;
6
+ }): import("react").JSX.Element;
7
+ /** Per fust: saldo bijhouden en eigen naam. */
8
+ export declare function FustInstellingenCell({ fust, onSaved }: {
9
+ fust: FustType;
10
+ onSaved: () => void;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=FustAdministratie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FustAdministratie.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustAdministratie.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BA8EjG;AAED,+CAA+C;AAC/C,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BAiD9F"}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Button, Card, Input, toast } from "@flowselections/core";
4
+ import { fetchFustStartmomenten, fustNaam, saldoBijhouden, saveFustInstellingen, saveFustStartmoment, } from "../../lib/fust-administratie.js";
5
+ function lokaleInvoer(d) {
6
+ const p = (n) => String(n).padStart(2, "0");
7
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
8
+ }
9
+ /** Startmoment en beginstand: boekingen vóór dit moment tellen niet mee in het saldo, maar blijven zichtbaar. */
10
+ export function FustStartmomentCard({ items, onSaved }) {
11
+ const [rows, setRows] = useState(null);
12
+ const [fustId, setFustId] = useState("");
13
+ const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
14
+ const [beginstand, setBeginstand] = useState("0");
15
+ const [busy, setBusy] = useState(false);
16
+ const load = () => fetchFustStartmomenten().then(setRows).catch(() => setRows([]));
17
+ useEffect(() => { void load(); }, []);
18
+ const algemeen = rows?.find((r) => r.fust_type_id === null) ?? null;
19
+ const perFust = (rows ?? []).filter((r) => r.fust_type_id !== null);
20
+ const opslaan = async () => {
21
+ const n = Number(beginstand);
22
+ if (!Number.isFinite(n)) {
23
+ toast.error("Vul een geldige beginstand in.");
24
+ return;
25
+ }
26
+ const d = new Date(moment);
27
+ if (Number.isNaN(d.getTime())) {
28
+ toast.error("Vul een geldig startmoment in.");
29
+ return;
30
+ }
31
+ setBusy(true);
32
+ try {
33
+ await saveFustStartmoment(fustId || null, d, n);
34
+ toast.success("Startmoment opgeslagen; het saldo is opnieuw berekend.");
35
+ await load();
36
+ onSaved();
37
+ }
38
+ catch (e) {
39
+ toast.error(e.message);
40
+ }
41
+ finally {
42
+ setBusy(false);
43
+ }
44
+ };
45
+ return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Startmoment fustenadministratie" }), rows !== null && !algemeen && perFust.length === 0 ? (_jsx("p", { className: "text-sm text-amber-700", children: "Er is nog geen startmoment. Stel een startmoment en beginstand in; tot die tijd telt het saldo alle boekingen uit het verleden mee." })) : (_jsxs("p", { className: "text-sm text-muted-foreground", children: [algemeen
46
+ ? `Alle fusten: vanaf ${new Date(algemeen.moment).toLocaleString("nl-NL")}${algemeen.automatisch ? " (automatisch bij de eerste order)" : ""}.`
47
+ : "Geen algemeen startmoment.", perFust.length > 0 && ` ${perFust.length} fust${perFust.length === 1 ? "" : "en"} met een eigen startmoment.`, " ", "Boekingen v\u00F3\u00F3r het startmoment blijven zichtbaar in de historie maar tellen niet mee."] }))] }), _jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [_jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Voor" }), _jsxs("select", { className: "h-9 rounded-md border border-input bg-background px-2 text-sm", value: fustId, onChange: (e) => setFustId(e.target.value), children: [_jsx("option", { value: "", children: "Alle fusten" }), items.map((f) => (_jsx("option", { value: f.id, children: fustNaam(f) }, f.id)))] })] }), _jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Startmoment" }), _jsx(Input, { type: "datetime-local", className: "h-9 w-52", value: moment, onChange: (e) => setMoment(e.target.value) })] }), _jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsxs("span", { className: "block", children: ["Beginstand", fustId ? "" : " (per fust)"] }), _jsx(Input, { type: "number", className: "h-9 w-28 text-right", value: beginstand, onChange: (e) => setBeginstand(e.target.value) })] }), _jsx(Button, { size: "sm", onClick: () => void opslaan(), disabled: busy, children: busy ? "Opslaan…" : "Startmoment opslaan" })] })] }));
48
+ }
49
+ /** Per fust: saldo bijhouden en eigen naam. */
50
+ export function FustInstellingenCell({ fust, onSaved }) {
51
+ const [open, setOpen] = useState(false);
52
+ const [naam, setNaam] = useState(fust.eigen_naam ?? "");
53
+ const [busy, setBusy] = useState(false);
54
+ const bijhouden = saldoBijhouden(fust);
55
+ const opslaan = async (saldo, eigenNaam) => {
56
+ setBusy(true);
57
+ try {
58
+ await saveFustInstellingen(fust.id, saldo, eigenNaam);
59
+ onSaved();
60
+ }
61
+ catch (e) {
62
+ toast.error(e.message);
63
+ }
64
+ finally {
65
+ setBusy(false);
66
+ }
67
+ };
68
+ return (_jsxs("div", { className: "mt-1 space-y-1 text-xs", children: [_jsxs("label", { className: "flex items-center gap-1 text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: bijhouden, disabled: busy, onChange: (e) => void opslaan(e.target.checked, fust.eigen_naam ?? null) }), "Saldo bijhouden", fust.saldo_bijhouden == null ? " (standaard)" : ""] }), !open ? (_jsx("button", { type: "button", className: "text-primary hover:underline", onClick: () => setOpen(true), children: fust.eigen_naam ? "Eigen naam wijzigen" : "Eigen naam geven" })) : (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-7 w-40", value: naam, placeholder: fust.name, onChange: (e) => setNaam(e.target.value) }), _jsx(Button, { size: "sm", className: "h-7", disabled: busy, onClick: async () => { await opslaan(fust.saldo_bijhouden ?? null, naam); setOpen(false); }, children: "Opslaan" }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-7", onClick: () => setOpen(false), children: "Annuleren" })] }))] }));
69
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAgLA,wBAAgB,UAAU,gCAiQzB"}
1
+ {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAmLA,wBAAgB,UAAU,gCA0PzB"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { useEffect, useMemo, useState } from "react";
3
3
  import { Boxes, Minus, Plus, RefreshCw, Search } from "lucide-react";
4
4
  import { Button, Card, Input, Badge, toast } from "@flowselections/core";
5
- import { supabase } from "../../integrations/supabase/client.js";
5
+ import { FustStartmomentCard, FustInstellingenCell } from "./FustAdministratie.js";
6
+ import { fustNaam, fustTekorten, saldoBijhouden } from "../../lib/fust-administratie.js";
6
7
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
7
8
  import { useFusten } from "../../hooks/useFusten.js";
8
9
  import { fetchFloricodeFustMap, fetchFloridayCustomPackageMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
@@ -61,7 +62,9 @@ function MutationHistory({ fust }) {
61
62
  ? "Order geannuleerd"
62
63
  : m.reason === "manual_sale"
63
64
  ? "Handmatige verkoop"
64
- : "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)))] }))] }));
65
+ : m.reason === "startmoment"
66
+ ? "Startmoment / beginstand"
67
+ : "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)))] }))] }));
65
68
  }
66
69
  export function FustenPage() {
67
70
  const { activeId } = useFloridayConnection();
@@ -80,14 +83,7 @@ export function FustenPage() {
80
83
  const [customPackages, setCustomPackages] = useState(null);
81
84
  const [busyId, setBusyId] = useState(null);
82
85
  const [amounts, setAmounts] = useState({});
83
- const [negative, setNegative] = useState([]);
84
- const loadNegative = useCallback(async () => {
85
- const { data } = await supabase.rpc("fust_negative_stock_report");
86
- setNegative(Array.isArray(data) ? data : []);
87
- }, []);
88
- useEffect(() => {
89
- void loadNegative();
90
- }, [loadNegative, items]);
86
+ const negative = useMemo(() => fustTekorten(items), [items]);
91
87
  const filtered = useMemo(() => {
92
88
  const q = search.trim().toLowerCase();
93
89
  if (!q)
@@ -107,7 +103,7 @@ export function FustenPage() {
107
103
  return;
108
104
  // Waarschuwen (niet blokkeren) bij afboeken boven de voorraad.
109
105
  if (sign < 0 && f.stock - step < 0) {
110
- toast.warning(`Tekort van ${Math.abs(f.stock - step).toLocaleString("nl-NL")} fusten na deze afboeking (${f.name.trim()}).`);
106
+ toast.warning(`Tekort van ${Math.abs(f.stock - step).toLocaleString("nl-NL")} fusten na deze afboeking (${fustNaam(f)}).`);
111
107
  }
112
108
  setBusyId(f.id);
113
109
  setAmounts((a) => ({ ...a, [f.id]: "" }));
@@ -116,10 +112,9 @@ export function FustenPage() {
116
112
  }
117
113
  finally {
118
114
  setBusyId(null);
119
- void loadNegative();
120
115
  }
121
116
  };
122
- 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] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer de ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [n.name.trim(), ": tekort van ", n.shortage.toLocaleString("nl-NL"), " fusten", n.duplicate_names > 0 ? " (dit fusttype bestaat meerdere keren)" : ""] }, n.fust_type_id))) })] })), _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, floricode: floricode, customPackages: customPackages, 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: f.stock < 0 ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
117
+ 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] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is sinds het startmoment meer uitgeleverd dan ingeboekt. Alleen fusten waarvan je het saldo bijhoudt staan hier. Controleer ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [fustNaam(n), ": tekort van ", Math.abs(n.stock).toLocaleString("nl-NL"), " fusten"] }, n.id))) })] })), _jsx(FustStartmomentCard, { items: items, onSaved: () => void sync() }), _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: fustNaam(f) }), f.eigen_naam?.trim() && _jsx("div", { className: "text-xs text-muted-foreground", 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(FustInstellingenCell, { fust: f, onSaved: () => void sync() }), _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, floricode: floricode, customPackages: customPackages, 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: f.stock < 0 && saldoBijhouden(f) ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
123
118
  ? "text-rose-600"
124
119
  : ""}`, 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) => {
125
120
  const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
@@ -0,0 +1,6 @@
1
+ /** Automatisch aanmaken van Floriday-artikelen: wat nog open staat en wat handmatig moet. */
2
+ export declare function KoppelingStatusPanel({ connectionId, onChanged }: {
3
+ connectionId: string | null;
4
+ onChanged?: () => void;
5
+ }): import("react").JSX.Element | null;
6
+ //# sourceMappingURL=KoppelingStatusPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KoppelingStatusPanel.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/KoppelingStatusPanel.tsx"],"names":[],"mappings":"AAYA,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,sCAqExH"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { Button, Card, toast } from "@flowselections/core";
4
+ import { supabase } from "../../integrations/supabase/client.js";
5
+ /** Automatisch aanmaken van Floriday-artikelen: wat nog open staat en wat handmatig moet. */
6
+ export function KoppelingStatusPanel({ connectionId, onChanged }) {
7
+ const [check, setCheck] = useState(null);
8
+ const [twijfel, setTwijfel] = useState([]);
9
+ const [busy, setBusy] = useState(false);
10
+ const load = useCallback(async () => {
11
+ const [c, t] = await Promise.all([
12
+ supabase.rpc("voorraad_koppeling_check"),
13
+ supabase.from("voorraad_koppel_twijfel").select("floriday_id, naam, reden").is("opgelost_at", null).order("laatst_gezien", { ascending: false }).limit(50),
14
+ ]);
15
+ if (c.error)
16
+ toast.error(c.error.message);
17
+ else
18
+ setCheck(c.data);
19
+ setTwijfel((t.data ?? []));
20
+ }, []);
21
+ useEffect(() => { void load(); }, [load]);
22
+ const nuControleren = async () => {
23
+ setBusy(true);
24
+ try {
25
+ const { data, error } = await supabase.rpc("voorraad_floriday_artikelen_aanmaken", {
26
+ p_connection_id: connectionId, p_limiet: 200, p_uitvoeren: true, p_max_seconden: 20,
27
+ });
28
+ if (error)
29
+ throw new Error(error.message);
30
+ toast.success(`${data?.aangemaakt ?? 0} aangemaakt, ${data?.gekoppeld ?? 0} gekoppeld, ${data?.twijfel ?? 0} twijfelgevallen${data?.resterend ? `, ${data.resterend} nog te doen` : ""}.`);
31
+ await load();
32
+ onChanged?.();
33
+ }
34
+ catch (e) {
35
+ toast.error(e.message);
36
+ }
37
+ finally {
38
+ setBusy(false);
39
+ }
40
+ };
41
+ if (!check)
42
+ return null;
43
+ const niets = check.fouten.length === 0 && check.floriday_zonder_eigen_artikel === 0 && twijfel.length === 0;
44
+ return (_jsxs(Card, { className: "p-4 space-y-2 text-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Floriday-artikelen automatisch overnemen" }), _jsxs("p", { className: "text-muted-foreground", children: [niets
45
+ ? "Alle Floriday-artikelen hebben een eigen artikel."
46
+ : `${check.floriday_zonder_eigen_artikel} Floriday-artikelen zonder eigen artikel, ${twijfel.length} twijfelgevallen.`, " ", "Nieuwe artikelen starten met voorraad 0 tot je telt of importeert."] })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void nuControleren(), disabled: busy, children: busy ? "Bezig…" : "Nu controleren" })] }), check.fouten.map((f) => (_jsx("p", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-2 text-destructive", children: f }, f))), twijfel.length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Kies zelf het juiste artikel" }), _jsx("ul", { className: "mt-1 divide-y divide-border rounded-md border border-border", children: twijfel.map((t) => (_jsxs("li", { className: "px-3 py-1.5", children: [_jsx("span", { className: "font-medium", children: t.naam ?? t.floriday_id }), _jsxs("span", { className: "text-muted-foreground", children: [" \u2014 ", t.reden] })] }, t.floriday_id))) })] }))] }));
47
+ }
@@ -64,7 +64,7 @@ export function NieuwArtikelPage() {
64
64
  void (async () => {
65
65
  const { data } = await supabase
66
66
  .from("fust_types")
67
- .select("name, vbn_code")
67
+ .select("name, eigen_naam, vbn_code")
68
68
  .is("merged_into", null)
69
69
  .not("vbn_code", "is", null)
70
70
  .order("name");
@@ -76,7 +76,7 @@ export function NieuwArtikelPage() {
76
76
  const v = Number(r.vbn_code);
77
77
  if (v > 0 && !seen.has(v)) {
78
78
  seen.add(v);
79
- list.push({ vbn: v, name: String(r.name ?? `VBN ${v}`) });
79
+ list.push({ vbn: v, name: String((r.eigen_naam && String(r.eigen_naam).trim()) || r.name || `VBN ${v}`) });
80
80
  }
81
81
  }
82
82
  setFusts(list);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
3
+ * zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
4
+ */
5
+ export declare function TelmomentButton({ onSaved }: {
6
+ onSaved?: () => void;
7
+ }): import("react").JSX.Element;
8
+ //# sourceMappingURL=TelmomentButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelmomentButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TelmomentButton.tsx"],"names":[],"mappings":"AA6BA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BAkGpE"}
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ClipboardCheck } from "lucide-react";
4
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast, } from "@flowselections/core";
5
+ import { supabase } from "../../integrations/supabase/client.js";
6
+ import { TELMOMENT_LABEL } from "../../lib/fust-administratie.js";
7
+ function lokaleInvoer(d) {
8
+ const p = (n) => String(n).padStart(2, "0");
9
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
10
+ }
11
+ /**
12
+ * Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
13
+ * zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
14
+ */
15
+ export function TelmomentButton({ onSaved }) {
16
+ const [open, setOpen] = useState(false);
17
+ const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
18
+ const [regels, setRegels] = useState(null);
19
+ const [alWeg, setAlWeg] = useState(new Set());
20
+ const [busy, setBusy] = useState(false);
21
+ const laden = async (m = moment) => {
22
+ setRegels(null);
23
+ const { data, error } = await supabase.rpc("voorraad_telmoment_lijst", { p_telmoment: new Date(m).toISOString(), p_keys: null });
24
+ if (error) {
25
+ toast.error(error.message);
26
+ setRegels([]);
27
+ return;
28
+ }
29
+ setRegels((data?.regels ?? []));
30
+ setAlWeg(new Set());
31
+ };
32
+ const opslaan = async () => {
33
+ setBusy(true);
34
+ try {
35
+ const { data, error } = await supabase.rpc("voorraad_telmoment_opslaan", {
36
+ p_telmoment: new Date(moment).toISOString(), p_keys: null, p_al_weg: [...alWeg], p_notitie: null,
37
+ });
38
+ if (error)
39
+ throw new Error(error.message);
40
+ toast.success(`Telmoment vastgelegd: ${data?.buiten_telling ?? 0} orderregels tellen niet mee.`);
41
+ setOpen(false);
42
+ onSaved?.();
43
+ }
44
+ catch (e) {
45
+ toast.error(e.message);
46
+ }
47
+ finally {
48
+ setBusy(false);
49
+ }
50
+ };
51
+ const groepen = ["teldag_open", "vertrokken", "voor_teldag"];
52
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => { setOpen(true); void laden(); }, children: [_jsx(ClipboardCheck, { className: "mr-2 h-4 w-4" }), "Telmoment"] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Telmoment vastleggen" }) }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Moment van tellen" }), _jsx(Input, { type: "datetime-local", className: "h-9 w-56", value: moment, onChange: (e) => setMoment(e.target.value) })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void laden(), children: "Ververs lijst" })] }), _jsxs("div", { className: "max-h-[50vh] overflow-y-auto space-y-4 text-sm", children: [regels === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), regels !== null && regels.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Geen openstaande orderregels tot en met de teldag." })), regels !== null && groepen.map((g) => {
53
+ const lijst = regels.filter((r) => r.categorie === g);
54
+ if (lijst.length === 0)
55
+ return null;
56
+ return (_jsxs("div", { children: [_jsxs("p", { className: "font-medium", children: [TELMOMENT_LABEL[g], " (", lijst.length, ")"] }), _jsx("ul", { className: "mt-1 divide-y divide-border rounded-md border border-border", children: lijst.map((r) => (_jsxs("li", { className: "flex items-center gap-3 px-3 py-1.5", children: [g === "teldag_open" && (_jsx("input", { type: "checkbox", "aria-label": "Is al weg", checked: alWeg.has(r.order_item_id), onChange: (e) => setAlWeg((s) => {
57
+ const n = new Set(s);
58
+ if (e.target.checked)
59
+ n.add(r.order_item_id);
60
+ else
61
+ n.delete(r.order_item_id);
62
+ return n;
63
+ }) })), _jsx("span", { className: "w-28 text-muted-foreground", children: r.order_number ?? "—" }), _jsxs("span", { className: "flex-1 truncate", children: [r.artikel ?? "—", " \u00B7 ", r.klant ?? ""] }), _jsx("span", { className: "tabular-nums", children: r.aantal })] }, r.order_item_id))) })] }, g));
64
+ })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => void opslaan(), disabled: busy || regels === null, children: busy ? "Opslaan…" : "Telmoment opslaan" })] })] }) })] }));
65
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,gCAiC7B"}
1
+ {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,gCAkC7B"}
@@ -5,8 +5,9 @@ import { ArrowLeft } from "lucide-react";
5
5
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
6
6
  import { useVoorraadData } from "../../hooks/useVoorraadData.js";
7
7
  import { ValidationOverviewTable } from "./ValidationOverviewTable.js";
8
+ import { KoppelingStatusPanel } from "./KoppelingStatusPanel.js";
8
9
  export function ValidationPage() {
9
10
  const { activeId } = useFloridayConnection();
10
11
  const { items, loading, reload } = useVoorraadData(activeId);
11
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", 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: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
12
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", 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: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(KoppelingStatusPanel, { connectionId: activeId, onChanged: reload }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
12
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCAsW3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCAuW3B"}
@@ -15,6 +15,7 @@ import { StockIntegrityAlert } from "./StockIntegrityAlert.js";
15
15
  import { SupplyReadScopeAlert } from "./SupplyReadScopeAlert.js";
16
16
  import { ExcelExportButton } from "./ExcelExportButton.js";
17
17
  import { StockBulkButtons } from "./StockBulkButtons.js";
18
+ import { TelmomentButton } from "./TelmomentButton.js";
18
19
  import { VoorraadTable } from "./VoorraadTable.js";
19
20
  import { VoorraadPagination } from "./VoorraadPagination.js";
20
21
  import { StockHistoryChart } from "./StockHistoryChart.js";
@@ -156,7 +157,7 @@ export function VoorraadPage() {
156
157
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
157
158
  ? "text-foreground"
158
159
  : "text-muted-foreground hover:text-foreground"}`, children: [t.label, _jsx("span", { className: `ml-2 text-xs ${active ? "text-primary" : "text-muted-foreground/70"}`, children: t.count }), active && (_jsx("span", { className: "absolute left-2 right-2 -bottom-px h-0.5 bg-primary rounded-full" }))] }, t.id));
159
- }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[220px] max-w-md", children: [_jsx(Search, { className: "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op product of artikelnummer...", className: "pl-9", value: search, onChange: (e) => setSearch(e.target.value) })] }), _jsxs(Select, { value: floridayFilter, onValueChange: (v) => setFloridayFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[190px]", children: _jsx(SelectValue, { placeholder: "Floriday" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle (Floriday)" }), _jsx(SelectItem, { value: "linked", children: "Gekoppeld aan Floriday" }), _jsx(SelectItem, { value: "unlinked", children: "Niet gekoppeld" })] })] }), _jsxs(Select, { value: typeFilter, onValueChange: (v) => setTypeFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[170px]", children: _jsx(SelectValue, { placeholder: "Type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle types" }), _jsx(SelectItem, { value: "artikel", children: "Artikel" }), _jsx(SelectItem, { value: "productgroep", children: "Productgroep" }), _jsx(SelectItem, { value: "samenstelling", children: "Samenstelling" })] })] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsx(ExcelExportButton, { items: filtered }), _jsx(StockBulkButtons, { items: items, activeConnectionId: activeId, onImported: reload })] })] }), selected.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b bg-primary/5 px-4 py-2 text-sm", children: [_jsxs("span", { className: "font-medium", children: [selected.size, " geselecteerd"] }), _jsx(Button, { size: "sm", onClick: () => openSettings([...selected]), children: "Voorraadsturing instellen" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setSelected(new Set()), children: "Selectie wissen" })] })), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading, settingsByKey: settingsByKey, selected: selected, onToggleSelect: toggleSelect, onToggleAll: toggleAll, onOpenSettings: (id) => openSettings([id]) }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }), _jsx(StockHistoryChart, { connectionId: activeId ?? null }), _jsx(InventorySettingsDrawer, { open: drawerOpen, onOpenChange: setDrawerOpen, connectionId: activeId ?? null, productIds: drawerIds, current: drawerIds.length === 1
160
+ }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[220px] max-w-md", children: [_jsx(Search, { className: "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op product of artikelnummer...", className: "pl-9", value: search, onChange: (e) => setSearch(e.target.value) })] }), _jsxs(Select, { value: floridayFilter, onValueChange: (v) => setFloridayFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[190px]", children: _jsx(SelectValue, { placeholder: "Floriday" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle (Floriday)" }), _jsx(SelectItem, { value: "linked", children: "Gekoppeld aan Floriday" }), _jsx(SelectItem, { value: "unlinked", children: "Niet gekoppeld" })] })] }), _jsxs(Select, { value: typeFilter, onValueChange: (v) => setTypeFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[170px]", children: _jsx(SelectValue, { placeholder: "Type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle types" }), _jsx(SelectItem, { value: "artikel", children: "Artikel" }), _jsx(SelectItem, { value: "productgroep", children: "Productgroep" }), _jsx(SelectItem, { value: "samenstelling", children: "Samenstelling" })] })] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsx(ExcelExportButton, { items: filtered }), _jsx(TelmomentButton, { onSaved: reload }), _jsx(StockBulkButtons, { items: items, activeConnectionId: activeId, onImported: reload })] })] }), selected.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b bg-primary/5 px-4 py-2 text-sm", children: [_jsxs("span", { className: "font-medium", children: [selected.size, " geselecteerd"] }), _jsx(Button, { size: "sm", onClick: () => openSettings([...selected]), children: "Voorraadsturing instellen" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setSelected(new Set()), children: "Selectie wissen" })] })), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading, settingsByKey: settingsByKey, selected: selected, onToggleSelect: toggleSelect, onToggleAll: toggleAll, onOpenSettings: (id) => openSettings([id]) }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }), _jsx(StockHistoryChart, { connectionId: activeId ?? null }), _jsx(InventorySettingsDrawer, { open: drawerOpen, onOpenChange: setDrawerOpen, connectionId: activeId ?? null, productIds: drawerIds, current: drawerIds.length === 1
160
161
  ? [...settingsByKey.values()].find((s) => s.product_id === drawerIds[0])
161
162
  : undefined, onSaved: () => {
162
163
  reloadSettings();