@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
|
@@ -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,11 +1946,11 @@ 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,
|
|
1932
|
-
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId ?? productId ?? null }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? productId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? productId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1953
|
+
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId ?? productId ?? null }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? productId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? productId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1933
1954
|
const isOpen = i === step;
|
|
1934
1955
|
const isValid = stepValidity[i];
|
|
1935
1956
|
const isVisited = i < step || isEditing;
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FustType } from "../../lib/fusten.js";
|
|
2
|
+
/** Startmoment en beginstand: boekingen vóór dit moment tellen niet mee in het saldo, maar blijven zichtbaar. */
|
|
3
|
+
export declare function FustStartmomentCard({ items, onSaved }: {
|
|
4
|
+
items: FustType[];
|
|
5
|
+
onSaved: () => void;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
/** Per fust: saldo bijhouden en eigen naam. */
|
|
8
|
+
export declare function FustInstellingenCell({ fust, onSaved }: {
|
|
9
|
+
fust: FustType;
|
|
10
|
+
onSaved: () => void;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=FustAdministratie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FustAdministratie.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustAdministratie.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BA8EjG;AAED,+CAA+C;AAC/C,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BAiD9F"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Button, Card, Input, toast } from "@flowselections/core";
|
|
4
|
+
import { fetchFustStartmomenten, fustNaam, saldoBijhouden, saveFustInstellingen, saveFustStartmoment, } from "../../lib/fust-administratie.js";
|
|
5
|
+
function lokaleInvoer(d) {
|
|
6
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
7
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
8
|
+
}
|
|
9
|
+
/** Startmoment en beginstand: boekingen vóór dit moment tellen niet mee in het saldo, maar blijven zichtbaar. */
|
|
10
|
+
export function FustStartmomentCard({ items, onSaved }) {
|
|
11
|
+
const [rows, setRows] = useState(null);
|
|
12
|
+
const [fustId, setFustId] = useState("");
|
|
13
|
+
const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
|
|
14
|
+
const [beginstand, setBeginstand] = useState("0");
|
|
15
|
+
const [busy, setBusy] = useState(false);
|
|
16
|
+
const load = () => fetchFustStartmomenten().then(setRows).catch(() => setRows([]));
|
|
17
|
+
useEffect(() => { void load(); }, []);
|
|
18
|
+
const algemeen = rows?.find((r) => r.fust_type_id === null) ?? null;
|
|
19
|
+
const perFust = (rows ?? []).filter((r) => r.fust_type_id !== null);
|
|
20
|
+
const opslaan = async () => {
|
|
21
|
+
const n = Number(beginstand);
|
|
22
|
+
if (!Number.isFinite(n)) {
|
|
23
|
+
toast.error("Vul een geldige beginstand in.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const d = new Date(moment);
|
|
27
|
+
if (Number.isNaN(d.getTime())) {
|
|
28
|
+
toast.error("Vul een geldig startmoment in.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
setBusy(true);
|
|
32
|
+
try {
|
|
33
|
+
await saveFustStartmoment(fustId || null, d, n);
|
|
34
|
+
toast.success("Startmoment opgeslagen; het saldo is opnieuw berekend.");
|
|
35
|
+
await load();
|
|
36
|
+
onSaved();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
toast.error(e.message);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setBusy(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Startmoment fustenadministratie" }), rows !== null && !algemeen && perFust.length === 0 ? (_jsx("p", { className: "text-sm text-amber-700", children: "Er is nog geen startmoment. Stel een startmoment en beginstand in; tot die tijd telt het saldo alle boekingen uit het verleden mee." })) : (_jsxs("p", { className: "text-sm text-muted-foreground", children: [algemeen
|
|
46
|
+
? `Alle fusten: vanaf ${new Date(algemeen.moment).toLocaleString("nl-NL")}${algemeen.automatisch ? " (automatisch bij de eerste order)" : ""}.`
|
|
47
|
+
: "Geen algemeen startmoment.", perFust.length > 0 && ` ${perFust.length} fust${perFust.length === 1 ? "" : "en"} met een eigen startmoment.`, " ", "Boekingen v\u00F3\u00F3r het startmoment blijven zichtbaar in de historie maar tellen niet mee."] }))] }), _jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [_jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Voor" }), _jsxs("select", { className: "h-9 rounded-md border border-input bg-background px-2 text-sm", value: fustId, onChange: (e) => setFustId(e.target.value), children: [_jsx("option", { value: "", children: "Alle fusten" }), items.map((f) => (_jsx("option", { value: f.id, children: fustNaam(f) }, f.id)))] })] }), _jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Startmoment" }), _jsx(Input, { type: "datetime-local", className: "h-9 w-52", value: moment, onChange: (e) => setMoment(e.target.value) })] }), _jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsxs("span", { className: "block", children: ["Beginstand", fustId ? "" : " (per fust)"] }), _jsx(Input, { type: "number", className: "h-9 w-28 text-right", value: beginstand, onChange: (e) => setBeginstand(e.target.value) })] }), _jsx(Button, { size: "sm", onClick: () => void opslaan(), disabled: busy, children: busy ? "Opslaan…" : "Startmoment opslaan" })] })] }));
|
|
48
|
+
}
|
|
49
|
+
/** Per fust: saldo bijhouden en eigen naam. */
|
|
50
|
+
export function FustInstellingenCell({ fust, onSaved }) {
|
|
51
|
+
const [open, setOpen] = useState(false);
|
|
52
|
+
const [naam, setNaam] = useState(fust.eigen_naam ?? "");
|
|
53
|
+
const [busy, setBusy] = useState(false);
|
|
54
|
+
const bijhouden = saldoBijhouden(fust);
|
|
55
|
+
const opslaan = async (saldo, eigenNaam) => {
|
|
56
|
+
setBusy(true);
|
|
57
|
+
try {
|
|
58
|
+
await saveFustInstellingen(fust.id, saldo, eigenNaam);
|
|
59
|
+
onSaved();
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
toast.error(e.message);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
setBusy(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return (_jsxs("div", { className: "mt-1 space-y-1 text-xs", children: [_jsxs("label", { className: "flex items-center gap-1 text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: bijhouden, disabled: busy, onChange: (e) => void opslaan(e.target.checked, fust.eigen_naam ?? null) }), "Saldo bijhouden", fust.saldo_bijhouden == null ? " (standaard)" : ""] }), !open ? (_jsx("button", { type: "button", className: "text-primary hover:underline", onClick: () => setOpen(true), children: fust.eigen_naam ? "Eigen naam wijzigen" : "Eigen naam geven" })) : (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-7 w-40", value: naam, placeholder: fust.name, onChange: (e) => setNaam(e.target.value) }), _jsx(Button, { size: "sm", className: "h-7", disabled: busy, onClick: async () => { await opslaan(fust.saldo_bijhouden ?? null, naam); setOpen(false); }, children: "Opslaan" }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-7", onClick: () => setOpen(false), children: "Annuleren" })] }))] }));
|
|
69
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAmLA,wBAAgB,UAAU,gCA0PzB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Boxes, Minus, Plus, RefreshCw, Search } from "lucide-react";
|
|
4
4
|
import { Button, Card, Input, Badge, toast } from "@flowselections/core";
|
|
5
|
-
import {
|
|
5
|
+
import { FustStartmomentCard, FustInstellingenCell } from "./FustAdministratie.js";
|
|
6
|
+
import { fustNaam, fustTekorten, saldoBijhouden } from "../../lib/fust-administratie.js";
|
|
6
7
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
7
8
|
import { useFusten } from "../../hooks/useFusten.js";
|
|
8
9
|
import { fetchFloricodeFustMap, fetchFloridayCustomPackageMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
|
|
@@ -61,7 +62,9 @@ function MutationHistory({ fust }) {
|
|
|
61
62
|
? "Order geannuleerd"
|
|
62
63
|
: m.reason === "manual_sale"
|
|
63
64
|
? "Handmatige verkoop"
|
|
64
|
-
:
|
|
65
|
+
: m.reason === "startmoment"
|
|
66
|
+
? "Startmoment / beginstand"
|
|
67
|
+
: "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
|
|
65
68
|
}
|
|
66
69
|
export function FustenPage() {
|
|
67
70
|
const { activeId } = useFloridayConnection();
|
|
@@ -80,14 +83,7 @@ export function FustenPage() {
|
|
|
80
83
|
const [customPackages, setCustomPackages] = useState(null);
|
|
81
84
|
const [busyId, setBusyId] = useState(null);
|
|
82
85
|
const [amounts, setAmounts] = useState({});
|
|
83
|
-
const
|
|
84
|
-
const loadNegative = useCallback(async () => {
|
|
85
|
-
const { data } = await supabase.rpc("fust_negative_stock_report");
|
|
86
|
-
setNegative(Array.isArray(data) ? data : []);
|
|
87
|
-
}, []);
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
void loadNegative();
|
|
90
|
-
}, [loadNegative, items]);
|
|
86
|
+
const negative = useMemo(() => fustTekorten(items), [items]);
|
|
91
87
|
const filtered = useMemo(() => {
|
|
92
88
|
const q = search.trim().toLowerCase();
|
|
93
89
|
if (!q)
|
|
@@ -107,7 +103,7 @@ export function FustenPage() {
|
|
|
107
103
|
return;
|
|
108
104
|
// Waarschuwen (niet blokkeren) bij afboeken boven de voorraad.
|
|
109
105
|
if (sign < 0 && f.stock - step < 0) {
|
|
110
|
-
toast.warning(`Tekort van ${Math.abs(f.stock - step).toLocaleString("nl-NL")} fusten na deze afboeking (${f
|
|
106
|
+
toast.warning(`Tekort van ${Math.abs(f.stock - step).toLocaleString("nl-NL")} fusten na deze afboeking (${fustNaam(f)}).`);
|
|
111
107
|
}
|
|
112
108
|
setBusyId(f.id);
|
|
113
109
|
setAmounts((a) => ({ ...a, [f.id]: "" }));
|
|
@@ -116,10 +112,9 @@ export function FustenPage() {
|
|
|
116
112
|
}
|
|
117
113
|
finally {
|
|
118
114
|
setBusyId(null);
|
|
119
|
-
void loadNegative();
|
|
120
115
|
}
|
|
121
116
|
};
|
|
122
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer
|
|
117
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is sinds het startmoment meer uitgeleverd dan ingeboekt. Alleen fusten waarvan je het saldo bijhoudt staan hier. Controleer ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [fustNaam(n), ": tekort van ", Math.abs(n.stock).toLocaleString("nl-NL"), " fusten"] }, n.id))) })] })), _jsx(FustStartmomentCard, { items: items, onSaved: () => void sync() }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: fustNaam(f) }), f.eigen_naam?.trim() && _jsx("div", { className: "text-xs text-muted-foreground", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx(FustInstellingenCell, { fust: f, onSaved: () => void sync() }), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, floricode: floricode, customPackages: customPackages, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 && saldoBijhouden(f) ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
|
|
123
118
|
? "text-rose-600"
|
|
124
119
|
: ""}`, children: f.stock })) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
|
|
125
120
|
const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Automatisch aanmaken van Floriday-artikelen: wat nog open staat en wat handmatig moet. */
|
|
2
|
+
export declare function KoppelingStatusPanel({ connectionId, onChanged }: {
|
|
3
|
+
connectionId: string | null;
|
|
4
|
+
onChanged?: () => void;
|
|
5
|
+
}): import("react").JSX.Element | null;
|
|
6
|
+
//# sourceMappingURL=KoppelingStatusPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KoppelingStatusPanel.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/KoppelingStatusPanel.tsx"],"names":[],"mappings":"AAYA,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,sCAqExH"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { Button, Card, toast } from "@flowselections/core";
|
|
4
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
5
|
+
/** Automatisch aanmaken van Floriday-artikelen: wat nog open staat en wat handmatig moet. */
|
|
6
|
+
export function KoppelingStatusPanel({ connectionId, onChanged }) {
|
|
7
|
+
const [check, setCheck] = useState(null);
|
|
8
|
+
const [twijfel, setTwijfel] = useState([]);
|
|
9
|
+
const [busy, setBusy] = useState(false);
|
|
10
|
+
const load = useCallback(async () => {
|
|
11
|
+
const [c, t] = await Promise.all([
|
|
12
|
+
supabase.rpc("voorraad_koppeling_check"),
|
|
13
|
+
supabase.from("voorraad_koppel_twijfel").select("floriday_id, naam, reden").is("opgelost_at", null).order("laatst_gezien", { ascending: false }).limit(50),
|
|
14
|
+
]);
|
|
15
|
+
if (c.error)
|
|
16
|
+
toast.error(c.error.message);
|
|
17
|
+
else
|
|
18
|
+
setCheck(c.data);
|
|
19
|
+
setTwijfel((t.data ?? []));
|
|
20
|
+
}, []);
|
|
21
|
+
useEffect(() => { void load(); }, [load]);
|
|
22
|
+
const nuControleren = async () => {
|
|
23
|
+
setBusy(true);
|
|
24
|
+
try {
|
|
25
|
+
const { data, error } = await supabase.rpc("voorraad_floriday_artikelen_aanmaken", {
|
|
26
|
+
p_connection_id: connectionId, p_limiet: 200, p_uitvoeren: true, p_max_seconden: 20,
|
|
27
|
+
});
|
|
28
|
+
if (error)
|
|
29
|
+
throw new Error(error.message);
|
|
30
|
+
toast.success(`${data?.aangemaakt ?? 0} aangemaakt, ${data?.gekoppeld ?? 0} gekoppeld, ${data?.twijfel ?? 0} twijfelgevallen${data?.resterend ? `, ${data.resterend} nog te doen` : ""}.`);
|
|
31
|
+
await load();
|
|
32
|
+
onChanged?.();
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
toast.error(e.message);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
setBusy(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
if (!check)
|
|
42
|
+
return null;
|
|
43
|
+
const niets = check.fouten.length === 0 && check.floriday_zonder_eigen_artikel === 0 && twijfel.length === 0;
|
|
44
|
+
return (_jsxs(Card, { className: "p-4 space-y-2 text-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Floriday-artikelen automatisch overnemen" }), _jsxs("p", { className: "text-muted-foreground", children: [niets
|
|
45
|
+
? "Alle Floriday-artikelen hebben een eigen artikel."
|
|
46
|
+
: `${check.floriday_zonder_eigen_artikel} Floriday-artikelen zonder eigen artikel, ${twijfel.length} twijfelgevallen.`, " ", "Nieuwe artikelen starten met voorraad 0 tot je telt of importeert."] })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void nuControleren(), disabled: busy, children: busy ? "Bezig…" : "Nu controleren" })] }), check.fouten.map((f) => (_jsx("p", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-2 text-destructive", children: f }, f))), twijfel.length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Kies zelf het juiste artikel" }), _jsx("ul", { className: "mt-1 divide-y divide-border rounded-md border border-border", children: twijfel.map((t) => (_jsxs("li", { className: "px-3 py-1.5", children: [_jsx("span", { className: "font-medium", children: t.naam ?? t.floriday_id }), _jsxs("span", { className: "text-muted-foreground", children: [" \u2014 ", t.reden] })] }, t.floriday_id))) })] }))] }));
|
|
47
|
+
}
|
|
@@ -64,7 +64,7 @@ export function NieuwArtikelPage() {
|
|
|
64
64
|
void (async () => {
|
|
65
65
|
const { data } = await supabase
|
|
66
66
|
.from("fust_types")
|
|
67
|
-
.select("name, vbn_code")
|
|
67
|
+
.select("name, eigen_naam, vbn_code")
|
|
68
68
|
.is("merged_into", null)
|
|
69
69
|
.not("vbn_code", "is", null)
|
|
70
70
|
.order("name");
|
|
@@ -76,7 +76,7 @@ export function NieuwArtikelPage() {
|
|
|
76
76
|
const v = Number(r.vbn_code);
|
|
77
77
|
if (v > 0 && !seen.has(v)) {
|
|
78
78
|
seen.add(v);
|
|
79
|
-
list.push({ vbn: v, name: String(r.name
|
|
79
|
+
list.push({ vbn: v, name: String((r.eigen_naam && String(r.eigen_naam).trim()) || r.name || `VBN ${v}`) });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
setFusts(list);
|
|
@@ -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"}
|