@flowselections/floriday-voorraad 1.0.143 → 1.0.147
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 +3 -1
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +57 -53
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +3 -4
- package/dist-lib/components/voorraad/FustAdministratie.d.ts +12 -0
- package/dist-lib/components/voorraad/FustAdministratie.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FustAdministratie.js +69 -0
- package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/FustenPage.js +9 -14
- package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts +6 -0
- package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts.map +1 -0
- package/dist-lib/components/voorraad/KoppelingStatusPanel.js +47 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -2
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +29 -13
- package/dist-lib/components/voorraad/ProductSalePriceField.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ProductSalePriceField.js +9 -8
- package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StepSamenstelling.js +35 -10
- package/dist-lib/components/voorraad/TelmomentButton.d.ts +8 -0
- package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -0
- package/dist-lib/components/voorraad/TelmomentButton.js +65 -0
- package/dist-lib/components/voorraad/TrayStickerCard.d.ts +5 -1
- package/dist-lib/components/voorraad/TrayStickerCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/TrayStickerCard.js +64 -66
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +0 -5
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +10 -4
- package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationPage.js +2 -1
- package/dist-lib/components/voorraad/VoorraadPage.d.ts +1 -13
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +103 -140
- package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts +6 -0
- package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadSturingCard.js +47 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts +5 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +263 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts +7 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.js +54 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts +21 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.js +15 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts +5 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +20 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts +6 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +43 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts +16 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.js +95 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +21 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +38 -0
- package/dist-lib/hooks/useVoorraadLijst.d.ts +21 -0
- package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -0
- package/dist-lib/hooks/useVoorraadLijst.js +71 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -0
- package/dist-lib/integrations/supabase/types.d.ts +989 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/artikel-pagina.d.ts +57 -0
- package/dist-lib/lib/artikel-pagina.d.ts.map +1 -0
- package/dist-lib/lib/artikel-pagina.js +93 -0
- package/dist-lib/lib/count-stock.d.ts +17 -0
- package/dist-lib/lib/count-stock.d.ts.map +1 -0
- package/dist-lib/lib/count-stock.js +26 -0
- package/dist-lib/lib/floriday-import-validator.d.ts +9 -0
- package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -1
- package/dist-lib/lib/floriday-import-validator.js +13 -0
- package/dist-lib/lib/fust-administratie.d.ts +29 -0
- package/dist-lib/lib/fust-administratie.d.ts.map +1 -0
- package/dist-lib/lib/fust-administratie.js +63 -0
- package/dist-lib/lib/fusten.d.ts +4 -0
- package/dist-lib/lib/fusten.d.ts.map +1 -1
- package/dist-lib/lib/pending-photos.d.ts +20 -0
- package/dist-lib/lib/pending-photos.d.ts.map +1 -0
- package/dist-lib/lib/pending-photos.js +90 -0
- package/dist-lib/lib/voorraad-custom-fields.d.ts +16 -1
- package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -1
- package/dist-lib/lib/voorraad-custom-fields.js +12 -6
- package/dist-lib/lib/voorraad-lijst.d.ts +91 -0
- package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -0
- package/dist-lib/lib/voorraad-lijst.js +82 -0
- package/package.json +1 -1
- package/sql/AGENTS.md +7 -0
- package/sql/floriday-voorraad-schema.sql +1627 -62
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useOpslagDeelnemer } from "./artikel/opslaan-context.js";
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
5
5
|
import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields.js";
|
|
6
6
|
import { fetchProductCustomValues, saveProductCustomValues, } from "../../lib/voorraad-custom-fields.js";
|
|
7
7
|
/** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
|
|
8
8
|
export function ProductCustomFieldsCard({ productId }) {
|
|
9
9
|
const { fields, loading } = useVoorraadCustomFields();
|
|
10
10
|
const [values, setValues] = useState({});
|
|
11
|
+
const [opened, setOpened] = useState({});
|
|
11
12
|
const [loadingValues, setLoadingValues] = useState(true);
|
|
12
13
|
const [saving, setSaving] = useState(false);
|
|
13
14
|
useEffect(() => {
|
|
@@ -15,12 +16,16 @@ export function ProductCustomFieldsCard({ productId }) {
|
|
|
15
16
|
setLoadingValues(true);
|
|
16
17
|
fetchProductCustomValues(productId)
|
|
17
18
|
.then((v) => {
|
|
18
|
-
if (!cancelled)
|
|
19
|
+
if (!cancelled) {
|
|
19
20
|
setValues(v);
|
|
21
|
+
setOpened(v);
|
|
22
|
+
}
|
|
20
23
|
})
|
|
21
24
|
.catch(() => {
|
|
22
|
-
if (!cancelled)
|
|
25
|
+
if (!cancelled) {
|
|
23
26
|
setValues({});
|
|
27
|
+
setOpened({});
|
|
28
|
+
}
|
|
24
29
|
})
|
|
25
30
|
.finally(() => {
|
|
26
31
|
if (!cancelled)
|
|
@@ -31,25 +36,36 @@ export function ProductCustomFieldsCard({ productId }) {
|
|
|
31
36
|
};
|
|
32
37
|
}, [productId]);
|
|
33
38
|
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
39
|
const set = (key, value) => setValues((prev) => ({ ...prev, [key]: value }));
|
|
40
|
+
const gewijzigd = detailFields.filter((f) => JSON.stringify(values[f.field_key] ?? null) !== JSON.stringify(opened[f.field_key] ?? null));
|
|
41
|
+
useOpslagDeelnemer("eigen_velden", gewijzigd.length > 0, () => save());
|
|
40
42
|
const save = async () => {
|
|
43
|
+
if (gewijzigd.length === 0)
|
|
44
|
+
return { ok: true };
|
|
41
45
|
setSaving(true);
|
|
42
46
|
try {
|
|
43
|
-
await saveProductCustomValues(productId, Object.fromEntries(
|
|
44
|
-
|
|
47
|
+
const res = await saveProductCustomValues(productId, Object.fromEntries(gewijzigd.map((f) => [f.field_key, opened[f.field_key] ?? null])), Object.fromEntries(gewijzigd.map((f) => [f.field_key, values[f.field_key] ?? null])));
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const namen = res.conflicten
|
|
50
|
+
.map((c) => detailFields.find((f) => f.field_key === c.veld)?.field_label ?? c.veld)
|
|
51
|
+
.join(", ");
|
|
52
|
+
const fresh = await fetchProductCustomValues(productId);
|
|
53
|
+
setOpened(fresh);
|
|
54
|
+
setValues(fresh);
|
|
55
|
+
return { ok: false, melding: `Eigen velden niet opgeslagen: iemand anders wijzigde intussen ${namen}. De nieuwste waarden zijn geladen; vul je wijziging opnieuw in.` };
|
|
56
|
+
}
|
|
57
|
+
setOpened((prev) => ({ ...prev, ...Object.fromEntries(gewijzigd.map((f) => [f.field_key, values[f.field_key] ?? null])) }));
|
|
58
|
+
return { ok: true };
|
|
45
59
|
}
|
|
46
60
|
catch (e) {
|
|
47
|
-
|
|
61
|
+
return { ok: false, melding: `Eigen velden: opslaan mislukt (${e.message}).` };
|
|
48
62
|
}
|
|
49
63
|
finally {
|
|
50
64
|
setSaving(false);
|
|
51
65
|
}
|
|
52
66
|
};
|
|
67
|
+
if (loading || loadingValues || detailFields.length === 0)
|
|
68
|
+
return null;
|
|
53
69
|
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
70
|
const v = values[f.field_key];
|
|
55
71
|
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"
|
|
@@ -61,5 +77,5 @@ export function ProductCustomFieldsCard({ productId }) {
|
|
|
61
77
|
? null
|
|
62
78
|
: Number(e.target.value)
|
|
63
79
|
: e.target.value) }))] }, f.id));
|
|
64
|
-
}) }),
|
|
80
|
+
}) }), gewijzigd.length > 0 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Niet opgeslagen wijzigingen; bewaar met \"Opslaan\" bovenaan." }))] })] }));
|
|
65
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductSalePriceField.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductSalePriceField.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,
|
|
1
|
+
{"version":3,"file":"ProductSalePriceField.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductSalePriceField.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,sCAoGA;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCAqCtE"}
|
|
@@ -58,18 +58,19 @@ export function ProductSalePriceField({ productId, onSaved, }) {
|
|
|
58
58
|
setSaving(true);
|
|
59
59
|
setError(null);
|
|
60
60
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.select("id");
|
|
61
|
+
// Zelfde opslagroute als "Opslaan" op de artikelpagina: conflictcontrole per veld.
|
|
62
|
+
const { data, error: err } = await supabase.rpc("voorraad_artikel_opslaan", {
|
|
63
|
+
p_id: productId, p_oud: { sale_price: saved }, p_nieuw: { sale_price: parsed },
|
|
64
|
+
});
|
|
66
65
|
if (err)
|
|
67
66
|
throw new Error(err.message);
|
|
68
|
-
if (!data
|
|
69
|
-
|
|
67
|
+
if (!data?.ok) {
|
|
68
|
+
const hun = data?.conflicten?.[0]?.hun;
|
|
69
|
+
throw new Error(`iemand anders heeft de prijs intussen gewijzigd${hun != null ? ` naar € ${hun}` : ""}. Laad het artikel opnieuw`);
|
|
70
|
+
}
|
|
70
71
|
setSaved(parsed);
|
|
71
72
|
onSaved?.(parsed);
|
|
72
|
-
toast.success("Prijs opgeslagen
|
|
73
|
+
toast.success("Prijs opgeslagen (alleen lokaal).");
|
|
73
74
|
}
|
|
74
75
|
catch (e) {
|
|
75
76
|
const msg = `Prijs niet opgeslagen: ${e?.message ?? e}`;
|
|
@@ -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;
|
|
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;AAwB5E,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,+BAqcP"}
|
|
@@ -94,6 +94,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
94
94
|
const [loading, setLoading] = useState(true);
|
|
95
95
|
const [saving, setSaving] = useState(false);
|
|
96
96
|
const [rows, setRows] = useState([]);
|
|
97
|
+
const [teVerwijderen, setTeVerwijderen] = useState([]);
|
|
97
98
|
const [names, setNames] = useState(new Map());
|
|
98
99
|
const [activeTab, setActiveTab] = useState("__base__");
|
|
99
100
|
const hasParent = Boolean(productId || floridayId);
|
|
@@ -131,8 +132,10 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
131
132
|
component_trade_item_floriday_id: r.component_trade_item_floriday_id ?? null,
|
|
132
133
|
quantity: Number(r.quantity ?? 1),
|
|
133
134
|
sort_order: Number(r.sort_order ?? 0),
|
|
135
|
+
_orig: { quantity: Number(r.quantity ?? 1), sort_order: Number(r.sort_order ?? 0) },
|
|
134
136
|
}));
|
|
135
137
|
setRows(list);
|
|
138
|
+
setTeVerwijderen([]);
|
|
136
139
|
await resolveNames(list);
|
|
137
140
|
setLoading(false);
|
|
138
141
|
}, [productId, floridayId, hasParent]);
|
|
@@ -230,13 +233,9 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
230
233
|
const row = rows.find((r) => r.id === id);
|
|
231
234
|
if (!row)
|
|
232
235
|
return;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
toast.error("Verwijderen mislukt");
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
236
|
+
// Verwijderen gebeurt pas bij "Opslaan" (bewuste actie met conflictcontrole).
|
|
237
|
+
if (!row._new)
|
|
238
|
+
setTeVerwijderen((l) => [...l, row]);
|
|
240
239
|
setRows((prev) => prev.filter((r) => r.id !== id));
|
|
241
240
|
};
|
|
242
241
|
const saveAll = async () => {
|
|
@@ -245,7 +244,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
245
244
|
return;
|
|
246
245
|
}
|
|
247
246
|
const dirty = rows.filter((r) => r._dirty);
|
|
248
|
-
if (dirty.length === 0)
|
|
247
|
+
if (dirty.length === 0 && teVerwijderen.length === 0)
|
|
249
248
|
return;
|
|
250
249
|
setSaving(true);
|
|
251
250
|
// Verhoudingscontrole: alle lagen meetellen, nooit blokkeren.
|
|
@@ -264,6 +263,32 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
264
263
|
// Controle mislukt: opslaan gaat gewoon door.
|
|
265
264
|
}
|
|
266
265
|
try {
|
|
266
|
+
// Conflictcontrole: bestaande regels die je wijzigt of verwijdert moeten nog
|
|
267
|
+
// dezelfde stand hebben als bij openen.
|
|
268
|
+
const gecontroleerd = [...dirty.filter((r) => !r._new), ...teVerwijderen].filter((r) => r._orig);
|
|
269
|
+
if (gecontroleerd.length) {
|
|
270
|
+
const { data: nu, error: nuErr } = await supabase
|
|
271
|
+
.from("product_bom_items")
|
|
272
|
+
.select("id,quantity,sort_order")
|
|
273
|
+
.in("id", gecontroleerd.map((r) => r.id));
|
|
274
|
+
if (nuErr)
|
|
275
|
+
throw nuErr;
|
|
276
|
+
const huidig = new Map((nu ?? []).map((r) => [r.id, r]));
|
|
277
|
+
const afwijkend = gecontroleerd.filter((r) => {
|
|
278
|
+
const h = huidig.get(r.id);
|
|
279
|
+
return !h || Number(h.quantity) !== r._orig.quantity || Number(h.sort_order) !== r._orig.sort_order;
|
|
280
|
+
});
|
|
281
|
+
if (afwijkend.length) {
|
|
282
|
+
toast.error("Iemand anders heeft deze samenstelling intussen gewijzigd. Er is niets opgeslagen; laad de samenstelling opnieuw.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const r of teVerwijderen) {
|
|
287
|
+
const { error } = await supabase.from("product_bom_items").delete().eq("id", r.id);
|
|
288
|
+
if (error)
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
setTeVerwijderen([]);
|
|
267
292
|
const inserts = dirty
|
|
268
293
|
.filter((r) => r._new)
|
|
269
294
|
.map((r) => ({
|
|
@@ -309,7 +334,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
309
334
|
if (error)
|
|
310
335
|
throw error;
|
|
311
336
|
}
|
|
312
|
-
setRows((prev) => prev.map((r) => ({ ...r, _dirty: false })));
|
|
337
|
+
setRows((prev) => prev.map((r) => ({ ...r, _dirty: false, _orig: { quantity: r.quantity, sort_order: r.sort_order } })));
|
|
313
338
|
toast.success("Samenstelling opgeslagen");
|
|
314
339
|
}
|
|
315
340
|
catch (e) {
|
|
@@ -319,7 +344,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
319
344
|
setSaving(false);
|
|
320
345
|
}
|
|
321
346
|
};
|
|
322
|
-
const hasUnsaved = rows.some((r) => r._dirty);
|
|
347
|
+
const hasUnsaved = rows.some((r) => r._dirty) || teVerwijderen.length > 0;
|
|
323
348
|
if (!hasParent) {
|
|
324
349
|
return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Sla het artikel eerst op om een samenstelling te configureren." }));
|
|
325
350
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
|
|
3
|
+
* zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
|
|
4
|
+
*/
|
|
5
|
+
export declare function TelmomentButton({ onSaved }: {
|
|
6
|
+
onSaved?: () => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=TelmomentButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TelmomentButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TelmomentButton.tsx"],"names":[],"mappings":"AA6BA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BAkGpE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ClipboardCheck } from "lucide-react";
|
|
4
|
+
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast, } from "@flowselections/core";
|
|
5
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
6
|
+
import { TELMOMENT_LABEL } from "../../lib/fust-administratie.js";
|
|
7
|
+
function lokaleInvoer(d) {
|
|
8
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
9
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
|
|
13
|
+
* zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
|
|
14
|
+
*/
|
|
15
|
+
export function TelmomentButton({ onSaved }) {
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
|
|
18
|
+
const [regels, setRegels] = useState(null);
|
|
19
|
+
const [alWeg, setAlWeg] = useState(new Set());
|
|
20
|
+
const [busy, setBusy] = useState(false);
|
|
21
|
+
const laden = async (m = moment) => {
|
|
22
|
+
setRegels(null);
|
|
23
|
+
const { data, error } = await supabase.rpc("voorraad_telmoment_lijst", { p_telmoment: new Date(m).toISOString(), p_keys: null });
|
|
24
|
+
if (error) {
|
|
25
|
+
toast.error(error.message);
|
|
26
|
+
setRegels([]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
setRegels((data?.regels ?? []));
|
|
30
|
+
setAlWeg(new Set());
|
|
31
|
+
};
|
|
32
|
+
const opslaan = async () => {
|
|
33
|
+
setBusy(true);
|
|
34
|
+
try {
|
|
35
|
+
const { data, error } = await supabase.rpc("voorraad_telmoment_opslaan", {
|
|
36
|
+
p_telmoment: new Date(moment).toISOString(), p_keys: null, p_al_weg: [...alWeg], p_notitie: null,
|
|
37
|
+
});
|
|
38
|
+
if (error)
|
|
39
|
+
throw new Error(error.message);
|
|
40
|
+
toast.success(`Telmoment vastgelegd: ${data?.buiten_telling ?? 0} orderregels tellen niet mee.`);
|
|
41
|
+
setOpen(false);
|
|
42
|
+
onSaved?.();
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
toast.error(e.message);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
setBusy(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const groepen = ["teldag_open", "vertrokken", "voor_teldag"];
|
|
52
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => { setOpen(true); void laden(); }, children: [_jsx(ClipboardCheck, { className: "mr-2 h-4 w-4" }), "Telmoment"] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Telmoment vastleggen" }) }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Moment van tellen" }), _jsx(Input, { type: "datetime-local", className: "h-9 w-56", value: moment, onChange: (e) => setMoment(e.target.value) })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void laden(), children: "Ververs lijst" })] }), _jsxs("div", { className: "max-h-[50vh] overflow-y-auto space-y-4 text-sm", children: [regels === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), regels !== null && regels.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Geen openstaande orderregels tot en met de teldag." })), regels !== null && groepen.map((g) => {
|
|
53
|
+
const lijst = regels.filter((r) => r.categorie === g);
|
|
54
|
+
if (lijst.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
return (_jsxs("div", { children: [_jsxs("p", { className: "font-medium", children: [TELMOMENT_LABEL[g], " (", lijst.length, ")"] }), _jsx("ul", { className: "mt-1 divide-y divide-border rounded-md border border-border", children: lijst.map((r) => (_jsxs("li", { className: "flex items-center gap-3 px-3 py-1.5", children: [g === "teldag_open" && (_jsx("input", { type: "checkbox", "aria-label": "Is al weg", checked: alWeg.has(r.order_item_id), onChange: (e) => setAlWeg((s) => {
|
|
57
|
+
const n = new Set(s);
|
|
58
|
+
if (e.target.checked)
|
|
59
|
+
n.add(r.order_item_id);
|
|
60
|
+
else
|
|
61
|
+
n.delete(r.order_item_id);
|
|
62
|
+
return n;
|
|
63
|
+
}) })), _jsx("span", { className: "w-28 text-muted-foreground", children: r.order_number ?? "—" }), _jsxs("span", { className: "flex-1 truncate", children: [r.artikel ?? "—", " \u00B7 ", r.klant ?? ""] }), _jsx("span", { className: "tabular-nums", children: r.aantal })] }, r.order_item_id))) })] }, g));
|
|
64
|
+
})] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => void opslaan(), disabled: busy || regels === null, children: busy ? "Opslaan…" : "Telmoment opslaan" })] })] }) })] }));
|
|
65
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
|
|
3
|
+
* Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
|
|
4
|
+
* met controle tegen de stand bij openen.
|
|
5
|
+
*/
|
|
2
6
|
export declare function TrayStickerCard({ productId }: {
|
|
3
7
|
productId: string;
|
|
4
8
|
}): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"AA4BA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,+BAoInE"}
|
|
@@ -1,100 +1,98 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle,
|
|
4
|
-
import { Loader2, Trash2, Upload } from "lucide-react";
|
|
3
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@flowselections/core";
|
|
4
|
+
import { Loader2, Trash2, Undo2, Upload } from "lucide-react";
|
|
5
5
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
6
|
+
import { useOpslagDeelnemer } from "./artikel/opslaan-context.js";
|
|
6
7
|
const BUCKET = "product-tray-stickers";
|
|
7
8
|
// Bucket is privé (publieke buckets zijn geblokkeerd): lange signed URL voor de logistiek-engine.
|
|
8
9
|
const SIGNED_URL_SECONDS = 60 * 60 * 24 * 365 * 10;
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
|
|
12
|
+
* Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
|
|
13
|
+
* met controle tegen de stand bij openen.
|
|
14
|
+
*/
|
|
10
15
|
export function TrayStickerCard({ productId }) {
|
|
11
16
|
const [row, setRow] = useState(null);
|
|
12
17
|
const [loading, setLoading] = useState(true);
|
|
13
|
-
const [
|
|
18
|
+
const [wachtend, setWachtend] = useState(null);
|
|
14
19
|
const inputRef = useRef(null);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
setLoading(true);
|
|
18
|
-
supabase
|
|
20
|
+
const laad = async () => {
|
|
21
|
+
const { data } = await supabase
|
|
19
22
|
.from("product_tray_stickers")
|
|
20
23
|
.select("*")
|
|
21
24
|
.eq("product_id", productId)
|
|
22
|
-
.maybeSingle()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
.maybeSingle();
|
|
26
|
+
setRow(data ?? null);
|
|
27
|
+
};
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
setLoading(true);
|
|
31
|
+
void laad().finally(() => {
|
|
26
32
|
if (!cancelled)
|
|
27
33
|
setLoading(false);
|
|
28
34
|
});
|
|
29
35
|
return () => {
|
|
30
36
|
cancelled = true;
|
|
31
37
|
};
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
39
|
}, [productId]);
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
useEffect(() => () => {
|
|
41
|
+
if (wachtend?.soort === "nieuw")
|
|
42
|
+
URL.revokeObjectURL(wachtend.voorbeeld);
|
|
43
|
+
}, [wachtend]);
|
|
44
|
+
const opslaan = async () => {
|
|
45
|
+
if (!wachtend)
|
|
46
|
+
return { ok: true };
|
|
47
|
+
let pad = null;
|
|
35
48
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
let next = null;
|
|
50
|
+
if (wachtend.soort === "nieuw") {
|
|
51
|
+
const file = wachtend.file;
|
|
52
|
+
const ext = file.name.includes(".") ? file.name.slice(file.name.lastIndexOf(".")) : "";
|
|
53
|
+
pad = `${productId}/${crypto.randomUUID()}${ext}`;
|
|
54
|
+
const up = await supabase.storage.from(BUCKET).upload(pad, file, { contentType: file.type || undefined });
|
|
55
|
+
if (up.error)
|
|
56
|
+
throw up.error;
|
|
57
|
+
const signed = await supabase.storage.from(BUCKET).createSignedUrl(pad, SIGNED_URL_SECONDS);
|
|
58
|
+
if (signed.error || !signed.data)
|
|
59
|
+
throw signed.error ?? new Error("Geen URL");
|
|
60
|
+
next = { product_id: productId, bestand_url: signed.data.signedUrl, bestandspad: pad, bestandsnaam: file.name, bestandsgrootte: file.size };
|
|
61
|
+
}
|
|
62
|
+
const { data, error } = await supabase.rpc("voorraad_traysticker_opslaan", {
|
|
63
|
+
p_product_id: productId,
|
|
64
|
+
p_oud_pad: row?.bestandspad ?? null,
|
|
65
|
+
p_nieuw: next,
|
|
40
66
|
});
|
|
41
|
-
if (up.error)
|
|
42
|
-
throw up.error;
|
|
43
|
-
const signed = await supabase.storage.from(BUCKET).createSignedUrl(path, SIGNED_URL_SECONDS);
|
|
44
|
-
if (signed.error || !signed.data)
|
|
45
|
-
throw signed.error ?? new Error("Geen URL");
|
|
46
|
-
const next = {
|
|
47
|
-
product_id: productId,
|
|
48
|
-
bestand_url: signed.data.signedUrl,
|
|
49
|
-
bestandspad: path,
|
|
50
|
-
bestandsnaam: file.name,
|
|
51
|
-
bestandsgrootte: file.size,
|
|
52
|
-
};
|
|
53
|
-
const { error } = await supabase
|
|
54
|
-
.from("product_tray_stickers")
|
|
55
|
-
.upsert(next, { onConflict: "product_id" });
|
|
56
67
|
if (error)
|
|
57
68
|
throw error;
|
|
69
|
+
if (!data.ok) {
|
|
70
|
+
if (pad)
|
|
71
|
+
await supabase.storage.from(BUCKET).remove([pad]);
|
|
72
|
+
setWachtend(null);
|
|
73
|
+
await laad();
|
|
74
|
+
return { ok: false, melding: "Traysticker niet opgeslagen: iemand anders wijzigde hem intussen. De nieuwste stand is geladen." };
|
|
75
|
+
}
|
|
58
76
|
if (row?.bestandspad)
|
|
59
77
|
await supabase.storage.from(BUCKET).remove([row.bestandspad]);
|
|
60
78
|
setRow(next);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
catch (e) {
|
|
64
|
-
toast.error(`Uploaden mislukt: ${e.message}`);
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
setBusy(false);
|
|
68
|
-
if (inputRef.current)
|
|
69
|
-
inputRef.current.value = "";
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
const remove = async () => {
|
|
73
|
-
if (!row)
|
|
74
|
-
return;
|
|
75
|
-
setBusy(true);
|
|
76
|
-
try {
|
|
77
|
-
const { error } = await supabase
|
|
78
|
-
.from("product_tray_stickers")
|
|
79
|
-
.delete()
|
|
80
|
-
.eq("product_id", productId);
|
|
81
|
-
if (error)
|
|
82
|
-
throw error;
|
|
83
|
-
if (row.bestandspad)
|
|
84
|
-
await supabase.storage.from(BUCKET).remove([row.bestandspad]);
|
|
85
|
-
setRow(null);
|
|
86
|
-
toast.success("Traysticker-afbeelding verwijderd");
|
|
79
|
+
setWachtend(null);
|
|
80
|
+
return { ok: true };
|
|
87
81
|
}
|
|
88
82
|
catch (e) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
setBusy(false);
|
|
83
|
+
if (pad)
|
|
84
|
+
await supabase.storage.from(BUCKET).remove([pad]);
|
|
85
|
+
return { ok: false, melding: `Traysticker: opslaan mislukt (${e.message}).` };
|
|
93
86
|
}
|
|
94
87
|
};
|
|
95
|
-
|
|
88
|
+
useOpslagDeelnemer("traysticker", wachtend !== null, opslaan);
|
|
89
|
+
const toonUrl = wachtend?.soort === "nieuw" ? wachtend.voorbeeld : wachtend?.soort === "weg" ? null : row?.bestand_url;
|
|
90
|
+
const toonNaam = wachtend?.soort === "nieuw" ? wachtend.file.name : row?.bestandsnaam;
|
|
91
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Traysticker-afbeelding" }), _jsx(CardDescription, { children: "Afbeelding die op het traysticker-etiket bij de pakbon op het pickstation wordt geprint. Dit staat los van de productfoto uit Floriday." })] }), _jsxs(CardContent, { className: "space-y-4", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : toonUrl ? (_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("img", { src: toonUrl, alt: "Traysticker", className: "h-24 w-24 rounded border object-contain bg-muted" }), _jsx("div", { className: "text-sm text-muted-foreground", children: _jsx("div", { children: toonNaam }) })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: wachtend?.soort === "weg" ? "Wordt verwijderd bij Opslaan." : "Nog geen traysticker-afbeelding." })), wachtend && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Niet opgeslagen wijziging; bewaar met \"Opslaan\" bovenaan." })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", className: "hidden", onChange: (e) => {
|
|
96
92
|
const f = e.target.files?.[0];
|
|
97
93
|
if (f)
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
setWachtend({ soort: "nieuw", file: f, voorbeeld: URL.createObjectURL(f) });
|
|
95
|
+
if (inputRef.current)
|
|
96
|
+
inputRef.current.value = "";
|
|
97
|
+
} }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => inputRef.current?.click(), disabled: loading, children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), row || wachtend?.soort === "nieuw" ? "Andere kiezen" : "Afbeelding kiezen"] }), row && wachtend?.soort !== "weg" && (_jsxs(Button, { variant: "outline", onClick: () => setWachtend({ soort: "weg" }), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] })), wachtend && (_jsxs(Button, { variant: "ghost", onClick: () => setWachtend(null), children: [_jsx(Undo2, { className: "mr-2 h-4 w-4" }), "Ongedaan maken"] }))] })] })] }));
|
|
100
98
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { TradeItem } from "../../lib/floriday-client.js";
|
|
2
|
-
/**
|
|
3
|
-
* Overzicht van producten die (nog) niet actief op Floriday staan. Er is geen concept-status
|
|
4
|
-
* meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
|
|
5
|
-
* activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
|
|
6
|
-
*/
|
|
7
2
|
export declare function ValidationOverviewTable({ items, connectionId, onChanged, }: {
|
|
8
3
|
items: TradeItem[];
|
|
9
4
|
connectionId: string | null;
|
|
@@ -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;
|
|
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;AAoB3D,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,+BAwKA"}
|
|
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
|
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { Badge, Button } from "@flowselections/core";
|
|
5
5
|
import { Link2, Loader2, Pencil, Send } from "lucide-react";
|
|
6
|
-
import { validateDraftForPublish } from "../../lib/floriday-import-validator.js";
|
|
6
|
+
import { conceptMetArtikel, validateDraftForPublish } from "../../lib/floriday-import-validator.js";
|
|
7
7
|
import { updateDraftMeta } from "../../hooks/useLocalTradeItems.js";
|
|
8
8
|
import { floriday } from "../../lib/floriday-client.js";
|
|
9
9
|
import { relinkProducts } from "../../lib/floriday-relink.js";
|
|
@@ -13,6 +13,12 @@ import { toast } from "@flowselections/core";
|
|
|
13
13
|
* meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
|
|
14
14
|
* activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
|
|
15
15
|
*/
|
|
16
|
+
/** Eigen naam en artikelnummer van het artikel (niet het Floriday-concept). */
|
|
17
|
+
function artikelVan(it) {
|
|
18
|
+
const raw = (it.raw ?? {});
|
|
19
|
+
const s = (v) => (typeof v === "string" && v.trim() ? v : null);
|
|
20
|
+
return { name: s(raw.product) ?? s(it.name), articleNumber: s(raw.article_number) ?? s(it.articleNumber) };
|
|
21
|
+
}
|
|
16
22
|
export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
17
23
|
const navigate = useNavigate();
|
|
18
24
|
const [relinking, setRelinking] = useState(false);
|
|
@@ -53,14 +59,14 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
|
53
59
|
item.raw?.floriday_payload ??
|
|
54
60
|
item.raw?.data ??
|
|
55
61
|
{};
|
|
56
|
-
const v = validateDraftForPublish(payload);
|
|
62
|
+
const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(item)));
|
|
57
63
|
if (!v.ok) {
|
|
58
64
|
toast.error("Nog niet compleet: " + v.missing.join(", "));
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
67
|
try {
|
|
62
68
|
const { buildAddTradeItemPayload } = await import("../../lib/floriday-payload.js");
|
|
63
|
-
const built = buildAddTradeItemPayload(payload);
|
|
69
|
+
const built = buildAddTradeItemPayload(conceptMetArtikel(payload, artikelVan(item)));
|
|
64
70
|
const res = await floriday.createTradeItem(connectionId, built);
|
|
65
71
|
await updateDraftMeta(productId, {
|
|
66
72
|
floridayLinkStatus: "published",
|
|
@@ -85,7 +91,7 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
|
85
91
|
it.raw?.floriday_payload ??
|
|
86
92
|
it.raw?.data ??
|
|
87
93
|
{};
|
|
88
|
-
const v = validateDraftForPublish(payload);
|
|
94
|
+
const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(it)));
|
|
89
95
|
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-3 font-mono text-xs", children: it.articleNumber || "—" }), _jsx("td", { className: "p-3", children: it.name }), _jsx("td", { className: "p-3", children: _jsx(Badge, { className: "bg-slate-500/15 text-slate-700 border-slate-500/30", children: "Niet actief op Floriday" }) }), _jsx("td", { className: "p-3 text-xs text-muted-foreground max-w-[260px]", children: reasonFor(it) }), _jsx("td", { className: "p-3", children: v.missing.length === 0 ? (_jsx("span", { className: "text-xs text-emerald-700", children: "Klaar om te activeren" })) : (_jsxs("span", { className: "text-xs text-muted-foreground", title: v.missing.join(", "), children: ["Nog nodig: ", v.missing.slice(0, 3).join(", ").toLowerCase(), v.missing.length > 3 ? ` en nog ${v.missing.length - 3} andere` : "", "."] })) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
90
96
|
const target = it.productId ?? it.localId ?? it.id;
|
|
91
97
|
navigate({ to: "/voorraad/$id", params: { id: target } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,gCAkC7B"}
|
|
@@ -5,8 +5,9 @@ import { ArrowLeft } from "lucide-react";
|
|
|
5
5
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
6
6
|
import { useVoorraadData } from "../../hooks/useVoorraadData.js";
|
|
7
7
|
import { ValidationOverviewTable } from "./ValidationOverviewTable.js";
|
|
8
|
+
import { KoppelingStatusPanel } from "./KoppelingStatusPanel.js";
|
|
8
9
|
export function ValidationPage() {
|
|
9
10
|
const { activeId } = useFloridayConnection();
|
|
10
11
|
const { items, loading, reload } = useVoorraadData(activeId);
|
|
11
|
-
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
|
|
12
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(KoppelingStatusPanel, { connectionId: activeId, onChanged: reload }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
|
|
12
13
|
}
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* "Op voorraad" = artikelen met liggende voorraad (fysiek > 0).
|
|
4
|
-
* "Vrij beschikbaar" = artikelen waarvan liggend min gereserveerd > 0.
|
|
5
|
-
*/
|
|
6
|
-
export declare function countStock(items: Array<Pick<TradeItem, "quantity" | "availableQuantity" | "minQuantity">>): {
|
|
7
|
-
total: number;
|
|
8
|
-
inStock: number;
|
|
9
|
-
outOfStock: number;
|
|
10
|
-
belowMin: number;
|
|
11
|
-
fullyReserved: number;
|
|
12
|
-
freeAvailable: number;
|
|
13
|
-
};
|
|
1
|
+
export { countStock } from "../../lib/count-stock.js";
|
|
14
2
|
export declare function VoorraadPage(): import("react").JSX.Element;
|
|
15
3
|
//# sourceMappingURL=VoorraadPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AA2CnD,wBAAgB,YAAY,gCA6M3B"}
|