@flowselections/floriday-voorraad 1.0.22 → 1.0.26

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 (65) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +4 -4
  2. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  3. package/dist-lib/components/voorraad/ArtikelWizard.js +655 -93
  4. package/dist-lib/components/voorraad/FloridayInfoCard.d.ts +12 -0
  5. package/dist-lib/components/voorraad/FloridayInfoCard.d.ts.map +1 -0
  6. package/dist-lib/components/voorraad/FloridayInfoCard.js +85 -0
  7. package/dist-lib/components/voorraad/StepSamenstelling.d.ts +10 -0
  8. package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -0
  9. package/dist-lib/components/voorraad/StepSamenstelling.js +333 -0
  10. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  11. package/dist-lib/components/voorraad/VoorraadPage.js +14 -3
  12. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  13. package/dist-lib/components/voorraad/VoorraadTable.js +8 -1
  14. package/dist-lib/hooks/useFloridayPriceGroups.d.ts +20 -0
  15. package/dist-lib/hooks/useFloridayPriceGroups.d.ts.map +1 -0
  16. package/dist-lib/hooks/useFloridayPriceGroups.js +55 -0
  17. package/dist-lib/hooks/useFloridayVbnRequired.d.ts +5 -0
  18. package/dist-lib/hooks/useFloridayVbnRequired.d.ts.map +1 -0
  19. package/dist-lib/hooks/useFloridayVbnRequired.js +16 -0
  20. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  21. package/dist-lib/hooks/useLocalTradeItems.js +14 -0
  22. package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -1
  23. package/dist-lib/hooks/useTradeItemSpec.js +48 -1
  24. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  25. package/dist-lib/hooks/useVoorraadData.js +36 -5
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2388 -345
  27. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.d.ts +2388 -345
  29. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/client.server.d.ts +2388 -345
  31. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.d.ts +2437 -343
  33. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  34. package/dist-lib/integrations/supabase/types.js +11 -9
  35. package/dist-lib/lib/centralSecrets.server.d.ts +21 -0
  36. package/dist-lib/lib/centralSecrets.server.d.ts.map +1 -0
  37. package/dist-lib/lib/centralSecrets.server.js +111 -0
  38. package/dist-lib/lib/floricode-required-features.functions.d.ts +2388 -345
  39. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-client.d.ts +4 -0
  41. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +2388 -345
  43. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-gateway.functions.d.ts +10860 -645
  45. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  47. package/dist-lib/lib/floriday-gateway.server.js +34 -9
  48. package/dist-lib/lib/floriday-info.d.ts +54 -0
  49. package/dist-lib/lib/floriday-info.d.ts.map +1 -0
  50. package/dist-lib/lib/floriday-info.js +114 -0
  51. package/dist-lib/lib/floriday-payload.d.ts +21 -3
  52. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  53. package/dist-lib/lib/floriday-payload.js +167 -34
  54. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +27769 -0
  55. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -0
  56. package/dist-lib/lib/floriday-vbn-required.functions.js +85 -0
  57. package/dist-lib/lib/floriday-warehouse.functions.d.ts +6602 -473
  58. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/floriday-writes.functions.d.ts +4681 -594
  60. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floriday-writes.functions.js +36 -1
  62. package/dist-lib/lib/trade-item-prices.d.ts +43 -0
  63. package/dist-lib/lib/trade-item-prices.d.ts.map +1 -0
  64. package/dist-lib/lib/trade-item-prices.js +229 -0
  65. package/package.json +3 -3
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ floridayTradeItemId: string | null;
3
+ /** Lokale productwaarden om ‘lokaal wijkt af van Floriday’-hints te tonen. */
4
+ local?: {
5
+ name?: string | null;
6
+ articleNumber?: string | null;
7
+ gtin?: string | null;
8
+ };
9
+ }
10
+ export declare function FloridayInfoCard({ floridayTradeItemId, local }: Props): import("react/jsx-runtime").JSX.Element | null;
11
+ export {};
12
+ //# sourceMappingURL=FloridayInfoCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloridayInfoCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayInfoCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH;AA6DD,wBAAgB,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,EAAE,KAAK,kDAqRrE"}
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Card, CardContent, Badge } from "@flowselections/core";
4
+ import { AlertTriangle, ChevronDown, ChevronRight, ExternalLink } from "lucide-react";
5
+ import { supabase } from "../../integrations/supabase/client";
6
+ import { floridayInfoFromCache, LOAD_CARRIER_LABELS, } from "../../lib/floriday-info";
7
+ const floridayLogo = "/flowselections-assets/Voorraad/floriday-logo.png";
8
+ function formatDate(iso) {
9
+ if (!iso)
10
+ return "—";
11
+ try {
12
+ return new Date(iso).toLocaleString("nl-NL", {
13
+ dateStyle: "medium",
14
+ timeStyle: "short",
15
+ });
16
+ }
17
+ catch {
18
+ return iso;
19
+ }
20
+ }
21
+ function DiffField({ label, floridayValue, localValue, }) {
22
+ const fv = floridayValue == null || floridayValue === "" ? "—" : String(floridayValue);
23
+ const lv = (localValue ?? "").trim();
24
+ const differs = lv.length > 0 && fv !== "—" && lv !== fv;
25
+ return (_jsxs("div", { className: "grid grid-cols-[140px_1fr] gap-2 text-sm py-1", children: [_jsx("div", { className: "text-muted-foreground", children: label }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: fv }), differs && (_jsxs("div", { className: "mt-0.5 inline-flex items-center gap-1 text-xs text-amber-700", title: "De lokale waarde wijkt af van Floriday. Puur informatief \u2014 er wordt niets automatisch overschreven.", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), "lokaal: ", _jsx("span", { className: "font-mono", children: lv })] }))] })] }));
26
+ }
27
+ function Field({ label, value, }) {
28
+ const v = value == null || value === "" ? "—" : String(value);
29
+ return (_jsxs("div", { className: "grid grid-cols-[140px_1fr] gap-2 text-sm py-1", children: [_jsx("div", { className: "text-muted-foreground", children: label }), _jsx("div", { className: "font-medium break-words", children: v })] }));
30
+ }
31
+ export function FloridayInfoCard({ floridayTradeItemId, local }) {
32
+ const [info, setInfo] = useState(null);
33
+ const [loading, setLoading] = useState(false);
34
+ const [notFound, setNotFound] = useState(false);
35
+ const [rawOpen, setRawOpen] = useState(false);
36
+ const [activePhoto, setActivePhoto] = useState(0);
37
+ useEffect(() => {
38
+ if (!floridayTradeItemId) {
39
+ setInfo(null);
40
+ setNotFound(false);
41
+ return;
42
+ }
43
+ let cancelled = false;
44
+ setLoading(true);
45
+ setNotFound(false);
46
+ (async () => {
47
+ try {
48
+ const { data, error } = await supabase
49
+ .from("floriday_trade_items_cache")
50
+ .select("data,floriday_id")
51
+ .eq("floriday_id", floridayTradeItemId)
52
+ .maybeSingle();
53
+ if (cancelled)
54
+ return;
55
+ if (error || !data) {
56
+ setInfo(null);
57
+ setNotFound(true);
58
+ return;
59
+ }
60
+ const mapped = floridayInfoFromCache({
61
+ ...data.data,
62
+ id: data.floriday_id,
63
+ });
64
+ setInfo(mapped);
65
+ setActivePhoto(0);
66
+ }
67
+ finally {
68
+ if (!cancelled)
69
+ setLoading(false);
70
+ }
71
+ })();
72
+ return () => {
73
+ cancelled = true;
74
+ };
75
+ }, [floridayTradeItemId]);
76
+ if (!floridayTradeItemId)
77
+ return null;
78
+ return (_jsx(Card, { className: "overflow-hidden", children: _jsxs(CardContent, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-semibold", children: "Floriday-informatie" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Rechtstreeks uit Floriday geladen \u2014 controle-informatie, wordt niet lokaal overschreven." })] }), info?.tradeItemVersion != null && (_jsxs(Badge, { variant: "secondary", className: "text-xs", children: ["v", info.tradeItemVersion] }))] }), loading && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Floriday-info laden\u2026" })), !loading && notFound && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-sm text-amber-900", children: ["Geen Floriday-record gevonden voor id", " ", _jsx("code", { className: "font-mono", children: floridayTradeItemId }), ". Wacht op de volgende sync of controleer de connectie."] })), info && (_jsxs(_Fragment, { children: [info.photos.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "rounded-md border overflow-hidden bg-muted/30", children: _jsx("img", { src: info.photos[activePhoto]?.url ?? info.photos[0].url, alt: info.name, className: "w-full max-h-80 object-contain" }) }), info.photos.length > 1 && (_jsx("div", { className: "flex gap-2 flex-wrap", children: info.photos.map((p, i) => (_jsx("button", { type: "button", onClick: () => setActivePhoto(i), className: `h-14 w-14 rounded border overflow-hidden ${i === activePhoto
79
+ ? "ring-2 ring-primary"
80
+ : "opacity-70 hover:opacity-100"}`, "aria-label": `Foto ${i + 1}`, children: _jsx("img", { src: p.url, alt: "", className: "h-full w-full object-cover" }) }, p.url))) }))] })), _jsxs("div", { className: "grid gap-x-8 gap-y-1 md:grid-cols-2", children: [_jsxs("div", { children: [_jsx(DiffField, { label: "Naam", floridayValue: info.name, localValue: local?.name ?? null }), _jsx(DiffField, { label: "Artikelnr", floridayValue: info.supplierArticleCode, localValue: local?.articleNumber ?? null }), _jsx(DiffField, { label: "GTIN", floridayValue: info.articleGtin, localValue: local?.gtin ?? null }), _jsx(Field, { label: "VBN-code", value: info.vbnProductCode }), _jsx(Field, { label: "Referentie", value: info.supplierReference })] }), _jsxs("div", { children: [_jsx(Field, { label: "Botanische naam", value: info.botanicalNames.join(", ") || null }), _jsx(Field, { label: "Land van herkomst", value: info.countryOfOrigin.join(", ") || null }), _jsx(Field, { label: "Seizoensperiode", value: info.seasonalPeriods.length > 0
81
+ ? info.seasonalPeriods
82
+ .map((s) => `wk ${s.startWeek}–${s.endWeek}`)
83
+ .join(", ")
84
+ : null }), _jsx(Field, { label: "Verborgen in catalogus", value: info.isHiddenInCatalog ? "Ja" : "Nee" }), _jsx(Field, { label: "Laatst gewijzigd", value: formatDate(info.lastModifiedDateTime) })] })] }), info.characteristics.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground mb-2", children: "Kenmerken" }), _jsx("div", { className: "rounded-md border overflow-hidden", 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-2 font-medium", children: "VBN-code" }), _jsx("th", { className: "p-2 font-medium", children: "Waarde" })] }) }), _jsx("tbody", { children: info.characteristics.map((c) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2 font-mono text-xs", children: c.vbnCode }), _jsx("td", { className: "p-2 font-mono text-xs", children: c.vbnValueCode })] }, c.vbnCode))) })] }) })] })), info.packingConfigurations.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground mb-2", children: "Beladingen (Floriday)" }), _jsx("div", { className: "rounded-md border 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-2 font-medium", children: "Ladingdrager" }), _jsx("th", { className: "p-2 font-medium", children: "Verpakking" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Stuks/verp." }), _jsx("th", { className: "p-2 text-right font-medium", children: "Verp./laag" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Lagen" }), _jsx("th", { className: "p-2 font-medium", children: "Primair" })] }) }), _jsx("tbody", { children: info.packingConfigurations.map((p, i) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2", children: LOAD_CARRIER_LABELS[p.loadCarrier] ?? p.loadCarrier }), _jsx("td", { className: "p-2 font-mono text-xs", children: p.packagingCode ?? p.customPackageId ?? "—" }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.piecesPerPackage }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.packagesPerLayer }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.layersPerLoadCarrier }), _jsx("td", { className: "p-2", children: p.primary ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Ja" })) : (_jsx("span", { className: "text-muted-foreground text-xs", children: "nee" })) })] }, i))) })] }) })] })), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setRawOpen((o) => !o), className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [rawOpen ? (_jsx(ChevronDown, { className: "h-3 w-3" })) : (_jsx(ChevronRight, { className: "h-3 w-3" })), "Alles wat Floriday teruggeeft (raw)"] }), rawOpen && (_jsx("pre", { className: "mt-2 max-h-96 overflow-auto rounded-md border bg-muted/30 p-3 text-[11px] leading-tight font-mono", children: JSON.stringify(info.raw, null, 2) }))] }), _jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-1", children: [_jsx(ExternalLink, { className: "h-3 w-3" }), "Floriday trade item ID:", " ", _jsx("code", { className: "font-mono", children: info.id })] })] }))] }) }));
85
+ }
@@ -0,0 +1,10 @@
1
+ import type { PackingConfigurationInput } from "../../lib/floriday-payload";
2
+ interface Props {
3
+ productId: string | null;
4
+ floridayId: string | null;
5
+ packings: PackingConfigurationInput[];
6
+ packagingTypeLabel: (code: string) => string;
7
+ }
8
+ export declare function StepSamenstelling({ productId, floridayId, packings, packagingTypeLabel, }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=StepSamenstelling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StepSamenstelling.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StepSamenstelling.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAsB5E,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC9C;AAUD,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAU,EACV,QAAQ,EACR,kBAAkB,GACnB,EAAE,KAAK,2CA+ZP"}
@@ -0,0 +1,333 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { Button, Input, Popover, PopoverContent, PopoverTrigger, toast, } from "@flowselections/core";
4
+ import { Loader2, Package, Plus, Search, Trash2 } from "lucide-react";
5
+ import { supabase } from "../../integrations/supabase/client";
6
+ function newRowId() {
7
+ return `new:${typeof crypto !== "undefined" && "randomUUID" in crypto
8
+ ? crypto.randomUUID()
9
+ : Date.now().toString(36) + Math.random().toString(36).slice(2)}`;
10
+ }
11
+ export function StepSamenstelling({ productId, floridayId, packings, packagingTypeLabel, }) {
12
+ const [loading, setLoading] = useState(true);
13
+ const [saving, setSaving] = useState(false);
14
+ const [rows, setRows] = useState([]);
15
+ const [names, setNames] = useState(new Map());
16
+ const [activeTab, setActiveTab] = useState("__base__");
17
+ const hasParent = Boolean(productId || floridayId);
18
+ const load = useCallback(async () => {
19
+ if (!hasParent) {
20
+ setRows([]);
21
+ setLoading(false);
22
+ return;
23
+ }
24
+ setLoading(true);
25
+ let q = supabase
26
+ .from("product_bom_items")
27
+ .select("id,packing_configuration_id,component_product_id,component_trade_item_floriday_id,quantity,sort_order")
28
+ .order("sort_order", { ascending: true });
29
+ if (productId && floridayId) {
30
+ q = q.or(`parent_product_id.eq.${productId},parent_trade_item_floriday_id.eq.${floridayId}`);
31
+ }
32
+ else if (productId) {
33
+ q = q.eq("parent_product_id", productId);
34
+ }
35
+ else if (floridayId) {
36
+ q = q.eq("parent_trade_item_floriday_id", floridayId);
37
+ }
38
+ const { data, error } = await q;
39
+ if (error) {
40
+ toast.error("Kon samenstelling niet laden");
41
+ setRows([]);
42
+ setLoading(false);
43
+ return;
44
+ }
45
+ const list = (data ?? []).map((r) => ({
46
+ id: r.id,
47
+ packing_configuration_id: r.packing_configuration_id ?? null,
48
+ component_product_id: r.component_product_id ?? null,
49
+ component_trade_item_floriday_id: r.component_trade_item_floriday_id ?? null,
50
+ quantity: Number(r.quantity ?? 1),
51
+ sort_order: Number(r.sort_order ?? 0),
52
+ }));
53
+ setRows(list);
54
+ await resolveNames(list);
55
+ setLoading(false);
56
+ }, [productId, floridayId, hasParent]);
57
+ const resolveNames = useCallback(async (list) => {
58
+ const prodIds = Array.from(new Set(list.map((r) => r.component_product_id).filter((v) => !!v)));
59
+ const flIds = Array.from(new Set(list
60
+ .map((r) => r.component_trade_item_floriday_id)
61
+ .filter((v) => !!v)));
62
+ const map = new Map();
63
+ if (prodIds.length) {
64
+ const { data } = await supabase
65
+ .from("products")
66
+ .select("id,product,barcode,article_number")
67
+ .in("id", prodIds);
68
+ (data ?? []).forEach((p) => {
69
+ map.set(`product:${p.id}`, {
70
+ kind: "product",
71
+ id: p.id,
72
+ name: String(p.product ?? p.id),
73
+ code: p.barcode ? String(p.barcode) : p.article_number ? String(p.article_number) : undefined,
74
+ });
75
+ });
76
+ }
77
+ if (flIds.length) {
78
+ const { data } = await supabase
79
+ .from("floriday_trade_items_cache")
80
+ .select("floriday_id,data")
81
+ .in("floriday_id", flIds);
82
+ (data ?? []).forEach((n) => {
83
+ const nm = n.data?.tradeItemName?.nl ??
84
+ n.data?.tradeItemName?.en ??
85
+ Object.values(n.data?.tradeItemName ?? {})[0] ??
86
+ n.floriday_id;
87
+ const code = n.data?.supplierArticleCode ??
88
+ n.data?.articleGtin ??
89
+ undefined;
90
+ map.set(`floriday:${n.floriday_id}`, {
91
+ kind: "floriday",
92
+ id: n.floriday_id,
93
+ name: String(nm),
94
+ code: code ? String(code) : undefined,
95
+ });
96
+ });
97
+ }
98
+ setNames((prev) => {
99
+ const merged = new Map(prev);
100
+ map.forEach((v, k) => merged.set(k, v));
101
+ return merged;
102
+ });
103
+ }, []);
104
+ useEffect(() => {
105
+ load();
106
+ }, [load]);
107
+ // Tabs = base + one per current packing.
108
+ const tabs = useMemo(() => [
109
+ { key: "__base__", label: "Basissamenstelling" },
110
+ ...packings.map((p, i) => ({
111
+ key: p.id,
112
+ label: `Belading ${i + 1}${p.isPrimary ? " (primair)" : ""}${p.packagingType ? " · " + packagingTypeLabel(p.packagingType) : ""}`,
113
+ })),
114
+ ], [packings, packagingTypeLabel]);
115
+ // If the active tab no longer exists (packing removed), fall back to base.
116
+ useEffect(() => {
117
+ if (!tabs.some((t) => t.key === activeTab))
118
+ setActiveTab("__base__");
119
+ }, [tabs, activeTab]);
120
+ const currentPackingId = activeTab === "__base__" ? null : activeTab;
121
+ const currentRows = rows.filter((r) => r.packing_configuration_id === currentPackingId);
122
+ const baseRows = rows.filter((r) => r.packing_configuration_id === null);
123
+ const addRow = (opt) => {
124
+ const sortOrder = currentRows.length;
125
+ setRows((prev) => [
126
+ ...prev,
127
+ {
128
+ id: newRowId(),
129
+ packing_configuration_id: currentPackingId,
130
+ component_product_id: opt.kind === "product" ? opt.id : null,
131
+ component_trade_item_floriday_id: opt.kind === "floriday" ? opt.id : null,
132
+ quantity: 1,
133
+ sort_order: sortOrder,
134
+ _new: true,
135
+ _dirty: true,
136
+ },
137
+ ]);
138
+ setNames((prev) => {
139
+ const m = new Map(prev);
140
+ m.set(`${opt.kind}:${opt.id}`, opt);
141
+ return m;
142
+ });
143
+ };
144
+ const updateRow = (id, patch) => {
145
+ setRows((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch, _dirty: true } : r)));
146
+ };
147
+ const removeRow = async (id) => {
148
+ const row = rows.find((r) => r.id === id);
149
+ if (!row)
150
+ return;
151
+ if (!row._new) {
152
+ const { error } = await supabase.from("product_bom_items").delete().eq("id", id);
153
+ if (error) {
154
+ toast.error("Verwijderen mislukt");
155
+ return;
156
+ }
157
+ }
158
+ setRows((prev) => prev.filter((r) => r.id !== id));
159
+ };
160
+ const saveAll = async () => {
161
+ if (!hasParent) {
162
+ toast.error("Sla het artikel eerst op voordat je een samenstelling toevoegt.");
163
+ return;
164
+ }
165
+ const dirty = rows.filter((r) => r._dirty);
166
+ if (dirty.length === 0)
167
+ return;
168
+ setSaving(true);
169
+ try {
170
+ const inserts = dirty
171
+ .filter((r) => r._new)
172
+ .map((r) => ({
173
+ parent_product_id: productId,
174
+ parent_trade_item_floriday_id: floridayId,
175
+ packing_configuration_id: r.packing_configuration_id,
176
+ component_product_id: r.component_product_id,
177
+ component_trade_item_floriday_id: r.component_trade_item_floriday_id,
178
+ quantity: r.quantity,
179
+ unit: "PIECE",
180
+ sort_order: r.sort_order,
181
+ }));
182
+ const updates = dirty.filter((r) => !r._new);
183
+ if (inserts.length) {
184
+ const { data, error } = await supabase
185
+ .from("product_bom_items")
186
+ .insert(inserts)
187
+ .select("id");
188
+ if (error)
189
+ throw error;
190
+ // Map new ids back onto local rows (align by index of the "new" rows we sent)
191
+ const newIds = (data ?? []).map((d) => d.id);
192
+ const newLocal = dirty.filter((r) => r._new);
193
+ setRows((prev) => {
194
+ const map = new Map();
195
+ newLocal.forEach((r, i) => {
196
+ if (newIds[i])
197
+ map.set(r.id, newIds[i]);
198
+ });
199
+ return prev.map((r) => map.has(r.id)
200
+ ? { ...r, id: map.get(r.id), _new: false, _dirty: false }
201
+ : r);
202
+ });
203
+ }
204
+ for (const r of updates) {
205
+ const { error } = await supabase
206
+ .from("product_bom_items")
207
+ .update({
208
+ quantity: r.quantity,
209
+ sort_order: r.sort_order,
210
+ })
211
+ .eq("id", r.id);
212
+ if (error)
213
+ throw error;
214
+ }
215
+ setRows((prev) => prev.map((r) => ({ ...r, _dirty: false })));
216
+ toast.success("Samenstelling opgeslagen");
217
+ }
218
+ catch (e) {
219
+ toast.error("Opslaan mislukt: " + e.message);
220
+ }
221
+ finally {
222
+ setSaving(false);
223
+ }
224
+ };
225
+ const hasUnsaved = rows.some((r) => r._dirty);
226
+ if (!hasParent) {
227
+ return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Sla het artikel eerst op om een samenstelling te configureren." }));
228
+ }
229
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground", children: "Configureer een basissamenstelling en optioneel afwijkende onderdelen per belading. Rijen op de belading-tabbladen gelden bovenop of in plaats van de basissamenstelling." }), _jsx("div", { className: "flex flex-wrap gap-1 border-b", children: tabs.map((t) => {
230
+ const active = t.key === activeTab;
231
+ const count = rows.filter((r) => r.packing_configuration_id === (t.key === "__base__" ? null : t.key)).length;
232
+ return (_jsxs("button", { type: "button", onClick: () => setActiveTab(t.key), className: `-mb-px border-b-2 px-3 py-2 text-sm ${active
233
+ ? "border-primary text-foreground font-medium"
234
+ : "border-transparent text-muted-foreground hover:text-foreground"}`, children: [t.label, _jsx("span", { className: "ml-2 rounded bg-muted px-1.5 py-0.5 text-xs", children: count })] }, t.key));
235
+ }) }), currentPackingId && baseRows.length > 0 && (_jsxs("div", { className: "rounded-md border bg-muted/20 p-3", children: [_jsx("div", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "Basissamenstelling (referentie)" }), _jsx("ul", { className: "divide-y", children: baseRows.map((r) => {
236
+ const key = r.component_product_id
237
+ ? `product:${r.component_product_id}`
238
+ : `floriday:${r.component_trade_item_floriday_id}`;
239
+ const info = names.get(key);
240
+ return (_jsxs("li", { className: "flex items-center gap-3 py-1.5 text-sm", children: [_jsx(Package, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0 truncate", children: [info?.name ?? key, info?.code && (_jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: info.code }))] }), _jsxs("div", { className: "text-xs tabular-nums text-muted-foreground", children: [r.quantity, "\u00D7"] })] }, r.id));
241
+ }) })] })), loading ? (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Samenstelling laden\u2026"] })) : currentRows.length === 0 ? (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: currentPackingId
242
+ ? "Nog geen afwijkende onderdelen voor deze belading."
243
+ : "Nog geen onderdelen in de basissamenstelling." }) })) : (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: "Onderdeel" }), _jsx("th", { className: "px-3 py-2 font-medium w-32", children: "Aantal per verpakking" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { className: "divide-y", children: currentRows.map((r) => {
244
+ const key = r.component_product_id
245
+ ? `product:${r.component_product_id}`
246
+ : `floriday:${r.component_trade_item_floriday_id}`;
247
+ const info = names.get(key);
248
+ return (_jsxs("tr", { children: [_jsx("td", { className: "px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("div", { className: "h-8 w-8 rounded bg-muted flex items-center justify-center shrink-0", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: info?.name ?? key }), info?.code && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: info.code }))] })] }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", value: r.quantity, onChange: (e) => updateRow(r.id, { quantity: Number(e.target.value) }), className: "h-8 w-24" }) }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeRow(r.id), children: _jsx(Trash2, { className: "h-4 w-4" }) }) })] }, r.id));
249
+ }) })] }) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(ComponentPicker, { excludeKeys: new Set(currentRows.map((r) => r.component_product_id
250
+ ? `product:${r.component_product_id}`
251
+ : `floriday:${r.component_trade_item_floriday_id}`)), onPick: addRow }), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [hasUnsaved && (_jsx("span", { className: "text-xs text-amber-700", children: "Niet-opgeslagen wijzigingen" })), _jsxs(Button, { type: "button", size: "sm", onClick: saveAll, disabled: !hasUnsaved || saving, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] })] })] })] }));
252
+ }
253
+ // ============================================================
254
+ // Component picker
255
+ // ============================================================
256
+ function ComponentPicker({ onPick, excludeKeys, }) {
257
+ const [open, setOpen] = useState(false);
258
+ const [query, setQuery] = useState("");
259
+ const [loading, setLoading] = useState(false);
260
+ const [results, setResults] = useState([]);
261
+ useEffect(() => {
262
+ if (!open)
263
+ return;
264
+ const q = query.trim();
265
+ let cancelled = false;
266
+ setLoading(true);
267
+ const timer = setTimeout(async () => {
268
+ try {
269
+ const [prodRes, flRes] = await Promise.all([
270
+ q.length > 0
271
+ ? supabase
272
+ .from("products")
273
+ .select("id,product,barcode,article_number")
274
+ .or(`product.ilike.%${q}%,barcode.ilike.%${q}%,article_number.ilike.%${q}%`)
275
+ .limit(25)
276
+ : supabase
277
+ .from("products")
278
+ .select("id,product,barcode,article_number")
279
+ .order("updated_at", { ascending: false })
280
+ .limit(15),
281
+ supabase
282
+ .from("floriday_trade_items_cache")
283
+ .select("floriday_id,data")
284
+ .eq("is_deleted", false)
285
+ .limit(200),
286
+ ]);
287
+ if (cancelled)
288
+ return;
289
+ const prodOpts = (prodRes.data ?? []).map((p) => ({
290
+ kind: "product",
291
+ id: p.id,
292
+ name: String(p.product ?? p.id),
293
+ code: p.barcode
294
+ ? String(p.barcode)
295
+ : p.article_number
296
+ ? String(p.article_number)
297
+ : undefined,
298
+ }));
299
+ const flOptsAll = (flRes.data ?? []).map((n) => {
300
+ const nm = n.data?.tradeItemName?.nl ??
301
+ n.data?.tradeItemName?.en ??
302
+ Object.values(n.data?.tradeItemName ?? {})[0] ??
303
+ n.floriday_id;
304
+ const code = n.data?.supplierArticleCode ?? n.data?.articleGtin ?? undefined;
305
+ return {
306
+ kind: "floriday",
307
+ id: n.floriday_id,
308
+ name: String(nm),
309
+ code: code ? String(code) : undefined,
310
+ };
311
+ });
312
+ const flOpts = q.length
313
+ ? flOptsAll.filter((o) => o.name.toLowerCase().includes(q.toLowerCase()) ||
314
+ (o.code ?? "").toLowerCase().includes(q.toLowerCase()))
315
+ : flOptsAll.slice(0, 15);
316
+ setResults([...prodOpts, ...flOpts].filter((o) => !excludeKeys.has(`${o.kind}:${o.id}`)));
317
+ }
318
+ finally {
319
+ if (!cancelled)
320
+ setLoading(false);
321
+ }
322
+ }, 200);
323
+ return () => {
324
+ cancelled = true;
325
+ clearTimeout(timer);
326
+ };
327
+ }, [open, query, excludeKeys]);
328
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Onderdeel toevoegen"] }) }), _jsxs(PopoverContent, { className: "w-96 p-0", align: "start", children: [_jsx("div", { className: "border-b p-2", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { autoFocus: true, placeholder: "Zoek artikel of Floriday-item\u2026", value: query, onChange: (e) => setQuery(e.target.value), className: "h-8 pl-8" })] }) }), _jsx("div", { className: "max-h-80 overflow-y-auto", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 p-3 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Zoeken\u2026"] })) : results.length === 0 ? (_jsx("div", { className: "p-3 text-sm text-muted-foreground", children: "Geen resultaten." })) : (_jsx("ul", { className: "divide-y", children: results.map((r) => (_jsx("li", { children: _jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted", onClick: () => {
329
+ onPick(r);
330
+ setOpen(false);
331
+ setQuery("");
332
+ }, children: [_jsx(Package, { className: "h-4 w-4 text-muted-foreground shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: r.name }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [r.kind === "product" ? "Lokaal artikel" : "Floriday", " \u00B7", " ", r.code ?? r.id] })] })] }) }, `${r.kind}:${r.id}`))) })) })] })] }));
333
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,YAAY,4CAiM3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,YAAY,4CAkO3B"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import { Link } from "@tanstack/react-router";
4
- import { Button, Card, Input } from "@flowselections/core";
4
+ import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
5
5
  import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection";
7
7
  import { useVoorraadData } from "../../hooks/useVoorraadData";
@@ -9,6 +9,9 @@ import { ExcelExportButton } from "./ExcelExportButton";
9
9
  import { StockBulkButtons } from "./StockBulkButtons";
10
10
  import { VoorraadTable } from "./VoorraadTable";
11
11
  import { VoorraadPagination } from "./VoorraadPagination";
12
+ function isLinkedToFloriday(i) {
13
+ return Boolean(i.floridayId) || i.source === "published";
14
+ }
12
15
  function toneClasses(tone) {
13
16
  switch (tone) {
14
17
  case "emerald":
@@ -34,6 +37,8 @@ export function VoorraadPage() {
34
37
  const { items, loading, error, reload } = useVoorraadData(activeId);
35
38
  const [search, setSearch] = useState("");
36
39
  const [stockFilter, setStockFilter] = useState("all");
40
+ const [floridayFilter, setFloridayFilter] = useState("all");
41
+ const [typeFilter, setTypeFilter] = useState("all");
37
42
  const [page, setPage] = useState(1);
38
43
  const [pageSize, setPageSize] = useState(25);
39
44
  const counts = useMemo(() => {
@@ -62,12 +67,18 @@ export function VoorraadPage() {
62
67
  if (stockFilter === "below_min" &&
63
68
  !((i.minQuantity ?? 0) > 0 && avail < i.minQuantity))
64
69
  return false;
70
+ if (floridayFilter === "linked" && !isLinkedToFloriday(i))
71
+ return false;
72
+ if (floridayFilter === "unlinked" && isLinkedToFloriday(i))
73
+ return false;
74
+ if (typeFilter !== "all" && (i.itemType ?? "artikel") !== typeFilter)
75
+ return false;
65
76
  if (!q)
66
77
  return true;
67
78
  return (i.name.toLowerCase().includes(q) ||
68
79
  (i.articleNumber ?? "").toLowerCase().includes(q));
69
80
  });
70
- }, [items, search, stockFilter]);
81
+ }, [items, search, stockFilter, floridayFilter, typeFilter]);
71
82
  const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
72
83
  useEffect(() => {
73
84
  if (page > totalPages)
@@ -86,5 +97,5 @@ export function VoorraadPage() {
86
97
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
87
98
  ? "text-foreground"
88
99
  : "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));
89
- }) }), _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("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsx(ExcelExportButton, { items: filtered }), _jsx(StockBulkButtons, { items: items, activeConnectionId: activeId, onImported: reload })] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] })] }));
100
+ }) }), _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 })] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] })] }));
90
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAY3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,2CAmHA"}
1
+ {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAY3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,2CA6IA"}
@@ -27,6 +27,8 @@ export function VoorraadTable({ items, loading, }) {
27
27
  if (linkId)
28
28
  navigate({ to: "/voorraad/$id", params: { id: linkId } });
29
29
  };
30
+ const floriday = item.floriday ?? null;
31
+ const thumbUrl = floriday?.photoUrl || item.imageUrl;
30
32
  return (_jsxs("tr", { className: `border-t hover:bg-muted/30 ${clickable ? "cursor-pointer" : ""}`, onClick: clickable ? open : undefined, role: clickable ? "button" : undefined, tabIndex: clickable ? 0 : undefined, onKeyDown: clickable
31
33
  ? (e) => {
32
34
  if (e.key === "Enter" || e.key === " ") {
@@ -34,6 +36,11 @@ export function VoorraadTable({ items, loading, }) {
34
36
  open();
35
37
  }
36
38
  }
37
- : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [_jsx("td", { className: "p-2", children: item.imageUrl ? (_jsx("img", { src: item.imageUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "font-medium text-foreground", children: item.name }) }), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key)))] }, item.id));
39
+ : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [_jsx("td", { className: "p-2", children: thumbUrl ? (_jsx("img", { src: thumbUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx("span", { className: "font-medium text-foreground", children: item.name }), _jsxs("div", { role: "status", "aria-label": floriday
40
+ ? "Online in Floriday"
41
+ : "Niet online in Floriday", title: floriday
42
+ ? "Online in Floriday"
43
+ : "Niet online in Floriday", className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-0.5 text-[11px] font-medium leading-none text-muted-foreground", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full " +
44
+ (floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key)))] }, item.id));
38
45
  }) })] }) }));
39
46
  }
@@ -0,0 +1,20 @@
1
+ export interface FloridayPriceGroup {
2
+ /** Floriday priceGroupId (UUID). */
3
+ id: string;
4
+ name: string;
5
+ currency: string;
6
+ salesUnit: string;
7
+ /** Floriday buyer-organization ids die tot deze prijsgroep behoren. */
8
+ customerOrganizationIds: string[];
9
+ }
10
+ /**
11
+ * Leest alle actieve (niet verwijderde) Floriday-prijsgroepen voor de opgegeven
12
+ * connectie uit `floriday_price_groups_cache`. De sync wordt door de
13
+ * Authenticatie-module beheerd; dit is puur een read-side hook.
14
+ */
15
+ export declare function useFloridayPriceGroups(activeId: string | null): {
16
+ priceGroups: FloridayPriceGroup[];
17
+ loading: boolean;
18
+ error: string | null;
19
+ };
20
+ //# sourceMappingURL=useFloridayPriceGroups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFloridayPriceGroups.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloridayPriceGroups.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,uBAAuB,EAAE,MAAM,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;;;;EA+C7D"}