@flowselections/floriday-voorraad 1.0.27 → 1.0.29
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.d.ts +2 -1
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +576 -324
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts +2 -1
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +5 -4
- package/dist-lib/components/voorraad/FustenPage.d.ts +2 -0
- package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FustenPage.js +97 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts +2 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.js +54 -0
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts +5 -0
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +65 -0
- package/dist-lib/components/voorraad/PublicatiesPage.d.ts +2 -0
- package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublicatiesPage.js +60 -0
- package/dist-lib/components/voorraad/PublishProgressCard.d.ts +12 -0
- package/dist-lib/components/voorraad/PublishProgressCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublishProgressCard.js +30 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.d.ts +10 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.js +12 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockBulkButtons.js +65 -2
- package/dist-lib/components/voorraad/StockSyncStatus.d.ts +2 -0
- package/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StockSyncStatus.js +23 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +8 -2
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadDashboard.js +4 -1
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +5 -1
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +10 -2
- package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts +2 -0
- package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadVeldenCard.js +97 -0
- package/dist-lib/hooks/useFusten.d.ts +17 -0
- package/dist-lib/hooks/useFusten.d.ts.map +1 -0
- package/dist-lib/hooks/useFusten.js +81 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts +17 -7
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +139 -37
- package/dist-lib/hooks/useLogisticsTasks.d.ts +13 -0
- package/dist-lib/hooks/useLogisticsTasks.d.ts.map +1 -0
- package/dist-lib/hooks/useLogisticsTasks.js +155 -0
- package/dist-lib/hooks/usePublishJob.d.ts +13 -0
- package/dist-lib/hooks/usePublishJob.d.ts.map +1 -0
- package/dist-lib/hooks/usePublishJob.js +67 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts +2 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts.map +1 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.js +31 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts +8 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -0
- package/dist-lib/hooks/useStockSyncHealth.js +42 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +37 -1
- package/dist-lib/hooks/useVoorraadCustomFields.d.ts +9 -0
- package/dist-lib/hooks/useVoorraadCustomFields.d.ts.map +1 -0
- package/dist-lib/hooks/useVoorraadCustomFields.js +26 -0
- package/dist-lib/index.d.ts +2 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +9 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +8714 -4631
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +49 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts +8467 -4487
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.d.ts +1 -1
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.js +1 -2
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +8467 -4487
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.js +14 -19
- package/dist-lib/lib/floriday-env.functions.d.ts +35712 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.functions.js +16 -0
- package/dist-lib/lib/floriday-env.server.d.ts +12 -0
- package/dist-lib/lib/floriday-env.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.server.js +15 -2
- package/dist-lib/lib/floriday-gateway.functions.d.ts +77902 -40145
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.js +12 -3
- package/dist-lib/lib/floriday-readiness.server.d.ts +11 -0
- package/dist-lib/lib/floriday-readiness.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-readiness.server.js +43 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +23115 -15155
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +31362 -19422
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.js +3 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +23191 -15231
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/fusten.d.ts +87 -0
- package/dist-lib/lib/fusten.d.ts.map +1 -0
- package/dist-lib/lib/fusten.js +389 -0
- package/dist-lib/lib/logistics-engine.d.ts +63 -0
- package/dist-lib/lib/logistics-engine.d.ts.map +1 -0
- package/dist-lib/lib/logistics-engine.js +57 -0
- package/dist-lib/lib/logistics-events.d.ts +18 -0
- package/dist-lib/lib/logistics-events.d.ts.map +1 -0
- package/dist-lib/lib/logistics-events.js +61 -0
- package/dist-lib/lib/module-runtime-config.functions.d.ts +14 -0
- package/dist-lib/lib/module-runtime-config.functions.d.ts.map +1 -0
- package/dist-lib/lib/module-runtime-config.functions.js +107 -0
- package/dist-lib/lib/publish-errors.d.ts +37 -0
- package/dist-lib/lib/publish-errors.d.ts.map +1 -0
- package/dist-lib/lib/publish-errors.js +183 -0
- package/dist-lib/lib/publish-pipeline.d.ts +91 -0
- package/dist-lib/lib/publish-pipeline.d.ts.map +1 -0
- package/dist-lib/lib/publish-pipeline.js +212 -0
- package/dist-lib/lib/publish-readiness.d.ts +42 -0
- package/dist-lib/lib/publish-readiness.d.ts.map +1 -0
- package/dist-lib/lib/publish-readiness.js +102 -0
- package/dist-lib/lib/voorraad-custom-fields.d.ts +22 -0
- package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -0
- package/dist-lib/lib/voorraad-custom-fields.js +71 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,WAAW,GACZ,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAmFA"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Card } from "@flowselections/core";
|
|
4
4
|
import { Loader2 } from "lucide-react";
|
|
5
5
|
import { ArtikelWizard } from "./ArtikelWizard";
|
|
6
|
+
import { ProductCustomFieldsCard } from "./ProductCustomFieldsCard";
|
|
6
7
|
import { supabase } from "../../integrations/supabase/client";
|
|
7
|
-
export function EditArtikelPage({ id }) {
|
|
8
|
+
export function EditArtikelPage({ id, resumeJobId, }) {
|
|
8
9
|
const [resolved, setResolved] = useState(null);
|
|
9
10
|
useEffect(() => {
|
|
10
11
|
let cancelled = false;
|
|
@@ -62,7 +63,7 @@ export function EditArtikelPage({ id }) {
|
|
|
62
63
|
return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 text-sm space-y-1", children: [_jsx("div", { children: "Artikel niet gevonden." }), resolved.reason && (_jsx("div", { className: "text-muted-foreground", children: resolved.reason }))] }) }));
|
|
63
64
|
}
|
|
64
65
|
if (resolved.kind === "product") {
|
|
65
|
-
return _jsx(ArtikelWizard, { productId: resolved.id });
|
|
66
|
+
return (_jsxs(_Fragment, { children: [_jsx(ArtikelWizard, { productId: resolved.id, resumeJobId: resumeJobId }), _jsx("div", { className: "p-6 pt-0", children: _jsx(ProductCustomFieldsCard, { productId: resolved.id }) })] }));
|
|
66
67
|
}
|
|
67
|
-
return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
|
|
68
|
+
return _jsx(ArtikelWizard, { tradeItemId: resolved.id, resumeJobId: resumeJobId });
|
|
68
69
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AA2JA,wBAAgB,UAAU,gCAkMzB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Boxes, Minus, Plus, RefreshCw, Search } from "lucide-react";
|
|
4
|
+
import { Button, Card, Input, Badge } from "@flowselections/core";
|
|
5
|
+
import { useFloridayConnection } from "../../hooks/useFloridayConnection";
|
|
6
|
+
import { useFusten } from "../../hooks/useFusten";
|
|
7
|
+
import { fetchFustMutations, formatFustDimensions, } from "../../lib/fusten";
|
|
8
|
+
/** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
|
|
9
|
+
function DimensionCell({ fust, onSave, }) {
|
|
10
|
+
const [editing, setEditing] = useState(false);
|
|
11
|
+
const [draft, setDraft] = useState({
|
|
12
|
+
length_mm: fust.length_mm?.toString() ?? "",
|
|
13
|
+
width_mm: fust.width_mm?.toString() ?? "",
|
|
14
|
+
height_mm: fust.height_mm?.toString() ?? "",
|
|
15
|
+
});
|
|
16
|
+
const label = formatFustDimensions(fust);
|
|
17
|
+
const toNum = (v) => {
|
|
18
|
+
const n = Math.trunc(Number(v));
|
|
19
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
20
|
+
};
|
|
21
|
+
if (!editing) {
|
|
22
|
+
return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
|
|
23
|
+
setDraft({
|
|
24
|
+
length_mm: fust.length_mm?.toString() ?? "",
|
|
25
|
+
width_mm: fust.width_mm?.toString() ?? "",
|
|
26
|
+
height_mm: fust.height_mm?.toString() ?? "",
|
|
27
|
+
});
|
|
28
|
+
setEditing(true);
|
|
29
|
+
}, children: label ? "Wijzigen" : "Afmeting toevoegen" })] }));
|
|
30
|
+
}
|
|
31
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1", children: [["length_mm", "width_mm", "height_mm"].map((k, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [i > 0 && _jsx("span", { className: "text-muted-foreground", children: "\u00D7" }), _jsx(Input, { type: "number", className: "h-8 w-20 text-right", placeholder: ["L", "B", "H"][i], value: draft[k], onChange: (e) => setDraft((d) => ({ ...d, [k]: e.target.value })) })] }, k))), _jsx("span", { className: "text-xs text-muted-foreground", children: "mm" })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", className: "h-7", onClick: async () => {
|
|
32
|
+
await onSave({
|
|
33
|
+
length_mm: toNum(draft.length_mm),
|
|
34
|
+
width_mm: toNum(draft.width_mm),
|
|
35
|
+
height_mm: toNum(draft.height_mm),
|
|
36
|
+
});
|
|
37
|
+
setEditing(false);
|
|
38
|
+
}, children: "Opslaan" }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-7", onClick: () => setEditing(false), children: "Annuleren" })] })] }));
|
|
39
|
+
}
|
|
40
|
+
function MutationHistory({ fust }) {
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
const [rows, setRows] = useState(null);
|
|
43
|
+
const toggle = async () => {
|
|
44
|
+
const next = !open;
|
|
45
|
+
setOpen(next);
|
|
46
|
+
if (next && rows === null) {
|
|
47
|
+
try {
|
|
48
|
+
setRows(await fetchFustMutations(fust.id));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
setRows([]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggle, className: "text-xs text-primary hover:underline", children: open ? "Historie verbergen" : "Historie" }), open && (_jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/30 p-2 text-xs", children: [rows === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), rows !== null && rows.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen mutaties." })), rows !== null &&
|
|
56
|
+
rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(m.created_at).toLocaleString("nl-NL") }), _jsxs("span", { className: "flex-1 truncate", children: [m.reason === "sale" ? "Verkoop" : "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
|
|
57
|
+
}
|
|
58
|
+
export function FustenPage() {
|
|
59
|
+
const { activeId } = useFloridayConnection();
|
|
60
|
+
const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
|
|
61
|
+
const [search, setSearch] = useState("");
|
|
62
|
+
const [busyId, setBusyId] = useState(null);
|
|
63
|
+
const [amounts, setAmounts] = useState({});
|
|
64
|
+
const filtered = useMemo(() => {
|
|
65
|
+
const q = search.trim().toLowerCase();
|
|
66
|
+
if (!q)
|
|
67
|
+
return items;
|
|
68
|
+
return items.filter((f) => f.name.toLowerCase().includes(q) ||
|
|
69
|
+
String(f.vbn_code ?? "").includes(q) ||
|
|
70
|
+
f.fust_key.toLowerCase().includes(q));
|
|
71
|
+
}, [items, search]);
|
|
72
|
+
const totals = useMemo(() => ({
|
|
73
|
+
types: items.length,
|
|
74
|
+
stock: items.reduce((sum, f) => sum + f.stock, 0),
|
|
75
|
+
belowMin: items.filter((f) => f.minimum_stock > 0 && f.stock < f.minimum_stock).length,
|
|
76
|
+
}), [items]);
|
|
77
|
+
const mutate = async (f, sign) => {
|
|
78
|
+
const step = Math.trunc(Number(amounts[f.id]) || 0);
|
|
79
|
+
if (step <= 0)
|
|
80
|
+
return;
|
|
81
|
+
setBusyId(f.id);
|
|
82
|
+
setAmounts((a) => ({ ...a, [f.id]: "" }));
|
|
83
|
+
try {
|
|
84
|
+
await adjust(f.id, sign * step, sign > 0 ? "Handmatig bijgeboekt" : "Handmatig afgeboekt");
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setBusyId(null);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: _jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
|
|
91
|
+
? "text-rose-600"
|
|
92
|
+
: ""}`, children: f.stock }) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
|
|
93
|
+
const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
|
|
94
|
+
if (v !== f.minimum_stock)
|
|
95
|
+
void patch(f.id, { minimum_stock: v });
|
|
96
|
+
} }) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, -1), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { className: "h-8 w-28 text-center", type: "number", placeholder: "Aantal", value: amounts[f.id] ?? "", onChange: (e) => setAmounts((a) => ({ ...a, [f.id]: e.target.value })) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] }) })] }, f.id))) })] }) })) })] }));
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogistiekTakenCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/LogistiekTakenCard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,kBAAkB,uCAoIjC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Toont de taken die de Logistics Engine voor de voorraadmodule plande
|
|
4
|
+
* (public.logistics_tasks, owner_module = "voorraad").
|
|
5
|
+
*
|
|
6
|
+
* Deze module beslist niets zelf: ze voert `stock.reserve` uit en meldt de
|
|
7
|
+
* taak af. In schaduwmodus wordt alleen vergeleken, nooit geschreven.
|
|
8
|
+
*/
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { Button, Card } from "@flowselections/core";
|
|
11
|
+
import { AlertTriangle, Eye, Play, RefreshCw } from "lucide-react";
|
|
12
|
+
import { runStockReserveTask, useLogisticsTasks, } from "../../hooks/useLogisticsTasks";
|
|
13
|
+
import { useFloridayConnection } from "../../hooks/useFloridayConnection";
|
|
14
|
+
import { describeLoadCarriers, parseStockReservePlan, } from "../../lib/logistics-engine";
|
|
15
|
+
export function LogistiekTakenCard() {
|
|
16
|
+
const { tasks, loading, error, refetch } = useLogisticsTasks([
|
|
17
|
+
"planned",
|
|
18
|
+
"ready",
|
|
19
|
+
]);
|
|
20
|
+
const { activeId } = useFloridayConnection();
|
|
21
|
+
const [busyId, setBusyId] = useState(null);
|
|
22
|
+
const [results, setResults] = useState({});
|
|
23
|
+
const reserveTasks = tasks.filter((t) => t.task_type === "stock.reserve");
|
|
24
|
+
if (loading)
|
|
25
|
+
return null;
|
|
26
|
+
if (!error && reserveTasks.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
async function handleRun(taskId) {
|
|
29
|
+
const task = reserveTasks.find((t) => t.id === taskId);
|
|
30
|
+
if (!task)
|
|
31
|
+
return;
|
|
32
|
+
setBusyId(taskId);
|
|
33
|
+
try {
|
|
34
|
+
const result = await runStockReserveTask(task, activeId);
|
|
35
|
+
setResults((prev) => ({ ...prev, [taskId]: result }));
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
setResults((prev) => ({ ...prev, [taskId]: e?.message ?? String(e) }));
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setBusyId(null);
|
|
42
|
+
refetch();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold", children: "Logistics Engine \u2014 taken" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "De Engine plant; deze module voert alleen uit. Taken met schaduwmodus worden uitsluitend vergeleken." })] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => refetch(), children: _jsx(RefreshCw, { className: "h-4 w-4" }) })] }), error && (_jsxs("div", { className: "flex 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("span", { children: ["Taken niet leesbaar: ", error] })] })), _jsx("ul", { className: "space-y-2", children: reserveTasks.map((task) => {
|
|
46
|
+
const plan = parseStockReservePlan(task.payload);
|
|
47
|
+
const result = results[task.id];
|
|
48
|
+
return (_jsxs("li", { className: "rounded-md border border-border p-3 text-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium", children: [task.task_type, " \u00B7 ", task.order_ref, task.shadow && (_jsx("span", { className: "ml-2 rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground", children: "schaduw" }))] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [describeLoadCarriers(plan.loadCarriers), " \u00B7", " ", plan.lines.length, " regel(s) \u00B7 status ", task.status, task.rule_version != null
|
|
49
|
+
? ` · rule v${task.rule_version}`
|
|
50
|
+
: "", task.planned_start
|
|
51
|
+
? ` · ${new Date(task.planned_start).toLocaleString("nl-NL")}`
|
|
52
|
+
: ""] })] }), _jsx(Button, { size: "sm", variant: task.shadow ? "outline" : "default", onClick: () => handleRun(task.id), disabled: busyId === task.id, children: task.shadow ? (_jsxs(_Fragment, { children: [_jsx(Eye, { className: "mr-1 h-4 w-4" }), " Vergelijk"] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "mr-1 h-4 w-4" }), " Uitvoeren"] })) })] }), result && (_jsx("div", { className: "mt-2 rounded bg-muted/50 p-2 text-xs", children: typeof result === "string" ? (_jsx("span", { className: "text-destructive", children: result })) : (_jsxs(_Fragment, { children: [_jsx("div", { children: result.message }), result.diff.length > 0 && (_jsx("ul", { className: "mt-1 space-y-0.5", children: result.diff.map((d) => (_jsxs("li", { children: [d.tradeItemKey, ": ", d.currentReserved, " \u2192", " ", d.engineReserved, " (", d.delta >= 0 ? "+" : "", d.delta, ")"] }, d.tradeItemKey))) }))] })) }))] }, task.id));
|
|
53
|
+
}) })] }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
|
|
2
|
+
export declare function ProductCustomFieldsCard({ productId }: {
|
|
3
|
+
productId: string;
|
|
4
|
+
}): import("react").JSX.Element | null;
|
|
5
|
+
//# sourceMappingURL=ProductCustomFieldsCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductCustomFieldsCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductCustomFieldsCard.tsx"],"names":[],"mappings":"AAyBA,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCA6H3E"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
4
|
+
import { Loader2, Save } from "lucide-react";
|
|
5
|
+
import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields";
|
|
6
|
+
import { fetchProductCustomValues, saveProductCustomValues, } from "../../lib/voorraad-custom-fields";
|
|
7
|
+
/** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
|
|
8
|
+
export function ProductCustomFieldsCard({ productId }) {
|
|
9
|
+
const { fields, loading } = useVoorraadCustomFields();
|
|
10
|
+
const [values, setValues] = useState({});
|
|
11
|
+
const [loadingValues, setLoadingValues] = useState(true);
|
|
12
|
+
const [saving, setSaving] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
setLoadingValues(true);
|
|
16
|
+
fetchProductCustomValues(productId)
|
|
17
|
+
.then((v) => {
|
|
18
|
+
if (!cancelled)
|
|
19
|
+
setValues(v);
|
|
20
|
+
})
|
|
21
|
+
.catch(() => {
|
|
22
|
+
if (!cancelled)
|
|
23
|
+
setValues({});
|
|
24
|
+
})
|
|
25
|
+
.finally(() => {
|
|
26
|
+
if (!cancelled)
|
|
27
|
+
setLoadingValues(false);
|
|
28
|
+
});
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true;
|
|
31
|
+
};
|
|
32
|
+
}, [productId]);
|
|
33
|
+
const detailFields = fields.filter((f) => f.show_in_detail);
|
|
34
|
+
if (loading || loadingValues) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (detailFields.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
const set = (key, value) => setValues((prev) => ({ ...prev, [key]: value }));
|
|
40
|
+
const save = async () => {
|
|
41
|
+
setSaving(true);
|
|
42
|
+
try {
|
|
43
|
+
await saveProductCustomValues(productId, Object.fromEntries(detailFields.map((f) => [f.field_key, values[f.field_key] ?? null])));
|
|
44
|
+
toast.success("Eigen velden opgeslagen");
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
toast.error(`Opslaan mislukt: ${e.message}`);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
setSaving(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Eigen velden" }), _jsx(CardDescription, { children: "Extra voorraadvelden zoals ingesteld bij Instellingen \u2192 Voorraadvelden." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: detailFields.map((f) => {
|
|
54
|
+
const v = values[f.field_key];
|
|
55
|
+
return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: `cf-${f.field_key}`, children: f.field_label }), f.field_type === "boolean" ? (_jsx("div", { className: "pt-1", children: _jsx(Checkbox, { id: `cf-${f.field_key}`, checked: v === true, onCheckedChange: (c) => set(f.field_key, c === true) }) })) : f.field_type === "select" ? (_jsxs(Select, { value: typeof v === "string" ? v : "", onValueChange: (val) => set(f.field_key, val), children: [_jsx(SelectTrigger, { id: `cf-${f.field_key}`, children: _jsx(SelectValue, { placeholder: "Kies\u2026" }) }), _jsx(SelectContent, { children: f.options.map((o) => (_jsx(SelectItem, { value: o, children: o }, o))) })] })) : (_jsx(Input, { id: `cf-${f.field_key}`, type: f.field_type === "number"
|
|
56
|
+
? "number"
|
|
57
|
+
: f.field_type === "date"
|
|
58
|
+
? "date"
|
|
59
|
+
: "text", value: v === null || v === undefined ? "" : String(v), onChange: (e) => set(f.field_key, f.field_type === "number"
|
|
60
|
+
? e.target.value === ""
|
|
61
|
+
? null
|
|
62
|
+
: Number(e.target.value)
|
|
63
|
+
: e.target.value) }))] }, f.id));
|
|
64
|
+
}) }), _jsxs(Button, { onClick: save, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Save, { className: "mr-2 h-4 w-4" })), "Eigen velden opslaan"] })] })] }));
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,eAAe,gCAuI9B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "@tanstack/react-router";
|
|
3
|
+
import { Card, Button } from "@flowselections/core";
|
|
4
|
+
import { ArrowLeft, Check, AlertTriangle, Loader2, RefreshCw } from "lucide-react";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { usePublishJobs } from "../../hooks/usePublishJob";
|
|
7
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
8
|
+
const STATUS_LABEL = {
|
|
9
|
+
running: "Bezig",
|
|
10
|
+
succeeded: "Gepubliceerd",
|
|
11
|
+
succeeded_with_warnings: "Aandachtspunten",
|
|
12
|
+
failed: "Actie nodig",
|
|
13
|
+
};
|
|
14
|
+
function StatusChip({ status }) {
|
|
15
|
+
const tone = status === "succeeded"
|
|
16
|
+
? "bg-emerald-100 text-emerald-800"
|
|
17
|
+
: status === "failed"
|
|
18
|
+
? "bg-destructive/10 text-destructive"
|
|
19
|
+
: status === "running"
|
|
20
|
+
? "bg-muted text-muted-foreground"
|
|
21
|
+
: "bg-amber-100 text-amber-800";
|
|
22
|
+
return (_jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${tone}`, children: STATUS_LABEL[status] ?? status }));
|
|
23
|
+
}
|
|
24
|
+
export function PublicatiesPage() {
|
|
25
|
+
const { jobs, loading, reload } = usePublishJobs(50);
|
|
26
|
+
const [stepsByJob, setStepsByJob] = useState({});
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (jobs.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
(async () => {
|
|
32
|
+
var _a;
|
|
33
|
+
const { data } = await supabase
|
|
34
|
+
.from("voorraad_publish_steps")
|
|
35
|
+
.select("*")
|
|
36
|
+
.in("job_id", jobs.map((j) => j.id))
|
|
37
|
+
.order("position");
|
|
38
|
+
if (cancelled)
|
|
39
|
+
return;
|
|
40
|
+
const grouped = {};
|
|
41
|
+
for (const row of (data ?? [])) {
|
|
42
|
+
(grouped[_a = row.job_id] ?? (grouped[_a] = [])).push(row);
|
|
43
|
+
}
|
|
44
|
+
setStepsByJob(grouped);
|
|
45
|
+
})();
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
};
|
|
49
|
+
}, [jobs]);
|
|
50
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-1 h-3 w-3" }), "Vernieuwen"] })] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Publicaties" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle producten die je hebt gepubliceerd, met wat er eventueel nog moet gebeuren." })] }), loading ? (_jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Laden\u2026"] })) : jobs.length === 0 ? (_jsx(Card, { className: "p-6 text-sm text-muted-foreground", children: "Er zijn nog geen publicaties." })) : (_jsx("div", { className: "space-y-3", children: jobs.map((job) => {
|
|
51
|
+
const steps = stepsByJob[job.id] ?? [];
|
|
52
|
+
const failed = steps.filter((s) => s.status === "failed");
|
|
53
|
+
const target = job.product_id ?? job.trade_item_id;
|
|
54
|
+
return (_jsxs(Card, { className: "p-4 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium", children: job.title || "Product" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [new Date(job.created_at).toLocaleString("nl-NL"), " \u00B7 referentie ", job.reference_code, job.environment !== "live" ? " · testomgeving" : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StatusChip, { status: job.status }), target && failed.length > 0 && (_jsx(Button, { asChild: true, size: "sm", variant: "outline", children: _jsx(Link, { to: "/voorraad/$id", params: { id: String(target) }, search: { hervat: job.id }, children: "Afronden" }) }))] })] }), steps.length > 0 && (_jsx("ul", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs", children: steps
|
|
55
|
+
.filter((s) => s.status !== "skipped")
|
|
56
|
+
.map((s) => (_jsxs("li", { className: "flex items-center gap-1", children: [s.status === "done" ? (_jsx(Check, { className: "h-3 w-3 text-emerald-600" })) : s.status === "failed" ? (_jsx(AlertTriangle, { className: "h-3 w-3 text-destructive" })) : (_jsx(Loader2, { className: "h-3 w-3 text-muted-foreground" })), _jsx("span", { className: s.status === "failed"
|
|
57
|
+
? "text-destructive"
|
|
58
|
+
: "text-muted-foreground", children: s.label })] }, s.id))) })), failed.map((s) => (_jsxs("p", { className: "text-xs text-destructive", children: [s.label, ": ", s.error_message] }, `err-${s.id}`)))] }, job.id));
|
|
59
|
+
}) }))] }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PublishJobRow, PublishStepRow } from "../../lib/publish-pipeline";
|
|
2
|
+
/**
|
|
3
|
+
* Toont de voortgang van een publicatie in deelstappen.
|
|
4
|
+
*/
|
|
5
|
+
export declare function PublishProgressCard({ job, steps, onRetry, retrying, onFixStep, }: {
|
|
6
|
+
job: PublishJobRow | null;
|
|
7
|
+
steps: PublishStepRow[];
|
|
8
|
+
onRetry?: () => void;
|
|
9
|
+
retrying?: boolean;
|
|
10
|
+
onFixStep?: (stepKey: string) => void;
|
|
11
|
+
}): import("react").JSX.Element | null;
|
|
12
|
+
//# sourceMappingURL=PublishProgressCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishProgressCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishProgressCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAoBhF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE;IACD,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,sCAyDA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, Button } from "@flowselections/core";
|
|
3
|
+
import { Check, AlertTriangle, Loader2, Circle, MinusCircle, RefreshCw } from "lucide-react";
|
|
4
|
+
function StatusIcon({ status }) {
|
|
5
|
+
if (status === "done")
|
|
6
|
+
return _jsx(Check, { className: "h-4 w-4 text-emerald-600" });
|
|
7
|
+
if (status === "running")
|
|
8
|
+
return _jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" });
|
|
9
|
+
if (status === "failed")
|
|
10
|
+
return _jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" });
|
|
11
|
+
if (status === "skipped")
|
|
12
|
+
return _jsx(MinusCircle, { className: "h-4 w-4 text-muted-foreground/60" });
|
|
13
|
+
return _jsx(Circle, { className: "h-4 w-4 text-muted-foreground/40" });
|
|
14
|
+
}
|
|
15
|
+
const JOB_TITLE = {
|
|
16
|
+
running: "Bezig met publiceren…",
|
|
17
|
+
succeeded: "Gepubliceerd",
|
|
18
|
+
succeeded_with_warnings: "Gepubliceerd, met aandachtspunten",
|
|
19
|
+
failed: "Publiceren niet gelukt",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Toont de voortgang van een publicatie in deelstappen.
|
|
23
|
+
*/
|
|
24
|
+
export function PublishProgressCard({ job, steps, onRetry, retrying, onFixStep, }) {
|
|
25
|
+
if (!job)
|
|
26
|
+
return null;
|
|
27
|
+
const visible = steps.filter((s) => s.status !== "skipped");
|
|
28
|
+
const failed = visible.filter((s) => s.status === "failed");
|
|
29
|
+
return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: JOB_TITLE[job.status] ?? "Publicatie" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Referentie ", job.reference_code] })] }), job.status !== "running" && failed.length > 0 && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onRetry, disabled: retrying, children: [retrying ? (_jsx(Loader2, { className: "mr-1 h-3 w-3 animate-spin" })) : (_jsx(RefreshCw, { className: "mr-1 h-3 w-3" })), "Opnieuw proberen"] }))] }), _jsx("ul", { className: "space-y-2", children: visible.map((s) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx("span", { className: "mt-0.5", children: _jsx(StatusIcon, { status: s.status }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: s.status === "done" ? "text-muted-foreground" : "", children: s.label }), s.status === "failed" && s.error_message && (_jsx("div", { className: "text-xs text-destructive", children: s.error_message }))] }), s.status === "failed" && onFixStep && (_jsx(Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onFixStep(s.step_key), children: "Oplossen" }))] }, s.id))) })] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReadinessReport } from "../../lib/publish-readiness";
|
|
2
|
+
/**
|
|
3
|
+
* Korte checklist die laat zien of het product klaar is om te publiceren.
|
|
4
|
+
*/
|
|
5
|
+
export declare function PublishReadinessCard({ report, loading, onGoToStep, }: {
|
|
6
|
+
report: ReadinessReport;
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
onGoToStep?: (stepKey: string) => void;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=PublishReadinessCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishReadinessCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishReadinessCard.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,OAAO,EACP,UAAU,GACX,EAAE;IACD,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,+BAuEA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, Button } from "@flowselections/core";
|
|
3
|
+
import { Check, AlertTriangle, Loader2, ArrowRight } from "lucide-react";
|
|
4
|
+
import { Link } from "@tanstack/react-router";
|
|
5
|
+
/**
|
|
6
|
+
* Korte checklist die laat zien of het product klaar is om te publiceren.
|
|
7
|
+
*/
|
|
8
|
+
export function PublishReadinessCard({ report, loading, onGoToStep, }) {
|
|
9
|
+
return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : report.ok ? (_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-100 text-emerald-700", children: _jsx(Check, { className: "h-3 w-3" }) })) : (_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-amber-100 text-amber-700", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) })), _jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: report.ok ? "Klaar om te publiceren" : "Nog even dit afmaken" }), _jsx("p", { className: "text-xs text-muted-foreground", children: report.ok
|
|
10
|
+
? "Alles is ingevuld. Je kunt het product nu publiceren."
|
|
11
|
+
: `${report.blockers.length} ding(en) moeten nog geregeld worden.` })] })] }), _jsx("ul", { className: "space-y-1.5", children: report.items.map((item) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [item.ok ? (_jsx(Check, { className: "mt-0.5 h-4 w-4 shrink-0 text-emerald-600" })) : (_jsx(AlertTriangle, { className: `mt-0.5 h-4 w-4 shrink-0 ${item.blocking ? "text-amber-600" : "text-muted-foreground"}` })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("span", { className: item.ok ? "text-muted-foreground" : "", children: item.label }), !item.ok && item.hint && (_jsx("div", { className: "text-xs text-muted-foreground", children: item.hint }))] }), !item.ok && item.stepKey && onGoToStep && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-6 shrink-0 px-2 text-xs", onClick: () => onGoToStep(item.stepKey), children: ["Invullen", _jsx(ArrowRight, { className: "ml-1 h-3 w-3" })] })), !item.ok && item.key === "connection" && (_jsx(Link, { to: "/instellingen", className: "shrink-0 text-xs text-primary underline", children: "Instellingen" }))] }, item.key))) })] }));
|
|
12
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsF3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+BAmoBA"}
|
|
@@ -9,6 +9,8 @@ import { upsertLocalTradeItem, updateDraftMeta } from "../../hooks/useLocalTrade
|
|
|
9
9
|
import { validateDraftForPublish } from "../../lib/floriday-import-validator";
|
|
10
10
|
import { floriday } from "../../lib/floriday-client";
|
|
11
11
|
import { ensureArticleCodes } from "../../lib/ensure-article-codes";
|
|
12
|
+
import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields";
|
|
13
|
+
import { saveProductCustomValues } from "../../lib/voorraad-custom-fields";
|
|
12
14
|
const COLUMNS = [
|
|
13
15
|
"Artikelcode",
|
|
14
16
|
"Artikelnaam",
|
|
@@ -61,6 +63,34 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
61
63
|
const [autoPublish, setAutoPublish] = useState(false);
|
|
62
64
|
const [result, setResult] = useState(null);
|
|
63
65
|
const [pastRuns, setPastRuns] = useState([]);
|
|
66
|
+
const { fields: customFields } = useVoorraadCustomFields();
|
|
67
|
+
const templateFields = customFields.filter((f) => f.show_in_template);
|
|
68
|
+
/**
|
|
69
|
+
* Leest de waarde van een eigen veld uit een Excel-rij. Kolomkoppen worden
|
|
70
|
+
* tolerant gematcht: op label óf key, hoofdletter- en spatie-ongevoelig.
|
|
71
|
+
*/
|
|
72
|
+
const readCustomValues = useCallback((row) => {
|
|
73
|
+
const norm = (s) => s.toLowerCase().replace(/[\s_-]+/g, "");
|
|
74
|
+
const lookup = new Map();
|
|
75
|
+
for (const [k, v] of Object.entries(row))
|
|
76
|
+
lookup.set(norm(k), v);
|
|
77
|
+
const patch = {};
|
|
78
|
+
for (const f of customFields) {
|
|
79
|
+
const raw = lookup.get(norm(f.field_label)) ?? lookup.get(norm(f.field_key));
|
|
80
|
+
if (raw === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const str = String(raw ?? "").trim();
|
|
83
|
+
patch[f.field_key] =
|
|
84
|
+
str === ""
|
|
85
|
+
? null
|
|
86
|
+
: f.field_type === "number"
|
|
87
|
+
? Number(str.replace(",", "."))
|
|
88
|
+
: f.field_type === "boolean"
|
|
89
|
+
? ["ja", "true", "1", "yes"].includes(str.toLowerCase())
|
|
90
|
+
: str;
|
|
91
|
+
}
|
|
92
|
+
return patch;
|
|
93
|
+
}, [customFields]);
|
|
64
94
|
const loadPastRuns = useCallback(async () => {
|
|
65
95
|
const { data, error } = await supabase
|
|
66
96
|
.from("stock_import_runs")
|
|
@@ -115,6 +145,12 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
115
145
|
const generatedCount = exportItems.filter((i, idx) => i.articleNumber !== items[idx].articleNumber).length;
|
|
116
146
|
const rows = exportItems.map((i) => {
|
|
117
147
|
const d = i.raw?.data;
|
|
148
|
+
const cf = (i.raw?.custom_fields ?? {});
|
|
149
|
+
const extra = {};
|
|
150
|
+
for (const f of templateFields) {
|
|
151
|
+
const v = cf[f.field_key];
|
|
152
|
+
extra[f.field_label] = v === null || v === undefined ? "" : v;
|
|
153
|
+
}
|
|
118
154
|
return {
|
|
119
155
|
Artikelcode: i.articleNumber,
|
|
120
156
|
Artikelnaam: i.name,
|
|
@@ -126,10 +162,12 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
126
162
|
"Minimum voorraad": i.minQuantity,
|
|
127
163
|
"Gereserveerde voorraad": i.reservedQuantity,
|
|
128
164
|
"Inkomende voorraad": i.incomingQuantity,
|
|
165
|
+
...extra,
|
|
129
166
|
};
|
|
130
167
|
});
|
|
131
|
-
const
|
|
132
|
-
ws
|
|
168
|
+
const headers = [...COLUMNS, ...templateFields.map((f) => f.field_label)];
|
|
169
|
+
const ws = XLSX.utils.json_to_sheet(rows, { header: headers });
|
|
170
|
+
ws["!cols"] = headers.map((c) => ({
|
|
133
171
|
wch: c === "Artikelnaam" ? 40 : c === "Artikelcode" ? 18 : 20,
|
|
134
172
|
}));
|
|
135
173
|
const wb = XLSX.utils.book_new();
|
|
@@ -257,6 +295,19 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
257
295
|
oldReserved: existing.reservedQuantity,
|
|
258
296
|
newReserved: applied.reserved_stock,
|
|
259
297
|
});
|
|
298
|
+
// Eigen voorraadvelden uit de template terugschrijven
|
|
299
|
+
const targetProductId = existing.productId ?? existing.localId ?? null;
|
|
300
|
+
if (targetProductId && customFields.length > 0) {
|
|
301
|
+
const patch = readCustomValues(row);
|
|
302
|
+
if (Object.keys(patch).length > 0) {
|
|
303
|
+
try {
|
|
304
|
+
await saveProductCustomValues(targetProductId, patch);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
/* eigen velden zijn niet blokkerend voor de voorraadimport */
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
260
311
|
updated++;
|
|
261
312
|
setProgress(Math.round(((idx + 1) / rows.length) * 100));
|
|
262
313
|
continue;
|
|
@@ -334,6 +385,18 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
334
385
|
},
|
|
335
386
|
});
|
|
336
387
|
created++;
|
|
388
|
+
// Eigen voorraadvelden ook op nieuw aangemaakte producten zetten
|
|
389
|
+
if (customFields.length > 0) {
|
|
390
|
+
const patch = readCustomValues(row);
|
|
391
|
+
if (Object.keys(patch).length > 0) {
|
|
392
|
+
try {
|
|
393
|
+
await saveProductCustomValues(draftId, patch);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
/* niet blokkerend */
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
337
400
|
// Stock koppelen aan het nieuwe product (draftId is nu de product-uuid)
|
|
338
401
|
const anyStock = stockValues.physical_stock !== null ||
|
|
339
402
|
stockValues.minimum_stock !== null ||
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StockSyncStatus.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockSyncStatus.tsx"],"names":[],"mappings":"AAOA,wBAAgB,eAAe,uCAgC9B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Subtiele melding op de voorraadpagina wanneer de push naar Floriday
|
|
4
|
+
* vastloopt of stilstaat.
|
|
5
|
+
*/
|
|
6
|
+
import { AlertTriangle } from "lucide-react";
|
|
7
|
+
import { useStockSyncHealth } from "../../hooks/useStockSyncHealth";
|
|
8
|
+
export function StockSyncStatus() {
|
|
9
|
+
const { data } = useStockSyncHealth();
|
|
10
|
+
if (!data)
|
|
11
|
+
return null;
|
|
12
|
+
const { failed, stalePending, oldestPendingAt } = data;
|
|
13
|
+
if (failed === 0 && stalePending === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const parts = [];
|
|
16
|
+
if (failed > 0) {
|
|
17
|
+
parts.push(`${failed} voorraadwijziging${failed === 1 ? "" : "en"} mislukt`);
|
|
18
|
+
}
|
|
19
|
+
if (stalePending > 0) {
|
|
20
|
+
parts.push(`${stalePending} wacht${stalePending === 1 ? "" : "en"} langer dan 10 minuten`);
|
|
21
|
+
}
|
|
22
|
+
return (_jsxs("div", { className: "flex 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", { children: [_jsx("span", { className: "font-medium", children: "Voorraadsync naar Floriday: " }), parts.join(" · "), ".", oldestPendingAt && (_jsxs("span", { className: "opacity-80", children: [" ", "Oudste wachtende regel: ", new Date(oldestPendingAt).toLocaleString("nl-NL"), "."] }))] })] }));
|
|
23
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAM3D;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+
|
|
1
|
+
{"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAM3D;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BAmHA"}
|