@flowselections/floriday-voorraad 1.0.75 → 1.0.77
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/components/voorraad/ArtikelWizard.js +9 -9
- package/dist-lib/components/voorraad/FloridayInfoCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/FloridayInfoCard.js +1 -1
- package/dist-lib/components/voorraad/FloridaySentCard.js +2 -2
- package/dist-lib/components/voorraad/InventoryStatusBadge.js +1 -1
- package/dist-lib/components/voorraad/ProductLinkCard.js +5 -5
- package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StepSamenstelling.js +97 -0
- package/dist-lib/components/voorraad/StockBulkButtons.js +1 -1
- package/dist-lib/components/voorraad/StockPushMonitor.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockPushMonitor.js +5 -5
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +21 -2
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +118 -35
- package/dist-lib/integrations/supabase/types.d.ts +271 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floriday-stock-supply.server.js +1 -1
- package/dist-lib/lib/product-types.js +1 -1
- package/package.json +1 -1
|
@@ -1517,7 +1517,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1517
1517
|
const vbnValid = validateStepLocal("vbn", data).ok;
|
|
1518
1518
|
const saveDraft = async () => {
|
|
1519
1519
|
if (!activeId) {
|
|
1520
|
-
toast.error("Geen actieve Floriday-
|
|
1520
|
+
toast.error("Geen actieve Floriday-koppeling. Kijk bij Instellingen › Floriday.");
|
|
1521
1521
|
return;
|
|
1522
1522
|
}
|
|
1523
1523
|
// Voor een nieuw concept blijft een VBN-code de minimale poort. Voor
|
|
@@ -1715,10 +1715,10 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1715
1715
|
? "Lokaal artikel laden..."
|
|
1716
1716
|
: productLoading
|
|
1717
1717
|
? "Product laden..."
|
|
1718
|
-
: "Floriday-
|
|
1718
|
+
: "Floriday-koppeling laden..."] }) }));
|
|
1719
1719
|
}
|
|
1720
1720
|
if (!activeConnection) {
|
|
1721
|
-
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-
|
|
1721
|
+
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-koppeling gevonden. Voeg er eerst een toe in Instellingen \u203A Floriday." }) }));
|
|
1722
1722
|
}
|
|
1723
1723
|
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || (!draftId && !vbnValid), children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), "Product opslaan"] })), (isEditingDraft || isEditingProduct) && (_jsxs(AlertDialog, { open: deleteOpen, onOpenChange: (o) => {
|
|
1724
1724
|
setDeleteOpen(o);
|
|
@@ -2152,10 +2152,10 @@ function StepEigenschappen({ data, update, spec, schemaQuery, regulatoryQuery })
|
|
|
2152
2152
|
return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Selecteer eerst een VBN-code bij stap 1 om de bijbehorende eigenschappen te laden." }));
|
|
2153
2153
|
}
|
|
2154
2154
|
if (schemaQuery.isLoading) {
|
|
2155
|
-
return (_jsxs("p", { className: "text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "
|
|
2155
|
+
return (_jsxs("p", { className: "text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Kenmerken uit Floriday laden\u2026"] }));
|
|
2156
2156
|
}
|
|
2157
2157
|
if (schemaQuery.isError || !schemaQuery.data) {
|
|
2158
|
-
return (_jsxs("p", { className: "text-sm text-destructive", children: ["
|
|
2158
|
+
return (_jsxs("p", { className: "text-sm text-destructive", children: ["De kenmerken uit Floriday konden niet worden geladen. Probeer het later opnieuw.", " ", _jsxs("details", { className: "text-xs opacity-80", children: [_jsx("summary", { className: "cursor-pointer", children: "Details voor beheer" }), schemaQuery.error instanceof Error ? schemaQuery.error.message : "onbekende fout"] })] }));
|
|
2159
2159
|
}
|
|
2160
2160
|
const hasRegulatoryLookup = /^\d{5,6}$/.test(String(data.vbnCode ?? "").trim());
|
|
2161
2161
|
if (hasRegulatoryLookup && regulatoryQuery.isLoading) {
|
|
@@ -2280,7 +2280,7 @@ function StepKlanten({ data, update, floridayCustomers }) {
|
|
|
2280
2280
|
});
|
|
2281
2281
|
}, [floridayCustomers, search, onlySelected, selectedSet]);
|
|
2282
2282
|
if (floridayCustomers.length === 0) {
|
|
2283
|
-
return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen klantorganisaties geladen vanuit Floriday. Controleer de
|
|
2283
|
+
return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen klantorganisaties geladen vanuit Floriday. Controleer de Floriday-koppeling (Instellingen \u203A Floriday) of probeer later opnieuw." }));
|
|
2284
2284
|
}
|
|
2285
2285
|
const toggle = (id) => {
|
|
2286
2286
|
if (selectedSet.has(id)) {
|
|
@@ -2326,7 +2326,7 @@ function StepFotos({ data, update, activeId }) {
|
|
|
2326
2326
|
if (!files || files.length === 0)
|
|
2327
2327
|
return;
|
|
2328
2328
|
if (!activeId) {
|
|
2329
|
-
toast.error("Geen actieve Floriday-
|
|
2329
|
+
toast.error("Geen actieve Floriday-koppeling – foto's kunnen niet worden geüpload");
|
|
2330
2330
|
return;
|
|
2331
2331
|
}
|
|
2332
2332
|
const fileArr = Array.from(files);
|
|
@@ -2661,7 +2661,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
2661
2661
|
packagingSupplierId: v,
|
|
2662
2662
|
packagingSupplierName: sup?.name,
|
|
2663
2663
|
}));
|
|
2664
|
-
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies leverancier..." }) }), _jsx(SelectContent, { children: PACKAGING_SUPPLIERS.map((s) => (_jsx(SelectItem, { value: s.id, children: s.name }, s.id))) })] }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Floricode VRS-materiaal UUID (optioneel)" }), _jsx(Input, { value: draft.packagingMaterialId ?? "", onChange: (e) => patch("packagingMaterialId", e.target.value.trim()), placeholder: "00000000-0000-0000-0000-000000000000", className: materialInvalid ? "border-destructive" : "" }), materialInvalid ? (_jsx("p", { className: "text-xs text-destructive", children: "
|
|
2664
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies leverancier..." }) }), _jsx(SelectContent, { children: PACKAGING_SUPPLIERS.map((s) => (_jsx(SelectItem, { value: s.id, children: s.name }, s.id))) })] }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Floricode VRS-materiaal UUID (optioneel)" }), _jsx(Input, { value: draft.packagingMaterialId ?? "", onChange: (e) => patch("packagingMaterialId", e.target.value.trim()), placeholder: "00000000-0000-0000-0000-000000000000", className: materialInvalid ? "border-destructive" : "" }), materialInvalid ? (_jsx("p", { className: "text-xs text-destructive", children: "Deze code klopt niet. Laat het veld leeg of kopieer de materiaalcode uit Floricode." })) : (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Wanneer ingevuld gaat het als ", _jsx("code", { children: "customPackageId" }), "naar Floriday en vervangt het de VBN-fust."] }))] })] }), hasCustomPkg && (_jsx("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-xs", children: "Eigen verpakking gekozen; het VBN-fust hieronder wordt niet naar Floriday gestuurd." }))] })), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: hasCustomPkg
|
|
2665
2665
|
? "VBN-fust (genegeerd — custom package actief)"
|
|
2666
2666
|
: draft.source === "SUPPLIER"
|
|
2667
2667
|
? "Fallback VBN-fust"
|
|
@@ -2839,7 +2839,7 @@ function StepAanbieden({ data, update, priceGroups, parentProductId }) {
|
|
|
2839
2839
|
? "Aanbod wordt aangemaakt direct na publiceren."
|
|
2840
2840
|
: "Alleen artikel publiceren, geen aanbod aanmaken." })] }), _jsx(Switch, { checked: data.offerForSale, onCheckedChange: (v) => update("offerForSale", Boolean(v)) })] }), data.offerForSale && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Voorraad (stuks)" }), _jsx(Input, { type: "number", inputMode: "numeric", min: 0, step: 1, value: stockStr, onChange: (e) => update("saleStockPieces", Math.max(0, Math.floor(Number(e.target.value) || 0))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt als fysieke voorraad geboekt en via continuous-stock naar Floriday gepusht." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Basisprijs per stuk (EUR, excl. btw)" }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", value: priceStr, onChange: (e) => update("salePricePerPiece", Number(e.target.value) || 0) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Wordt gepusht als ", _jsx("code", { children: "weekly-base-supply" }), " \u2014 de standaard catalogusprijs voor klanten zonder eigen prijsgroep."] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aanbod geldig vanaf" }), _jsx(Input, { type: "date", value: data.offerStartDate, onChange: (e) => update("offerStartDate", e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aanbod loopt automatisch tot 1 jaar na deze datum." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Zichtbaar voor" }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: data.customerOrganizationIds.length
|
|
2841
2841
|
? `${data.customerOrganizationIds.length} geselecteerde klant(en)`
|
|
2842
|
-
: "Alle gekoppelde Floriday-klanten" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Klantselectie beheer je in de stap ", _jsx("em", { children: "Klanten" }), "."] })] })] }), _jsxs("div", { className: "space-y-2 rounded-md border p-4", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Prijzen per Floriday-prijsgroep" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Laat leeg om voor die groep g\u00E9\u00E9n afwijkende prijs te publiceren. Basisprijs blijft dan gelden." })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [priceGroups.length, " groep", priceGroups.length === 1 ? "" : "en"] })] }), priceGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4", children: "Geen actieve prijsgroepen gevonden voor deze Floriday-
|
|
2842
|
+
: "Alle gekoppelde Floriday-klanten" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Klantselectie beheer je in de stap ", _jsx("em", { children: "Klanten" }), "."] })] })] }), _jsxs("div", { className: "space-y-2 rounded-md border p-4", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Prijzen per Floriday-prijsgroep" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Laat leeg om voor die groep g\u00E9\u00E9n afwijkende prijs te publiceren. Basisprijs blijft dan gelden." })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [priceGroups.length, " groep", priceGroups.length === 1 ? "" : "en"] })] }), priceGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4", children: "Geen actieve prijsgroepen gevonden voor deze Floriday-koppeling." })) : (_jsx("div", { className: "grid gap-3 sm:grid-cols-2 pt-2", children: priceGroups.map((g) => {
|
|
2843
2843
|
const val = data.priceGroupPrices?.[g.id];
|
|
2844
2844
|
return (_jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-sm", children: [g.name, _jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: [g.customerOrganizationIds.length, " klant", g.customerOrganizationIds.length === 1 ? "" : "en"] })] }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", placeholder: "\u20AC", value: val ? String(val) : "", onChange: (e) => setGroupPrice(g.id, e.target.value) })] }, g.id));
|
|
2845
2845
|
}) }))] })] })), parentProductId && (_jsx(ProductSalePriceField, { productId: parentProductId, onSaved: (p) => update("salePricePerPiece", p) })), _jsx(VatSection, { data: data, update: update })] }));
|
|
@@ -1 +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,
|
|
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,sCAmRrE"}
|
|
@@ -75,7 +75,7 @@ export function FloridayInfoCard({ floridayTradeItemId, local }) {
|
|
|
75
75
|
}, [floridayTradeItemId]);
|
|
76
76
|
if (!floridayTradeItemId)
|
|
77
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 && (
|
|
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 && (_jsx("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-sm text-amber-900", children: "Dit artikel is nog niet opgehaald uit Floriday. Klik op \u2018Bijwerken\u2019 en probeer opnieuw." })), 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
79
|
? "ring-2 ring-primary"
|
|
80
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
81
|
? info.seasonalPeriods
|
|
@@ -35,12 +35,12 @@ export function resultText(r) {
|
|
|
35
35
|
return `Week ${r.target_week ?? "?"} is gesloten in Floriday`;
|
|
36
36
|
case "pending":
|
|
37
37
|
case "processing":
|
|
38
|
-
return
|
|
38
|
+
return "Wacht op Floriday";
|
|
39
39
|
case "superseded":
|
|
40
40
|
return "Vervangen door een nieuwere stand";
|
|
41
41
|
case "dead_letter":
|
|
42
42
|
case "failed":
|
|
43
|
-
return
|
|
43
|
+
return "Niet aangekomen in Floriday. Klik op ‘Opnieuw proberen’.";
|
|
44
44
|
default:
|
|
45
45
|
return r.status;
|
|
46
46
|
}
|
|
@@ -43,5 +43,5 @@ export function InventoryStatusBadge({ setting }) {
|
|
|
43
43
|
? `${dot.title}: ${ACTION_REASON_LABELS[setting.action_reason] ?? setting.action_reason}`
|
|
44
44
|
: setting.last_sync_error
|
|
45
45
|
? `${dot.title}: ${setting.last_sync_error}`
|
|
46
|
-
: dot.title, "aria-label": dot.title }), _jsx("span", { className: "tabular-nums text-xs text-muted-foreground", children: setting.desired_quantity ?? 0 }), setting.last_sync_error?.startsWith("Geen voorkeursmagazijn") && (_jsx("span", { className: "rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive", title: setting.last_sync_error, children: "Geen magazijn \u2014 kies er een
|
|
46
|
+
: dot.title, "aria-label": dot.title }), _jsx("span", { className: "tabular-nums text-xs text-muted-foreground", children: setting.desired_quantity ?? 0 }), setting.last_sync_error?.startsWith("Geen voorkeursmagazijn") && (_jsx("span", { className: "rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive", title: setting.last_sync_error, children: "Geen magazijn \u2014 kies er een in Instellingen \u203A Floriday" }))] }));
|
|
47
47
|
}
|
|
@@ -137,10 +137,10 @@ export function ProductLinkCard({ tradeItemKey, productName }) {
|
|
|
137
137
|
throw error;
|
|
138
138
|
const rows = data ?? [];
|
|
139
139
|
const n = rows[0]?.processed_items ?? 0;
|
|
140
|
-
toast.success(`
|
|
140
|
+
toast.success(`Openstaande orders gereserveerd: ${n} orderregel(s)`);
|
|
141
141
|
}
|
|
142
142
|
catch (e) {
|
|
143
|
-
toast.error(
|
|
143
|
+
toast.error("Openstaande orders reserveren is niet gelukt. Probeer het opnieuw.");
|
|
144
144
|
}
|
|
145
145
|
finally {
|
|
146
146
|
setBusy(false);
|
|
@@ -151,9 +151,9 @@ export function ProductLinkCard({ tradeItemKey, productName }) {
|
|
|
151
151
|
return "Sla het artikel eerst op om producten te kunnen koppelen.";
|
|
152
152
|
}
|
|
153
153
|
if (linked.length === 0) {
|
|
154
|
-
return "Nog geen
|
|
154
|
+
return "Nog geen eigen product gekoppeld. Zonder koppeling worden mailbox-orders niet automatisch van de voorraad afgeschreven.";
|
|
155
155
|
}
|
|
156
|
-
return "Bestellingen op deze
|
|
156
|
+
return "Bestellingen op deze eigen producten reserveren automatisch op deze voorraadrij.";
|
|
157
157
|
}, [tradeItemKey, linked.length]);
|
|
158
|
-
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Link2, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium", children: "Gekoppelde
|
|
158
|
+
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Link2, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium", children: "Gekoppelde eigen producten" }), _jsx("div", { className: "text-xs text-muted-foreground", children: helpText })] }), _jsxs("div", { className: "flex gap-2", children: [productName && (_jsxs(Button, { variant: "outline", size: "sm", onClick: autoLinkByName, disabled: !tradeItemKey || busy, title: "Zoek eigen producten met exact deze naam en koppel ze", children: [_jsx(Wand2, { className: "h-4 w-4 mr-1" }), "Auto op naam"] })), _jsxs(Button, { variant: "outline", size: "sm", onClick: backfillReservations, disabled: busy, title: "Openstaande orders reserveren", children: [_jsx(RefreshCw, { className: "h-4 w-4 mr-1" }), "Openstaande orders reserveren"] })] })] }), _jsx(CardContent, { className: "pt-0 space-y-3", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden\u2026"] })) : (_jsxs(_Fragment, { children: [linked.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border", children: linked.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => unlinkProduct(p.id), disabled: busy, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))) })), _jsxs("div", { className: "space-y-2", children: [_jsx(Input, { placeholder: "Zoek eigen product op naam of artikelcode\u2026", value: query, onChange: (e) => setQuery(e.target.value), disabled: !tradeItemKey || busy }), candidates.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border max-h-56 overflow-auto", children: candidates.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { size: "sm", onClick: () => linkProduct(p.id), disabled: busy, children: "Koppel" })] }, p.id))) }))] })] })) })] }));
|
|
159
159
|
}
|
|
@@ -1 +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;
|
|
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;AA6FD,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAU,EACV,QAAQ,EACR,kBAAkB,GACnB,EAAE,KAAK,+BA+aP"}
|
|
@@ -3,6 +3,88 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
3
3
|
import { Button, Input, Popover, PopoverContent, PopoverTrigger, toast, } from "@flowselections/core";
|
|
4
4
|
import { Loader2, Package, Plus, Search, Trash2 } from "lucide-react";
|
|
5
5
|
import { supabase } from "../../integrations/supabase/client";
|
|
6
|
+
/** Vanaf dit aantal per stuk vragen we of de verhouding klopt. */
|
|
7
|
+
const RATIO_WARN_THRESHOLD = 10;
|
|
8
|
+
const MAX_DEPTH = 6;
|
|
9
|
+
function fmt(n) {
|
|
10
|
+
return n.toLocaleString("nl-NL", { maximumFractionDigits: 2 });
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Telt per eindonderdeel hoeveel er in 1 stuk van het artikel zit, door alle
|
|
14
|
+
* lagen van de samenstelling heen (op id). Een groep telt mee als haar eerste lid.
|
|
15
|
+
*/
|
|
16
|
+
async function unlikelyRatios(parentId, baseRows) {
|
|
17
|
+
const totals = new Map();
|
|
18
|
+
const bomCache = new Map();
|
|
19
|
+
const groupCache = new Map();
|
|
20
|
+
const childrenOf = async (pid) => {
|
|
21
|
+
if (bomCache.has(pid))
|
|
22
|
+
return bomCache.get(pid);
|
|
23
|
+
const { data } = await supabase
|
|
24
|
+
.from("product_bom_items")
|
|
25
|
+
.select("component_product_id,quantity")
|
|
26
|
+
.eq("parent_product_id", pid)
|
|
27
|
+
.is("packing_configuration_id", null);
|
|
28
|
+
const list = (data ?? [])
|
|
29
|
+
.filter((r) => r.component_product_id)
|
|
30
|
+
.map((r) => ({ id: r.component_product_id, qty: Number(r.quantity ?? 1) }));
|
|
31
|
+
bomCache.set(pid, list);
|
|
32
|
+
return list;
|
|
33
|
+
};
|
|
34
|
+
const groupMemberOf = async (pid) => {
|
|
35
|
+
if (groupCache.has(pid))
|
|
36
|
+
return groupCache.get(pid);
|
|
37
|
+
const { data: g } = await supabase
|
|
38
|
+
.from("product_groups")
|
|
39
|
+
.select("id")
|
|
40
|
+
.eq("head_product_id", pid)
|
|
41
|
+
.eq("is_active", true)
|
|
42
|
+
.order("updated_at", { ascending: false })
|
|
43
|
+
.limit(1)
|
|
44
|
+
.maybeSingle();
|
|
45
|
+
let member = null;
|
|
46
|
+
if (g?.id) {
|
|
47
|
+
const { data: m } = await supabase
|
|
48
|
+
.from("product_group_items")
|
|
49
|
+
.select("trade_item_id")
|
|
50
|
+
.eq("product_group_id", g.id)
|
|
51
|
+
.order("sort_order", { ascending: true })
|
|
52
|
+
.limit(1)
|
|
53
|
+
.maybeSingle();
|
|
54
|
+
member = m?.trade_item_id ?? null;
|
|
55
|
+
}
|
|
56
|
+
groupCache.set(pid, member);
|
|
57
|
+
return member;
|
|
58
|
+
};
|
|
59
|
+
const walk = async (pid, mult, depth) => {
|
|
60
|
+
if (depth > MAX_DEPTH)
|
|
61
|
+
return;
|
|
62
|
+
const kids = await childrenOf(pid);
|
|
63
|
+
if (kids.length > 0) {
|
|
64
|
+
for (const k of kids)
|
|
65
|
+
await walk(k.id, mult * k.qty, depth + 1);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const member = await groupMemberOf(pid);
|
|
69
|
+
if (member && member !== pid) {
|
|
70
|
+
await walk(member, mult, depth + 1);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
totals.set(pid, (totals.get(pid) ?? 0) + mult);
|
|
74
|
+
};
|
|
75
|
+
for (const r of baseRows) {
|
|
76
|
+
if (r.component_product_id)
|
|
77
|
+
await walk(r.component_product_id, Number(r.quantity) || 0, 1);
|
|
78
|
+
}
|
|
79
|
+
const over = Array.from(totals.entries()).filter(([, q]) => q >= RATIO_WARN_THRESHOLD);
|
|
80
|
+
if (over.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
const ids = [...over.map(([id]) => id), ...(parentId ? [parentId] : [])];
|
|
83
|
+
const { data: prods } = await supabase.from("products").select("id,product").in("id", ids);
|
|
84
|
+
const nameOf = new Map((prods ?? []).map((p) => [p.id, String(p.product ?? "")]));
|
|
85
|
+
const parent = (parentId && nameOf.get(parentId)) || "stuk van dit artikel";
|
|
86
|
+
return over.map(([id, qty]) => ({ parent, name: nameOf.get(id) || "onderdeel", qty }));
|
|
87
|
+
}
|
|
6
88
|
function newRowId() {
|
|
7
89
|
return `new:${typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
8
90
|
? crypto.randomUUID()
|
|
@@ -166,6 +248,21 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
166
248
|
if (dirty.length === 0)
|
|
167
249
|
return;
|
|
168
250
|
setSaving(true);
|
|
251
|
+
// Verhoudingscontrole: alle lagen meetellen, nooit blokkeren.
|
|
252
|
+
try {
|
|
253
|
+
const warnings = await unlikelyRatios(productId, rows.filter((r) => r.packing_configuration_id === null));
|
|
254
|
+
if (warnings.length > 0) {
|
|
255
|
+
const ok = window.confirm(warnings.map((w) => `Let op: 1 ${w.parent} bevat ${fmt(w.qty)} ${w.name}. Klopt dat?`).join("\n") +
|
|
256
|
+
"\n\nKies OK om toch op te slaan, of Annuleren om aan te passen.");
|
|
257
|
+
if (!ok) {
|
|
258
|
+
setSaving(false);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Controle mislukt: opslaan gaat gewoon door.
|
|
265
|
+
}
|
|
169
266
|
try {
|
|
170
267
|
const inserts = dirty
|
|
171
268
|
.filter((r) => r._new)
|
|
@@ -193,7 +193,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
193
193
|
/** Stap 1: bestand lezen en per rij controleren (nog niets opgeslagen). */
|
|
194
194
|
const handleFile = async (file) => {
|
|
195
195
|
if (!activeConnectionId) {
|
|
196
|
-
toast.error("Geen actieve Floriday-
|
|
196
|
+
toast.error("Geen actieve Floriday-koppeling. Kijk bij Instellingen › Floriday.");
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
setBusy("lezen");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"AA4NA,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+
|
|
1
|
+
{"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"AA4NA,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+BAkNjF"}
|
|
@@ -60,7 +60,7 @@ function explainError(raw) {
|
|
|
60
60
|
const m = raw.toLowerCase();
|
|
61
61
|
if (m.includes("token") || m.includes("requested path is invalid")) {
|
|
62
62
|
return {
|
|
63
|
-
text: "De Floriday
|
|
63
|
+
text: "De koppeling met Floriday werkt niet; de voorraad wordt niet doorgegeven. Neem contact op met Flow Selections.",
|
|
64
64
|
action: "Controleer in de Floriday-Authenticatiemodule het adres van Central Auth en of de gepubliceerde versie actueel is. Plan de opdracht daarna opnieuw in.",
|
|
65
65
|
};
|
|
66
66
|
}
|
|
@@ -89,7 +89,7 @@ function displayStatus(job) {
|
|
|
89
89
|
return {
|
|
90
90
|
label: "Vastgelopen",
|
|
91
91
|
tone: "error",
|
|
92
|
-
hint: "
|
|
92
|
+
hint: "Deze wijziging bleef hangen en wordt automatisch opnieuw verstuurd.",
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
return { label: "Bezig", tone: "warn" };
|
|
@@ -98,7 +98,7 @@ function displayStatus(job) {
|
|
|
98
98
|
return {
|
|
99
99
|
label: "Geen magazijn, niet verzonden",
|
|
100
100
|
tone: "error",
|
|
101
|
-
hint: job.skipReason ?? "Geen voorkeursmagazijn voor deze koppeling; kies er een
|
|
101
|
+
hint: job.skipReason ?? "Geen voorkeursmagazijn voor deze koppeling; kies er een in Instellingen › Floriday",
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
if (job.status === "skipped_no_price") {
|
|
@@ -237,9 +237,9 @@ export function StockPushMonitor({ connectionId }) {
|
|
|
237
237
|
return `laatste: ${displayStatus(last).label} om ${timeLabel(last.createdAt)}`;
|
|
238
238
|
}, [rows, jobs.isError, jobsError]);
|
|
239
239
|
const stuckCount = rows.filter((r) => displayStatus(r).label === "Vastgelopen").length;
|
|
240
|
-
return (_jsxs("section", { className: "rounded-lg border bg-card", children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", onClick: () => setOpen((v) => !v), children: [open ? (_jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })), "
|
|
240
|
+
return (_jsxs("section", { className: "rounded-lg border bg-card", children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", onClick: () => setOpen((v) => !v), children: [open ? (_jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })), "Voorraad naar Floriday testen", _jsx("span", { className: "ml-auto text-xs font-normal text-muted-foreground", children: summary })] }), open && (_jsxs("div", { className: "space-y-4 border-t px-4 py-4 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "Elke voorraadwijziging van een gekoppeld artikel zet automatisch opdrachten in de wachtrij. De Floriday-Authenticatiemodule verwerkt die elke minuut. Hieronder plan je een testopdracht in met de huidige voorraadstand, zonder iets te wijzigen." }), _jsxs("p", { className: "rounded-md border bg-muted/40 px-3 py-2 text-muted-foreground", children: ["E\u00E9n wijziging levert bewust meerdere berichten op: \u00E9\u00E9n met de", " ", _jsx("span", { className: "font-medium text-foreground", children: "actuele voorraad" }), " en daarnaast \u00E9\u00E9n per week met de ", _jsx("span", { className: "font-medium text-foreground", children: "verwachte beschikbaarheid" }), " voor de komende weken. Ze lijken op elkaar, maar gaan elk over een andere periode \u2014 zie de kolom \u201CGeldt voor\u201D. Plan je opnieuw een wijziging in, dan wordt een nog wachtende opdracht voor dezelfde week vervangen."] }), blockade.data?.blocked && (_jsxs("div", { className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-destructive", children: [_jsx("span", { className: "font-medium", children: "Actie nodig: " }), "De koppeling met Floriday werkt niet; de voorraad wordt niet doorgegeven. Neem contact op met Flow Selections.", blockade.data.message && (_jsxs("details", { className: "mt-1 opacity-80", children: [_jsx("summary", { className: "cursor-pointer", children: "Details voor beheer" }), blockade.data.message] }))] })), stuckCount > 0 && (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-amber-800", children: [stuckCount === 1 ? "Eén wijziging bleef" : `${stuckCount} wijzigingen bleven`, " hangen en ", stuckCount === 1 ? "wordt" : "worden", " automatisch opnieuw verstuurd."] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Select, { value: selected, onValueChange: setSelected, disabled: !connectionId || items.isLoading || itemList.length === 0, children: [_jsx(SelectTrigger, { className: "h-9 w-[22rem]", children: _jsx(SelectValue, { placeholder: items.isLoading ? "Artikelen laden…" : "Geen gekoppelde artikelen gevonden" }) }), _jsx(SelectContent, { className: "max-h-72", children: itemList.map((i) => (_jsx(SelectItem, { value: i.id, children: _jsxs("span", { className: "flex w-full items-center justify-between gap-3", children: [_jsx("span", { className: "truncate", children: i.name }), _jsxs("span", { className: "tabular-nums text-muted-foreground", children: [i.stock, " stuks"] })] }) }, i.id))) })] }), _jsxs(Button, { type: "button", disabled: !selected || mutation.isPending, onClick: () => selected && mutation.mutate(selected), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Test versturen"] }), _jsxs(Button, { type: "button", variant: "outline", disabled: jobs.isFetching, onClick: () => void jobs.refetch(), children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${jobs.isFetching ? "animate-spin" : ""}` }), "Ververs"] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left text-xs", children: [_jsx("thead", { className: "text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "py-1 pr-3 font-medium", children: "Artikel" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Geldt voor" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Stuks" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Status" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Pogingen" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Aangemaakt" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Bijgewerkt" }), _jsx("th", { className: "py-1 font-medium", children: "Melding" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 8, className: "py-3 text-muted-foreground", children: jobs.isError
|
|
241
241
|
? `Pushes konden niet worden geladen${jobsError?.code ? ` (fout ${jobsError.code})` : ""}: ${jobsError?.message ?? "onbekende fout"}`
|
|
242
|
-
: "Er
|
|
242
|
+
: "Er is nog geen voorraad naar Floriday gestuurd." }) })), rows.map((r) => {
|
|
243
243
|
const status = displayStatus(r);
|
|
244
244
|
const detail = explainError(r.lastError);
|
|
245
245
|
const scope = scopeLabel(r);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAyGA,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAyGA,wBAAgB,YAAY,gCAmX3B"}
|
|
@@ -56,6 +56,7 @@ export function VoorraadPage() {
|
|
|
56
56
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
57
57
|
// Artikelen zonder geldige prijs per stuk (eenheid "stuks", prijs > 0), op id.
|
|
58
58
|
const [noPriceIds, setNoPriceIds] = useState(new Set());
|
|
59
|
+
const [noPriceBreakdown, setNoPriceBreakdown] = useState({ empty: 0, zero: 0, unit: 0 });
|
|
59
60
|
const [noPriceOnly, setNoPriceOnly] = useState(false);
|
|
60
61
|
useEffect(() => {
|
|
61
62
|
let cancelled = false;
|
|
@@ -66,12 +67,24 @@ export function VoorraadPage() {
|
|
|
66
67
|
if (cancelled)
|
|
67
68
|
return;
|
|
68
69
|
const s = new Set();
|
|
70
|
+
const b = { empty: 0, zero: 0, unit: 0 };
|
|
69
71
|
for (const p of (data ?? [])) {
|
|
70
72
|
const isPiece = (p.unit ?? "").trim().toLowerCase() === "stuks";
|
|
71
|
-
if (!isPiece
|
|
73
|
+
if (!isPiece) {
|
|
72
74
|
s.add(p.id);
|
|
75
|
+
b.unit++;
|
|
76
|
+
}
|
|
77
|
+
else if (p.sale_price == null) {
|
|
78
|
+
s.add(p.id);
|
|
79
|
+
b.empty++;
|
|
80
|
+
}
|
|
81
|
+
else if (!(Number(p.sale_price) > 0)) {
|
|
82
|
+
s.add(p.id);
|
|
83
|
+
b.zero++;
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
86
|
setNoPriceIds(s);
|
|
87
|
+
setNoPriceBreakdown(b);
|
|
75
88
|
});
|
|
76
89
|
return () => {
|
|
77
90
|
cancelled = true;
|
|
@@ -146,7 +159,13 @@ export function VoorraadPage() {
|
|
|
146
159
|
{ id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
|
|
147
160
|
{ id: "below_min", label: "Onder minimum", count: counts.belowMin },
|
|
148
161
|
];
|
|
149
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? "" : "en", "
|
|
162
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? " staat" : "en staan", " niet in Floriday zonder geldige prijs:", " ", [
|
|
163
|
+
noPriceBreakdown.empty > 0 ? `${noPriceBreakdown.empty} zonder verkoopprijs` : null,
|
|
164
|
+
noPriceBreakdown.zero > 0 ? `${noPriceBreakdown.zero} met prijs € 0,00` : null,
|
|
165
|
+
noPriceBreakdown.unit > 0 ? `${noPriceBreakdown.unit} met een andere eenheid dan stuks` : null,
|
|
166
|
+
]
|
|
167
|
+
.filter(Boolean)
|
|
168
|
+
.join(", "), "."] }), " ", "Kies \"Toon deze artikelen\", open een artikel en vul bij Artikel regulier de verkoopprijs per stuk (excl. btw) in. De voorraadsturing van elk artikel heeft ook een link \"Prijs invullen\"."] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setNoPriceOnly((v) => !v), children: noPriceOnly ? "Alle artikelen tonen" : "Toon deze artikelen" })] })), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-koppeling laden mislukt. ", _jsxs("details", { className: "inline", children: [_jsx("summary", { className: "cursor-pointer inline", children: "Details voor beheer" }), connError] })] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
|
|
150
169
|
const active = stockFilter === t.id;
|
|
151
170
|
return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
|
|
152
171
|
? "text-foreground"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA6EzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BAuSA"}
|