@flowselections/floriday-voorraad 1.0.145 → 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 +56 -52
- 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/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/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/VoorraadPage.d.ts +1 -13
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +102 -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 +465 -1
- 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/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 +4 -0
- package/sql/floriday-voorraad-schema.sql +723 -10
|
@@ -9,7 +9,9 @@ export interface WizardInitial {
|
|
|
9
9
|
}
|
|
10
10
|
/** Neemt naam, code, voorraad, prijs en (indien compleet) belading over. */
|
|
11
11
|
export declare function applyWizardInitial(base: WizardData, init?: WizardInitial): WizardData;
|
|
12
|
-
export declare function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, initial, }?: {
|
|
12
|
+
export declare function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, initial, embedded, }?: {
|
|
13
|
+
/** Binnen de artikelpagina: voorraad, samenstelling en geschiedenis staan in de eigen tabbladen. */
|
|
14
|
+
embedded?: boolean;
|
|
13
15
|
draftId?: string;
|
|
14
16
|
tradeItemId?: string;
|
|
15
17
|
productId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA6DA,OAAO,EAUL,KAAK,UAAU,EAMhB,MAAM,4BAA4B,CAAC;AA4DpC,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMpF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,UAAU,CA0BrF;AAgED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,EACP,QAAgB,GACjB,GAAE;IACD,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;CACpB,+BA+6EL"}
|
|
@@ -3,9 +3,10 @@ import { saveOwnPackings, packingRowsFromWizard } from "../../lib/product-packin
|
|
|
3
3
|
import { FloridayLoadCarriers } from "./FloridayLoadCarriers.js";
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
6
|
-
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button, Card, CardContent, Checkbox, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, toast, } from "@flowselections/core";
|
|
6
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button, Card, CardContent, Checkbox, Dialog, DialogDescription, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, toast, } from "@flowselections/core";
|
|
7
7
|
import { ArrowLeft, ArrowRight, Check, ChevronDown, ClipboardList, Info, Loader2, Plus, Search, Trash2, Upload, UploadCloud, Users, X, Star, } from "lucide-react";
|
|
8
8
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
9
|
+
import { countPendingPhotos, registerPendingPhoto, resolvePendingPhotos, wijzigingenOverzicht } from "../../lib/pending-photos.js";
|
|
9
10
|
import { useFlorydayCustomers } from "../../hooks/useFlorydayCustomers.js";
|
|
10
11
|
import { floriday } from "../../lib/floriday-client.js";
|
|
11
12
|
import { listFloricode, listFloricodeByField } from "../../lib/floricode-client.js";
|
|
@@ -91,7 +92,6 @@ const STEPS = [
|
|
|
91
92
|
{ key: "fotos", label: "Foto's" },
|
|
92
93
|
{ key: "warehouse", label: "Warehouse" },
|
|
93
94
|
{ key: "beladingen", label: "Beladingen" },
|
|
94
|
-
{ key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
|
|
95
95
|
{ key: "samenstelling", label: "Samenstelling" },
|
|
96
96
|
{ key: "beschikbaarheid", label: "Verwachte beschikbaarheid" },
|
|
97
97
|
{ key: "aanbieden", label: "Aanbieden & voorraad" },
|
|
@@ -137,7 +137,7 @@ async function lookupProductGroupForVbn(vbnCode) {
|
|
|
137
137
|
.maybeSingle();
|
|
138
138
|
return data ? groupOfRecord(data) : null;
|
|
139
139
|
}
|
|
140
|
-
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, initial, } = {}) {
|
|
140
|
+
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, initial, embedded = false, } = {}) {
|
|
141
141
|
const navigate = useNavigate();
|
|
142
142
|
const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
|
|
143
143
|
const connectionReadiness = useQuery({
|
|
@@ -960,7 +960,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
960
960
|
const update = (key, value) => setData((d) => ({ ...d, [key]: value }));
|
|
961
961
|
// Stap 1 (VBN) is de poort: zodra die geldig is, mag de gebruiker vrij
|
|
962
962
|
// tussen alle stappen springen door erop te klikken.
|
|
963
|
-
const firstStepUnlocked = useMemo(() => steps.length > 0 && validateStepLocal(steps[0].key, data).ok, [steps, data]);
|
|
963
|
+
const firstStepUnlocked = useMemo(() => (!isEditingPublished && !publishToFloriday) || (steps.length > 0 && validateStepLocal(steps[0].key, data).ok), [steps, data, isEditingPublished, publishToFloriday]);
|
|
964
964
|
// Per-stap validatie → bron voor visuele status én publish-gate.
|
|
965
965
|
// "samenstelling" is read-only ("binnenkort beschikbaar") en mag nooit
|
|
966
966
|
// als ongeldig tellen.
|
|
@@ -971,7 +971,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
971
971
|
}), [steps, data, spec, regulatoryQuery.isLoading, regulatoryQuery.isError, regulatoryQuery.error]);
|
|
972
972
|
// Foto's zijn alleen verplicht om naar Floriday te publiceren
|
|
973
973
|
// (Floriday: "One or more product images"); opslaan als concept kan zonder.
|
|
974
|
-
|
|
974
|
+
// 1.0.146: zonder publiceren telt alleen wat lokaal nodig is (een naam).
|
|
975
|
+
// Floriday-velden en foto's zijn dan nooit "nog nodig".
|
|
976
|
+
const lokaalAlleen = !isEditingPublished && !publishToFloriday;
|
|
977
|
+
const gateValidity = lokaalAlleen
|
|
978
|
+
? steps.map((st) => st.key !== "artikel" || data.name.trim().length > 0)
|
|
979
|
+
: stepValidity;
|
|
975
980
|
const invalidStepCount = gateValidity.filter((v) => !v).length;
|
|
976
981
|
const firstInvalidIndex = gateValidity.findIndex((v) => !v);
|
|
977
982
|
const goTo = (i) => {
|
|
@@ -1197,6 +1202,16 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1197
1202
|
});
|
|
1198
1203
|
});
|
|
1199
1204
|
}, []);
|
|
1205
|
+
const wizardData = data;
|
|
1206
|
+
const [overzicht, setOverzicht] = useState(null);
|
|
1207
|
+
/** Niets gaat naar Floriday zonder dit overzicht vooraf. */
|
|
1208
|
+
const metOverzicht = (run, naarFloriday) => {
|
|
1209
|
+
if (!naarFloriday) {
|
|
1210
|
+
run();
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
setOverzicht({ wijzigingen: wijzigingenOverzicht(lastSavedRef.current, data), fotos: countPendingPhotos(data), run });
|
|
1214
|
+
};
|
|
1200
1215
|
const submit = async (options) => {
|
|
1201
1216
|
if (!activeId) {
|
|
1202
1217
|
toast.error("Geen actieve verbinding met de handelsomgeving", {
|
|
@@ -1204,6 +1219,18 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1204
1219
|
});
|
|
1205
1220
|
return;
|
|
1206
1221
|
}
|
|
1222
|
+
// Foto's gaan pas nu naar Floriday (na het overzicht vooraf).
|
|
1223
|
+
let data = wizardData;
|
|
1224
|
+
if (wantsPublish && countPendingPhotos(wizardData) > 0) {
|
|
1225
|
+
try {
|
|
1226
|
+
data = await resolvePendingPhotos(wizardData, (f) => floriday.uploadImage(activeId, f, f.type || "image/jpeg", f.name));
|
|
1227
|
+
setData(data);
|
|
1228
|
+
}
|
|
1229
|
+
catch (e) {
|
|
1230
|
+
toast.error(e.message || "Foto's konden niet naar Floriday");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1207
1234
|
// Zijn de productregels (schema) nog niet binnen? Dan publiceren we niet,
|
|
1208
1235
|
// maar bewaren we de wijziging ALTIJD lokaal en markeren we hem als
|
|
1209
1236
|
// "nog niet doorgegeven aan Floriday".
|
|
@@ -1820,6 +1847,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1820
1847
|
// 2. Doorgeven aan Floriday.
|
|
1821
1848
|
try {
|
|
1822
1849
|
await saveWarehousePreference(pushTargetId, snapshot.warehouseId);
|
|
1850
|
+
snapshot = await resolvePendingPhotos(snapshot, (f) => floriday.uploadImage(activeId, f, f.type || "image/jpeg", f.name));
|
|
1851
|
+
setData(snapshot);
|
|
1823
1852
|
const payload = buildUpdateTradeItemPayload(snapshot);
|
|
1824
1853
|
const res = await floriday.updateTradeItem(activeId, pushTargetId, payload);
|
|
1825
1854
|
if (!res?.attemptId) {
|
|
@@ -1872,16 +1901,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1872
1901
|
lastSavedRef.current = serialized;
|
|
1873
1902
|
return;
|
|
1874
1903
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
const snapshot = data;
|
|
1878
|
-
const t = window.setTimeout(() => {
|
|
1879
|
-
// De lokale opslag slaagt (bijna) altijd; de baseline mag dus mee, ook als
|
|
1880
|
-
// de Floriday-push faalt — de invoer staat dan veilig in de eigen database.
|
|
1881
|
-
lastSavedRef.current = serialized;
|
|
1882
|
-
void persistAndPush(snapshot);
|
|
1883
|
-
}, 800);
|
|
1884
|
-
return () => window.clearTimeout(t);
|
|
1904
|
+
// 1.0.146: geen automatisch opslaan/versturen meer. Alleen bewust via
|
|
1905
|
+
// "Naar Floriday sturen" / "Op Floriday zetten" met het overzicht vooraf.
|
|
1885
1906
|
}, [
|
|
1886
1907
|
data,
|
|
1887
1908
|
draftId,
|
|
@@ -1910,7 +1931,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1910
1931
|
if (connLoading || draftLoading || publishedLoading || productLoading) {
|
|
1911
1932
|
// Voorraadblok direct bruikbaar; Floriday-gegevens laden op de achtergrond (1.0.138).
|
|
1912
1933
|
const vroegVoorraad = !connLoading && (tradeItemId || productId || draftId);
|
|
1913
|
-
return (_jsxs("main", { className: "p-6 space-y-4", children: [vroegVoorraad && (_jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: productId ?? null })), _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" }), publishedLoading
|
|
1934
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [vroegVoorraad && !embedded && (_jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: productId ?? null })), _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" }), publishedLoading
|
|
1914
1935
|
? "Artikel laden..."
|
|
1915
1936
|
: draftLoading
|
|
1916
1937
|
? "Lokaal artikel laden..."
|
|
@@ -1925,7 +1946,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1925
1946
|
setDeleteOpen(o);
|
|
1926
1947
|
if (!o)
|
|
1927
1948
|
setDeleteAck(false);
|
|
1928
|
-
}, children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, productId: productId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(FloridayStockReasonLine, { productId: linkedProductId ?? productId ?? null }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: retrySync, children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [editTarget.text, " Je kunt vrij tussen alle secties navigeren."] })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(ProductStructureCard, { productId: linkedProductId ?? productId ?? null }), _jsx(ArtikelCompositionCard, { productId: linkedProductId ?? productId ?? null, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1949
|
+
}, children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, productId: productId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), _jsx(Dialog, { open: overzicht !== null, onOpenChange: (o) => !o && setOverzicht(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Naar Floriday sturen" }), _jsx(DialogDescription, { children: "Dit gaat naar Floriday. Er verandert niets tot je bevestigt." })] }), _jsxs("div", { className: "space-y-2 text-sm", children: [overzicht && overzicht.wijzigingen.length > 0 ? (_jsx("ul", { className: "list-disc pl-5", children: overzicht.wijzigingen.map((w) => _jsx("li", { children: w }, w)) })) : (_jsx("p", { className: "text-muted-foreground", children: "Geen gewijzigde onderdelen sinds de laatste keer; de huidige gegevens worden opnieuw verstuurd." })), overzicht && overzicht.fotos > 0 && _jsxs("p", { children: [overzicht.fotos, " nieuwe foto('s) worden ge\u00FCpload."] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => setOverzicht(null), children: "Annuleren" }), _jsx(Button, { onClick: () => { const r = overzicht?.run; setOverzicht(null); r?.(); }, children: "Versturen" })] })] }) }), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(FloridayStockReasonLine, { productId: linkedProductId ?? productId ?? null }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: () => metOverzicht(() => void retrySync(), true), children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [editTarget.text, " Je kunt vrij tussen alle secties navigeren."] })] }), _jsxs(Button, { onClick: () => metOverzicht(() => void submit(), wantsPublish), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => metOverzicht(() => void submit(), wantsPublish), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && !embedded && (_jsxs(_Fragment, { children: [_jsx(ProductStructureCard, { productId: linkedProductId ?? productId ?? null }), _jsx(ArtikelCompositionCard, { productId: linkedProductId ?? productId ?? null, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1929
1950
|
name: data.name || data.vbnName,
|
|
1930
1951
|
articleNumber: data.articleCode,
|
|
1931
1952
|
gtin: data.gtin,
|
|
@@ -1953,7 +1974,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1953
1974
|
? "border-destructive bg-destructive text-destructive-foreground"
|
|
1954
1975
|
: "border-muted-foreground/30 text-muted-foreground"}`, children: status === "valid" ? (_jsx(Check, { className: "h-4 w-4" })) : status === "invalid" ? ("!") : (i + 1) }), _jsx("span", { className: `min-w-0 truncate font-medium ${status === "untouched" ? "text-muted-foreground" : ""}`, children: s.label }), !isOpen && summary && (_jsx("span", { className: `max-w-xs truncate text-xs ${status === "invalid" ? "text-destructive" : "text-muted-foreground"}`, children: status === "invalid" ? `Onvolledig — ${summary}` : summary }))] }), isOpen && (_jsxs(CardContent, { className: "space-y-4 pt-0", children: [_jsx("div", { "data-wizard-form-content": true, className: "w-full min-w-0", children: _jsx(StepBody, { stepKey: s.key, data: data, packingStatus: packingStatus, update: update, vbnProducts: vbnProducts, featureTypes: featureTypes, packagingTypes: packagingTypes, loadCarriers: loadCarriers, floridayCustomers: floridayCustomers, schemaQuery: schemaQuery, regulatoryQuery: regulatoryQuery, spec: spec, collectionsList: collectionsList, setCollectionsList: setCollectionsList, activeId: activeId, acceptedVbnCodes: acceptedVbnCodes, priceGroups: priceGroups, parentProductId: linkedProductId ?? draftId ?? null, parentFloridayId: draftFloridayId ?? tradeItemId ?? null, articleCodeLocked: Boolean(draftFloridayId || tradeItemId), botanicalDraft: botanicalDraft, setBotanicalDraft: setBotanicalDraft }) }), isLastStep && !isEditingPublished && (_jsxs("div", { className: "rounded-lg border bg-muted/30 p-4 flex items-start gap-3", children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-7 w-7 shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Label, { htmlFor: "publish-to-floriday", className: "font-medium cursor-pointer", children: "Publiceren naar Floriday" }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: publishToFloriday
|
|
1955
1976
|
? "Het artikel wordt aangemaakt in Floriday en is binnen enkele minuten zichtbaar voor klanten."
|
|
1956
|
-
: "Het artikel wordt alleen lokaal opgeslagen. Je kunt het later vanuit de voorraadlijst alsnog publiceren naar Floriday." })] }), _jsx(Switch, { id: "publish-to-floriday", checked: publishToFloriday, onCheckedChange: setPublishToFloriday })] })), (isLastStep || isEditing) && (_jsx(PublishReadinessCard, { report: readiness, loading: connLoading || connectionReadiness.isLoading || schemaQuery.isLoading, onGoToStep: goToStepKey })), publishJob && (_jsx(PublishProgressCard, { job: publishJob, steps: publishSteps, retrying: submitting, onRetry: () => void submit({ resumeJobId: publishJob.id }), onFixStep: (key) => goToStepKey(key === "trade-item" ? "eigenschappen" : "aanbod") })), (isLastStep || isEditing) && invalidStepCount > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive flex items-center justify-between gap-3", children: [_jsxs("span", { children: ["Nog ", invalidStepCount, " stap", invalidStepCount === 1 ? "" : "pen", " onvolledig"] }), firstInvalidIndex >= 0 && (_jsxs("button", { type: "button", onClick: () => setStep(firstInvalidIndex), className: "underline font-medium", children: ["Ga naar ", steps[firstInvalidIndex].label] }))] })), _jsx("div", { className: "flex justify-end pt-2", children: isLastStep || isEditing ? (_jsxs(Button, { onClick: () => void submit(), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), isEditingPublished
|
|
1977
|
+
: "Het artikel wordt alleen lokaal opgeslagen. Je kunt het later vanuit de voorraadlijst alsnog publiceren naar Floriday." })] }), _jsx(Switch, { id: "publish-to-floriday", checked: publishToFloriday, onCheckedChange: setPublishToFloriday })] })), (isLastStep || isEditing) && (_jsx(PublishReadinessCard, { report: readiness, loading: connLoading || connectionReadiness.isLoading || schemaQuery.isLoading, onGoToStep: goToStepKey })), publishJob && (_jsx(PublishProgressCard, { job: publishJob, steps: publishSteps, retrying: submitting, onRetry: () => metOverzicht(() => void submit({ resumeJobId: publishJob.id }), true), onFixStep: (key) => goToStepKey(key === "trade-item" ? "eigenschappen" : "aanbod") })), (isLastStep || isEditing) && invalidStepCount > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive flex items-center justify-between gap-3", children: [_jsxs("span", { children: ["Nog ", invalidStepCount, " stap", invalidStepCount === 1 ? "" : "pen", " onvolledig"] }), firstInvalidIndex >= 0 && (_jsxs("button", { type: "button", onClick: () => setStep(firstInvalidIndex), className: "underline font-medium", children: ["Ga naar ", steps[firstInvalidIndex].label] }))] })), _jsx("div", { className: "flex justify-end pt-2", children: isLastStep || isEditing ? (_jsxs(Button, { onClick: () => metOverzicht(() => void submit(), wantsPublish), disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), isEditingPublished
|
|
1957
1978
|
? "Wijzigingen opslaan"
|
|
1958
1979
|
: publishToFloriday
|
|
1959
1980
|
? isEditingDraft
|
|
@@ -1963,10 +1984,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1963
1984
|
? "Wijzigingen opslaan"
|
|
1964
1985
|
: "Artikel aanmaken"] })) : ((() => {
|
|
1965
1986
|
const pendingBotanical = s.key === "paspoort" && botanicalDraft.trim() !== "";
|
|
1966
|
-
const canGo = isValid ||
|
|
1987
|
+
const canGo = isValid || gateValidity[i] || pendingBotanical;
|
|
1967
1988
|
const res = canGo ? null : validateStepLocal(s.key, data);
|
|
1968
|
-
|
|
1969
|
-
|
|
1989
|
+
// Alleen wat echt ontbreekt, zonder herhalingen.
|
|
1990
|
+
const reasons = res && !res.ok ? Array.from(new Set(res.errors)) : [];
|
|
1991
|
+
return (_jsxs("div", { className: "flex flex-col items-end gap-1", children: [_jsxs(Button, { onClick: next, disabled: !canGo, children: ["Volgende stap", _jsx(ArrowRight, { className: "ml-2 h-4 w-4" })] }), !canGo && reasons.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground text-right", children: ["Nog nodig: ", reasons.join(" · ")] })), s.key === "fotos" && !isValid && !lokaalAlleen && (_jsx("p", { className: "text-xs text-muted-foreground text-right", children: "Je kunt verder zonder foto. Voor publiceren naar Floriday is minstens \u00E9\u00E9n foto nodig." }))] }));
|
|
1970
1992
|
})()) })] }))] }) }, s.key));
|
|
1971
1993
|
}) }), _jsx(AlertDialog, { open: Boolean(conflict), onOpenChange: (open) => {
|
|
1972
1994
|
if (!open)
|
|
@@ -2566,10 +2588,6 @@ function StepFotos({ data, update, activeId }) {
|
|
|
2566
2588
|
const onFiles = async (files) => {
|
|
2567
2589
|
if (!files || files.length === 0)
|
|
2568
2590
|
return;
|
|
2569
|
-
if (!activeId) {
|
|
2570
|
-
toast.error("Geen actieve Floriday-koppeling – foto's kunnen niet worden geüpload");
|
|
2571
|
-
return;
|
|
2572
|
-
}
|
|
2573
2591
|
const fileArr = Array.from(files);
|
|
2574
2592
|
setUploading(true);
|
|
2575
2593
|
setUploadError(null);
|
|
@@ -2617,27 +2635,14 @@ function StepFotos({ data, update, activeId }) {
|
|
|
2617
2635
|
if (storageErr) {
|
|
2618
2636
|
console.warn("[upload] Supabase storage mislukt", f.name, storageErr);
|
|
2619
2637
|
}
|
|
2620
|
-
// 3)
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
url: res.url,
|
|
2629
|
-
previewUrl,
|
|
2630
|
-
imageId: res.imageId ?? null,
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
catch (e) {
|
|
2634
|
-
const msg = e instanceof Error && e.message
|
|
2635
|
-
? e.message
|
|
2636
|
-
: "Foto kon niet worden geüpload.";
|
|
2637
|
-
console.error("[StepFotos] upload mislukt", { filename: f.name, message: msg, error: e });
|
|
2638
|
-
setUploadError({ message: `${f.name}: ${msg}`, detail: e?.adminDetail ?? null });
|
|
2639
|
-
toast.error(`${f.name} — ${msg}`);
|
|
2640
|
-
}
|
|
2638
|
+
// 3) Niet naar Floriday bij het kiezen: alleen een tijdelijke verwijzing.
|
|
2639
|
+
// Uploaden gebeurt pas bij "Naar Floriday sturen" (na het overzicht).
|
|
2640
|
+
additions.push({
|
|
2641
|
+
name: f.name,
|
|
2642
|
+
url: registerPendingPhoto(f),
|
|
2643
|
+
previewUrl: URL.createObjectURL(f),
|
|
2644
|
+
imageId: null,
|
|
2645
|
+
});
|
|
2641
2646
|
}
|
|
2642
2647
|
};
|
|
2643
2648
|
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
|
|
@@ -2868,19 +2873,18 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
2868
2873
|
(draft.source === "VBN" || Boolean(draft.packagingSupplierId)) &&
|
|
2869
2874
|
(!draft.isCustomerSpecific || draft.customerOrganizationIds.length > 0);
|
|
2870
2875
|
const onUploadPhoto = async (file) => {
|
|
2871
|
-
if (!file
|
|
2876
|
+
if (!file)
|
|
2872
2877
|
return;
|
|
2873
2878
|
setUploadingPhoto(true);
|
|
2874
2879
|
try {
|
|
2875
2880
|
const { prepareFloridayImage } = await import("../../lib/image-resize.js");
|
|
2876
2881
|
const prepared = await prepareFloridayImage(file);
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
throw new Error("Geen URL ontvangen van Floriday");
|
|
2882
|
+
// Niet naar Floriday bij het kiezen; uploaden gebeurt pas bij versturen.
|
|
2883
|
+
const pendingUrl = registerPendingPhoto(prepared);
|
|
2880
2884
|
setDraft((d) => ({
|
|
2881
2885
|
...d,
|
|
2882
|
-
photoUrl:
|
|
2883
|
-
photoImageId:
|
|
2886
|
+
photoUrl: pendingUrl,
|
|
2887
|
+
photoImageId: undefined,
|
|
2884
2888
|
photoPreviewUrl: URL.createObjectURL(prepared),
|
|
2885
2889
|
}));
|
|
2886
2890
|
toast.success("Foto toegevoegd aan belading");
|
|
@@ -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,+BA4EA"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } 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.js";
|
|
6
|
-
import {
|
|
7
|
-
import { TrayStickerCard } from "./TrayStickerCard.js";
|
|
6
|
+
import { ArtikelPagina } from "./artikel/ArtikelPagina.js";
|
|
8
7
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
9
8
|
export function EditArtikelPage({ id, resumeJobId, }) {
|
|
10
9
|
const [resolved, setResolved] = useState(null);
|
|
@@ -64,7 +63,7 @@ export function EditArtikelPage({ id, resumeJobId, }) {
|
|
|
64
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 }))] }) }));
|
|
65
64
|
}
|
|
66
65
|
if (resolved.kind === "product") {
|
|
67
|
-
return
|
|
66
|
+
return _jsx(ArtikelPagina, { id: resolved.id, resumeJobId: resumeJobId });
|
|
68
67
|
}
|
|
69
68
|
return _jsx(ArtikelWizard, { tradeItemId: resolved.id, resumeJobId: resumeJobId });
|
|
70
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductCustomFieldsCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductCustomFieldsCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductCustomFieldsCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductCustomFieldsCard.tsx"],"names":[],"mappings":"AAuBA,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCA+I3E"}
|
|
@@ -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
|
}
|
|
@@ -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"}
|