@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.
- package/dist-lib/assets/floriday-logo.png.asset.json +4 -4
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +655 -93
- package/dist-lib/components/voorraad/FloridayInfoCard.d.ts +12 -0
- package/dist-lib/components/voorraad/FloridayInfoCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FloridayInfoCard.js +85 -0
- package/dist-lib/components/voorraad/StepSamenstelling.d.ts +10 -0
- package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StepSamenstelling.js +333 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +14 -3
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +8 -1
- package/dist-lib/hooks/useFloridayPriceGroups.d.ts +20 -0
- package/dist-lib/hooks/useFloridayPriceGroups.d.ts.map +1 -0
- package/dist-lib/hooks/useFloridayPriceGroups.js +55 -0
- package/dist-lib/hooks/useFloridayVbnRequired.d.ts +5 -0
- package/dist-lib/hooks/useFloridayVbnRequired.d.ts.map +1 -0
- package/dist-lib/hooks/useFloridayVbnRequired.js +16 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +14 -0
- package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemSpec.js +48 -1
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +36 -5
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2388 -345
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +2388 -345
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2388 -345
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2437 -343
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +11 -9
- package/dist-lib/lib/centralSecrets.server.d.ts +21 -0
- package/dist-lib/lib/centralSecrets.server.d.ts.map +1 -0
- package/dist-lib/lib/centralSecrets.server.js +111 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts +2388 -345
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.d.ts +4 -0
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +2388 -345
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +10860 -645
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.js +34 -9
- package/dist-lib/lib/floriday-info.d.ts +54 -0
- package/dist-lib/lib/floriday-info.d.ts.map +1 -0
- package/dist-lib/lib/floriday-info.js +114 -0
- package/dist-lib/lib/floriday-payload.d.ts +21 -3
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +167 -34
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +27769 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-vbn-required.functions.js +85 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +6602 -473
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +4681 -594
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.js +36 -1
- package/dist-lib/lib/trade-item-prices.d.ts +43 -0
- package/dist-lib/lib/trade-item-prices.d.ts.map +1 -0
- package/dist-lib/lib/trade-item-prices.js +229 -0
- 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":"
|
|
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,
|
|
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:
|
|
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"}
|