@flowselections/floriday-voorraad 1.0.15 → 1.0.17
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/AdditionalPackagingDialog.d.ts +9 -0
- package/dist-lib/components/voorraad/AdditionalPackagingDialog.d.ts.map +1 -0
- package/dist-lib/components/voorraad/AdditionalPackagingDialog.js +51 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +411 -28
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +78 -4
- package/dist-lib/components/voorraad/ExcelExportButton.d.ts +2 -1
- package/dist-lib/components/voorraad/ExcelExportButton.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ExcelExportButton.js +24 -5
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +8 -0
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/OrderHistoryCard.js +133 -0
- package/dist-lib/components/voorraad/PriceStatusBadge.d.ts +7 -0
- package/dist-lib/components/voorraad/PriceStatusBadge.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PriceStatusBadge.js +67 -0
- package/dist-lib/components/voorraad/ProductLinkCard.d.ts +13 -0
- package/dist-lib/components/voorraad/ProductLinkCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ProductLinkCard.js +159 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockBulkButtons.js +88 -7
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +2 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +5 -4
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +6 -1
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +4 -0
- package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemSpec.js +4 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts +14 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +76 -17
- package/dist-lib/hooks/useTradeItemSupplyLine.d.ts +21 -0
- package/dist-lib/hooks/useTradeItemSupplyLine.d.ts.map +1 -0
- package/dist-lib/hooks/useTradeItemSupplyLine.js +49 -0
- package/dist-lib/hooks/useVoorraadData.d.ts +5 -4
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +73 -7
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1307 -94
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1307 -94
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1307 -94
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1328 -87
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/ensure-article-codes.d.ts +8 -0
- package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -0
- package/dist-lib/lib/ensure-article-codes.js +78 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts +1313 -94
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.js +20 -2
- package/dist-lib/lib/floriday-client.d.ts +6 -0
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +11264 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-customer-offer.functions.js +175 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts +16129 -17
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.js +150 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts +22 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.js +3 -0
- package/dist-lib/lib/floriday-payload.d.ts +76 -8
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +209 -39
- package/dist-lib/lib/floriday-supply-line.d.ts +53 -0
- package/dist-lib/lib/floriday-supply-line.d.ts.map +1 -0
- package/dist-lib/lib/floriday-supply-line.js +109 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +33638 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-warehouse.functions.js +98 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +2707 -281
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.js +39 -6
- package/dist-lib/lib/vrs-catalog.d.ts +27 -0
- package/dist-lib/lib/vrs-catalog.d.ts.map +1 -0
- package/dist-lib/lib/vrs-catalog.js +98 -0
- package/package.json +3 -2
- package/public/flowselections-assets/Voorraad/floriday-logo.png +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
4
4
|
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";
|
|
5
5
|
import { ArrowLeft, ArrowRight, Check, ChevronDown, ClipboardList, Info, Loader2, Plus, Search, Trash2, Upload, UploadCloud, Users, X, } from "lucide-react";
|
|
@@ -8,14 +8,29 @@ import { useFlorydayCustomers } from "../../hooks/useFlorydayCustomers";
|
|
|
8
8
|
import { floriday } from "../../lib/floriday-client";
|
|
9
9
|
import { listFloricode } from "../../lib/floricode-client";
|
|
10
10
|
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
-
import { buildAddTradeItemPayload, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
11
|
+
import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, buildPrimaryPackingBase, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
12
|
+
import { AdditionalPackagingDialog } from "./AdditionalPackagingDialog";
|
|
13
|
+
import { publishCustomerOfferFn } from "../../lib/floriday-customer-offer.functions";
|
|
14
|
+
import { upsertSupplyLineForTradeItem } from "../../lib/floriday-supply-line";
|
|
12
15
|
import { deleteLocalTradeItem, getLocalTradeItem, upsertLocalTradeItem, } from "../../hooks/useLocalTradeItems";
|
|
13
16
|
import { useTradeItemSpec } from "../../hooks/useTradeItemSpec";
|
|
14
17
|
import { useVbnRegulatoryFeatures } from "../../hooks/useVbnRegulatoryFeatures";
|
|
15
18
|
import { useCodeList, useTradeItemFormSchema } from "../../hooks/useFloriday";
|
|
16
|
-
|
|
19
|
+
const floridayLogo = "/flowselections-assets/Voorraad/floriday-logo.png";
|
|
17
20
|
import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
|
|
21
|
+
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
22
|
+
import { PriceStatusBadge } from "../../components/voorraad/PriceStatusBadge";
|
|
23
|
+
import { OrderHistoryCard } from "../../components/voorraad/OrderHistoryCard";
|
|
24
|
+
import { stockKeyFor } from "../../hooks/useTradeItemStock";
|
|
18
25
|
const empty = emptyWizardData;
|
|
26
|
+
const LOAD_CARRIER_LABELS = {
|
|
27
|
+
AUCTION_TROLLEY: "Stapelwagen",
|
|
28
|
+
DANISH_TROLLEY: "Deense container",
|
|
29
|
+
EURO_TROLLEY: "Euro-rolcontainer",
|
|
30
|
+
PALLET: "Pallet",
|
|
31
|
+
EURO_PALLET: "Euro-pallet",
|
|
32
|
+
NONE: "Geen ladingdrager",
|
|
33
|
+
};
|
|
19
34
|
const STEPS = [
|
|
20
35
|
{ key: "vbn", label: "VBN-code" },
|
|
21
36
|
{ key: "artikel", label: "Artikel" },
|
|
@@ -23,10 +38,12 @@ const STEPS = [
|
|
|
23
38
|
{ key: "eigenschappen", label: "Eigenschappen" },
|
|
24
39
|
{ key: "klanten", label: "Klanten" },
|
|
25
40
|
{ key: "fotos", label: "Foto's" },
|
|
41
|
+
{ key: "warehouse", label: "Warehouse" },
|
|
26
42
|
{ key: "beladingen", label: "Beladingen" },
|
|
27
43
|
{ key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
|
|
28
44
|
{ key: "samenstelling", label: "Samenstelling", disabled: true },
|
|
29
45
|
{ key: "beschikbaarheid", label: "Verwachte beschikbaarheid" },
|
|
46
|
+
{ key: "aanbieden", label: "Aanbieden & voorraad" },
|
|
30
47
|
];
|
|
31
48
|
function visibleSteps(d) {
|
|
32
49
|
return STEPS.filter((s) => {
|
|
@@ -36,6 +53,20 @@ function visibleSteps(d) {
|
|
|
36
53
|
return true;
|
|
37
54
|
});
|
|
38
55
|
}
|
|
56
|
+
function warehouseDisplayLabel(raw, fallback) {
|
|
57
|
+
const addr = raw?.location?.address ?? raw?.address ?? {};
|
|
58
|
+
const line = addr.addressLine ?? [addr.street, addr.houseNumber].filter(Boolean).join(" ");
|
|
59
|
+
const city = addr.city ?? "";
|
|
60
|
+
return ((raw?.name && String(raw.name).trim()) ||
|
|
61
|
+
[line, city].filter(Boolean).join(", ") ||
|
|
62
|
+
fallback);
|
|
63
|
+
}
|
|
64
|
+
function warehouseSubLabel(raw) {
|
|
65
|
+
const addr = raw?.location?.address ?? raw?.address ?? {};
|
|
66
|
+
const line = addr.addressLine ?? [addr.street, addr.houseNumber].filter(Boolean).join(" ");
|
|
67
|
+
const city = addr.city ?? "";
|
|
68
|
+
return [line, city].filter(Boolean).join(", ") || undefined;
|
|
69
|
+
}
|
|
39
70
|
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
40
71
|
const navigate = useNavigate();
|
|
41
72
|
const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
|
|
@@ -45,11 +76,43 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
45
76
|
const [submitting, setSubmitting] = useState(false);
|
|
46
77
|
const [publishToFloriday, setPublishToFloriday] = useState(true);
|
|
47
78
|
const [draftId, setDraftId] = useState(initialDraftId);
|
|
79
|
+
const [draftFloridayId, setDraftFloridayId] = useState(null);
|
|
48
80
|
const [draftLoading, setDraftLoading] = useState(Boolean(initialDraftId));
|
|
49
81
|
const [publishedLoading, setPublishedLoading] = useState(Boolean(tradeItemId));
|
|
82
|
+
const [syncState, setSyncState] = useState("idle");
|
|
83
|
+
const [syncError, setSyncError] = useState(null);
|
|
50
84
|
const isEditingDraft = Boolean(initialDraftId);
|
|
51
85
|
const isEditingPublished = Boolean(tradeItemId);
|
|
52
86
|
const isEditing = isEditingDraft || isEditingPublished;
|
|
87
|
+
const saveWarehousePreference = useCallback(async (targetTradeItemId, warehouseId) => {
|
|
88
|
+
if (!activeId || !targetTradeItemId || !warehouseId)
|
|
89
|
+
return;
|
|
90
|
+
let warehouseName = null;
|
|
91
|
+
try {
|
|
92
|
+
const { data: warehouseRow } = await supabase
|
|
93
|
+
.from("floriday_warehouses_cache")
|
|
94
|
+
.select("data")
|
|
95
|
+
.eq("connection_id", activeId)
|
|
96
|
+
.eq("floriday_id", warehouseId)
|
|
97
|
+
.maybeSingle();
|
|
98
|
+
if (warehouseRow?.data) {
|
|
99
|
+
warehouseName = warehouseDisplayLabel(warehouseRow.data, warehouseId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Naam is alleen voor herkenbaarheid; de id is leidend.
|
|
104
|
+
}
|
|
105
|
+
const { error } = await supabase
|
|
106
|
+
.from("trade_item_warehouse_preferences")
|
|
107
|
+
.upsert({
|
|
108
|
+
connection_id: activeId,
|
|
109
|
+
trade_item_id: targetTradeItemId,
|
|
110
|
+
warehouse_id: warehouseId,
|
|
111
|
+
warehouse_name: warehouseName,
|
|
112
|
+
}, { onConflict: "connection_id,trade_item_id" });
|
|
113
|
+
if (error)
|
|
114
|
+
throw new Error(`Voorraadlocatie opslaan mislukt: ${error.message}`);
|
|
115
|
+
}, [activeId]);
|
|
53
116
|
// Load existing draft when editing
|
|
54
117
|
useEffect(() => {
|
|
55
118
|
if (!initialDraftId)
|
|
@@ -57,10 +120,23 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
57
120
|
let cancelled = false;
|
|
58
121
|
setDraftLoading(true);
|
|
59
122
|
getLocalTradeItem(initialDraftId)
|
|
60
|
-
.then((row) => {
|
|
123
|
+
.then(async (row) => {
|
|
61
124
|
if (cancelled || !row)
|
|
62
125
|
return;
|
|
63
|
-
|
|
126
|
+
let warehouseId = row.data.warehouseId;
|
|
127
|
+
if (activeId && row.floriday_id) {
|
|
128
|
+
const { data: pref } = await supabase
|
|
129
|
+
.from("trade_item_warehouse_preferences")
|
|
130
|
+
.select("warehouse_id")
|
|
131
|
+
.eq("connection_id", activeId)
|
|
132
|
+
.eq("trade_item_id", row.floriday_id)
|
|
133
|
+
.maybeSingle();
|
|
134
|
+
warehouseId = pref?.warehouse_id ?? warehouseId;
|
|
135
|
+
}
|
|
136
|
+
if (cancelled)
|
|
137
|
+
return;
|
|
138
|
+
setData({ ...empty, ...row.data, warehouseId });
|
|
139
|
+
setDraftFloridayId(row.floriday_id ?? null);
|
|
64
140
|
})
|
|
65
141
|
.catch((e) => toast.error(`Lokaal artikel laden mislukt: ${e.message}`))
|
|
66
142
|
.finally(() => {
|
|
@@ -70,7 +146,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
70
146
|
return () => {
|
|
71
147
|
cancelled = true;
|
|
72
148
|
};
|
|
73
|
-
}, [initialDraftId]);
|
|
149
|
+
}, [initialDraftId, activeId]);
|
|
74
150
|
// Load existing published trade item from the shared cache
|
|
75
151
|
useEffect(() => {
|
|
76
152
|
if (!tradeItemId || !activeId)
|
|
@@ -85,6 +161,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
85
161
|
.eq("connection_id", activeId)
|
|
86
162
|
.eq("floriday_id", tradeItemId)
|
|
87
163
|
.maybeSingle();
|
|
164
|
+
const { data: pref } = await supabase
|
|
165
|
+
.from("trade_item_warehouse_preferences")
|
|
166
|
+
.select("warehouse_id")
|
|
167
|
+
.eq("connection_id", activeId)
|
|
168
|
+
.eq("trade_item_id", tradeItemId)
|
|
169
|
+
.maybeSingle();
|
|
88
170
|
if (cancelled)
|
|
89
171
|
return;
|
|
90
172
|
if (error) {
|
|
@@ -95,7 +177,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
95
177
|
toast.error("Artikel niet gevonden in de cache");
|
|
96
178
|
return;
|
|
97
179
|
}
|
|
98
|
-
|
|
180
|
+
const wizardData = floridayCacheToWizardData(row.data);
|
|
181
|
+
setData({
|
|
182
|
+
...wizardData,
|
|
183
|
+
warehouseId: pref?.warehouse_id ?? wizardData.warehouseId,
|
|
184
|
+
});
|
|
99
185
|
setPublishToFloriday(true);
|
|
100
186
|
}
|
|
101
187
|
catch (e) {
|
|
@@ -120,8 +206,16 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
120
206
|
useEffect(() => {
|
|
121
207
|
// Kleine resources volledig laden
|
|
122
208
|
listFloricode("FeatureType", undefined, 300).then(setFeatureTypes).catch(() => { });
|
|
123
|
-
|
|
124
|
-
|
|
209
|
+
// Fust/VBN-package: Floricode-resource `LoadCarrier` (=/v2/VBN/Packaging, ~560 rijen).
|
|
210
|
+
// `PackagingType` (33 rijen) zijn slechts categorieën (Kraag/Deksel/…) en NIET wat
|
|
211
|
+
// Floriday in `package.vbnPackageCode` valideert — dat leverde 400 "Package type code X
|
|
212
|
+
// not found in floricode data" op. Zelfde lijst voor fust-picker en label-lookup.
|
|
213
|
+
listFloricode("LoadCarrier", undefined, 1000)
|
|
214
|
+
.then((rows) => {
|
|
215
|
+
setLoadCarriers(rows);
|
|
216
|
+
setPackagingTypes(rows);
|
|
217
|
+
})
|
|
218
|
+
.catch(() => { });
|
|
125
219
|
// VbnProduct (51k records) wordt server-side per productgroep geladen in StepVbn.
|
|
126
220
|
supabase
|
|
127
221
|
.from("collections")
|
|
@@ -164,6 +258,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
164
258
|
}
|
|
165
259
|
return set.size ? set : null;
|
|
166
260
|
}, [schemaQuery.data]);
|
|
261
|
+
const linkedProductId = typeof data.linkedProductId === "string" ? data.linkedProductId : null;
|
|
262
|
+
const localTradeItemKey = stockKeyFor({
|
|
263
|
+
tradeItemId: tradeItemId ?? null,
|
|
264
|
+
productId: !tradeItemId ? linkedProductId : null,
|
|
265
|
+
draftId: !tradeItemId && !linkedProductId ? (draftId ?? null) : null,
|
|
266
|
+
});
|
|
167
267
|
// Bij wissel van VBN: gooi features weg die niet meer bij het schema horen.
|
|
168
268
|
const lastPrunedVbnRef = useRef("");
|
|
169
269
|
useEffect(() => {
|
|
@@ -299,17 +399,26 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
299
399
|
setSubmitting(true);
|
|
300
400
|
try {
|
|
301
401
|
if (isEditingPublished && tradeItemId) {
|
|
402
|
+
await saveWarehousePreference(tradeItemId, data.warehouseId);
|
|
302
403
|
// PUT vervangt het bestaande trade item in Floriday
|
|
303
|
-
|
|
404
|
+
// PUT /trade-items/{id} — body volgens TradeItemUpdate (zonder tradeItemId + supplierArticleCode)
|
|
405
|
+
const payload = buildUpdateTradeItemPayload(data);
|
|
304
406
|
const res = await floriday.updateTradeItem(activeId, tradeItemId, payload);
|
|
305
407
|
if (!res?.attemptId) {
|
|
306
408
|
throw new Error("Gateway gaf geen attemptId terug — wijziging niet bevestigd");
|
|
307
409
|
}
|
|
308
410
|
// Optimistisch: cache-rij meteen bijwerken zodat de tabel niet 5 min wacht
|
|
309
411
|
try {
|
|
412
|
+
const cachePayload = {
|
|
413
|
+
...buildAddTradeItemPayload(data),
|
|
414
|
+
tradeItemId,
|
|
415
|
+
};
|
|
310
416
|
await supabase
|
|
311
417
|
.from("floriday_trade_items_cache")
|
|
312
|
-
.update({
|
|
418
|
+
.update({
|
|
419
|
+
data: cachePayload,
|
|
420
|
+
fetched_at: new Date().toISOString(),
|
|
421
|
+
})
|
|
313
422
|
.eq("connection_id", activeId)
|
|
314
423
|
.eq("floriday_id", tradeItemId);
|
|
315
424
|
}
|
|
@@ -317,6 +426,30 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
317
426
|
/* niet kritiek, sync corrigeert binnen 5 min */
|
|
318
427
|
}
|
|
319
428
|
toast.success("Artikel bijgewerkt in Floriday");
|
|
429
|
+
// Herprijzen: als er een prijs is ingevuld, supply-line (opnieuw) pushen.
|
|
430
|
+
if (data.offerForSale && data.salePricePerPiece > 0) {
|
|
431
|
+
try {
|
|
432
|
+
const primary = data.packingConfigurations.find((p) => p.isPrimary) ??
|
|
433
|
+
data.packingConfigurations[0];
|
|
434
|
+
if (primary?.id) {
|
|
435
|
+
await upsertSupplyLineForTradeItem({
|
|
436
|
+
connectionId: activeId,
|
|
437
|
+
tradeItemId,
|
|
438
|
+
pricePerPiece: data.salePricePerPiece,
|
|
439
|
+
packingConfigurationId: primary.id,
|
|
440
|
+
availableQuantity: data.saleStockPieces,
|
|
441
|
+
customerOrganizationIds: data.customerOrganizationIds,
|
|
442
|
+
periodStart: data.offerStartDate
|
|
443
|
+
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
444
|
+
: undefined,
|
|
445
|
+
});
|
|
446
|
+
toast.success("Prijs bijgewerkt in Floriday-catalogus");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch (e) {
|
|
450
|
+
toast.error(`Prijs bijwerken mislukt: ${e.message}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
320
453
|
}
|
|
321
454
|
else if (publishToFloriday) {
|
|
322
455
|
const payload = buildAddTradeItemPayload(data);
|
|
@@ -324,6 +457,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
324
457
|
if (!res?.attemptId) {
|
|
325
458
|
throw new Error("Gateway gaf geen attemptId terug — aanmaken niet bevestigd");
|
|
326
459
|
}
|
|
460
|
+
const createdTradeItemId = res.tradeItemId ?? payload.tradeItemId;
|
|
461
|
+
await saveWarehousePreference(createdTradeItemId, data.warehouseId);
|
|
327
462
|
// Als dit een lokaal artikel was, ruim de draft-rij op
|
|
328
463
|
if (draftId) {
|
|
329
464
|
try {
|
|
@@ -333,7 +468,91 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
333
468
|
// Niet kritiek; rij verdwijnt na volgende reload
|
|
334
469
|
}
|
|
335
470
|
}
|
|
336
|
-
|
|
471
|
+
// ---- Direct-sale flow -----------------------------------------
|
|
472
|
+
// Alleen doorzetten als de gebruiker de "Aanbieden"-stap geactiveerd
|
|
473
|
+
// heeft. Falen mag de TradeItem-succes NIET terugdraaien — daarom
|
|
474
|
+
// in een aparte try/catch en apart toast bericht.
|
|
475
|
+
if (data.offerForSale) {
|
|
476
|
+
const tradeItemId = createdTradeItemId;
|
|
477
|
+
// 1) Seed lokale voorraad (physical_stock) — de continuous-stock
|
|
478
|
+
// trigger pusht dit naar Floriday zodra de trade item in de
|
|
479
|
+
// cache verschijnt.
|
|
480
|
+
try {
|
|
481
|
+
const key = stockKeyFor({ tradeItemId });
|
|
482
|
+
if (key && data.saleStockPieces > 0) {
|
|
483
|
+
const userRes = await supabase.auth.getUser();
|
|
484
|
+
const userId = userRes.data.user?.id;
|
|
485
|
+
if (userId) {
|
|
486
|
+
await supabase
|
|
487
|
+
.from("trade_item_stock")
|
|
488
|
+
.upsert({
|
|
489
|
+
user_id: userId,
|
|
490
|
+
connection_id: activeId,
|
|
491
|
+
trade_item_key: key,
|
|
492
|
+
physical_stock: data.saleStockPieces,
|
|
493
|
+
minimum_stock: 0,
|
|
494
|
+
reserved_stock: 0,
|
|
495
|
+
incoming_stock: 0,
|
|
496
|
+
}, { onConflict: "connection_id,trade_item_key" });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (e) {
|
|
501
|
+
toast.error(`Voorraad opslaan mislukt: ${e.message}`);
|
|
502
|
+
}
|
|
503
|
+
// 2) CustomerOffer publiceren (POST /customer-offers)
|
|
504
|
+
try {
|
|
505
|
+
const packingBase = buildPrimaryPackingBase(data);
|
|
506
|
+
await publishCustomerOfferFn({
|
|
507
|
+
data: {
|
|
508
|
+
customerOfferId: crypto.randomUUID(),
|
|
509
|
+
tradeItemId,
|
|
510
|
+
title: data.name || data.vbnName || "Aanbod",
|
|
511
|
+
description: "",
|
|
512
|
+
pricePerPiece: data.salePricePerPiece,
|
|
513
|
+
currency: "EUR",
|
|
514
|
+
despatchWarehouseId: data.warehouseId,
|
|
515
|
+
numberOfPieces: data.saleStockPieces,
|
|
516
|
+
allowedCustomerOrganizationIds: data.customerOrganizationIds,
|
|
517
|
+
packingConfiguration: packingBase,
|
|
518
|
+
orderStart: new Date(`${data.offerStartDate}T00:00:00Z`).toISOString(),
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
toast.success("Artikel aangemaakt en aangeboden in Floriday");
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
toast.error(`Aanbod publiceren mislukt: ${e.message}`);
|
|
525
|
+
}
|
|
526
|
+
// 3) Supply-line pushen naar de Authenticatie-module gateway
|
|
527
|
+
// zodat het product met prijs online komt in de Floriday-catalogus.
|
|
528
|
+
// Idempotent op supplyLineId — herprijzen hergebruikt dezelfde regel.
|
|
529
|
+
try {
|
|
530
|
+
const primary = data.packingConfigurations.find((p) => p.isPrimary) ??
|
|
531
|
+
data.packingConfigurations[0];
|
|
532
|
+
if (primary?.id && data.salePricePerPiece > 0) {
|
|
533
|
+
const sl = await upsertSupplyLineForTradeItem({
|
|
534
|
+
connectionId: activeId,
|
|
535
|
+
tradeItemId,
|
|
536
|
+
pricePerPiece: data.salePricePerPiece,
|
|
537
|
+
packingConfigurationId: primary.id,
|
|
538
|
+
availableQuantity: data.saleStockPieces,
|
|
539
|
+
customerOrganizationIds: data.customerOrganizationIds,
|
|
540
|
+
periodStart: data.offerStartDate
|
|
541
|
+
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
542
|
+
: undefined,
|
|
543
|
+
});
|
|
544
|
+
if (sl.supplyLineId) {
|
|
545
|
+
toast.success("Prijs online gezet in Floriday-catalogus");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
toast.error(`Prijs online zetten mislukt: ${e.message}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
toast.success("Artikel aangemaakt in Floriday");
|
|
555
|
+
}
|
|
337
556
|
}
|
|
338
557
|
else {
|
|
339
558
|
const newId = await upsertLocalTradeItem({
|
|
@@ -379,7 +598,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
379
598
|
toast.error("Geen actieve Floriday-connectie geselecteerd");
|
|
380
599
|
return;
|
|
381
600
|
}
|
|
382
|
-
|
|
601
|
+
// Voor een nieuw concept blijft een VBN-code de minimale poort. Voor
|
|
602
|
+
// bestaande concepten (draftId aanwezig) mag ALLES wat de gebruiker
|
|
603
|
+
// wijzigt (o.a. beladingen) opgeslagen worden — anders raken tussentijdse
|
|
604
|
+
// bewerkingen verloren zodra ze terug navigeren.
|
|
605
|
+
if (!draftId && !vbnValid) {
|
|
383
606
|
toast.error("Kies eerst een VBN-code voordat je een concept opslaat");
|
|
384
607
|
setStep(0);
|
|
385
608
|
return;
|
|
@@ -391,6 +614,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
391
614
|
connectionId: activeId,
|
|
392
615
|
data,
|
|
393
616
|
});
|
|
617
|
+
if (draftFloridayId && data.warehouseId) {
|
|
618
|
+
await saveWarehousePreference(draftFloridayId, data.warehouseId);
|
|
619
|
+
}
|
|
394
620
|
setDraftId(newId);
|
|
395
621
|
toast.success(draftId ? "Concept bijgewerkt" : "Concept opgeslagen");
|
|
396
622
|
navigate({ to: "/voorraad" });
|
|
@@ -402,6 +628,87 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
402
628
|
setSubmitting(false);
|
|
403
629
|
}
|
|
404
630
|
};
|
|
631
|
+
// Auto-save: zodra er een bestaand concept OF een gepubliceerd artikel in
|
|
632
|
+
// bewerking is, persisteren we wijzigingen debounced. Voor concepten gaan de
|
|
633
|
+
// wijzigingen naar `trade_item_drafts`; voor gepubliceerde artikelen wordt
|
|
634
|
+
// een PUT /trade-items/{id} naar Floriday gestuurd en de lokale cache
|
|
635
|
+
// meteen bijgewerkt, zodat aanpassingen (o.a. beladingen) direct overal in
|
|
636
|
+
// het systeem beschikbaar zijn en niet verloren raken bij navigatie.
|
|
637
|
+
const lastSavedRef = useRef(null);
|
|
638
|
+
useEffect(() => {
|
|
639
|
+
if (!activeId)
|
|
640
|
+
return;
|
|
641
|
+
if (draftLoading || publishedLoading)
|
|
642
|
+
return;
|
|
643
|
+
const pushTargetId = (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null);
|
|
644
|
+
if (!draftId && !pushTargetId)
|
|
645
|
+
return;
|
|
646
|
+
const serialized = JSON.stringify(data);
|
|
647
|
+
if (lastSavedRef.current === null) {
|
|
648
|
+
lastSavedRef.current = serialized;
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (lastSavedRef.current === serialized)
|
|
652
|
+
return;
|
|
653
|
+
const t = window.setTimeout(async () => {
|
|
654
|
+
setSyncState("saving");
|
|
655
|
+
setSyncError(null);
|
|
656
|
+
try {
|
|
657
|
+
// Altijd lokaal opslaan als we een draft-rij hebben
|
|
658
|
+
if (draftId) {
|
|
659
|
+
await upsertLocalTradeItem({ id: draftId, connectionId: activeId, data });
|
|
660
|
+
}
|
|
661
|
+
// Push naar Floriday als er een gekoppeld/gepubliceerd trade-item is
|
|
662
|
+
if (pushTargetId) {
|
|
663
|
+
await saveWarehousePreference(pushTargetId, data.warehouseId);
|
|
664
|
+
const payload = buildUpdateTradeItemPayload(data);
|
|
665
|
+
const res = await floriday.updateTradeItem(activeId, pushTargetId, payload);
|
|
666
|
+
if (!res?.attemptId) {
|
|
667
|
+
throw new Error("Gateway gaf geen attemptId terug");
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const cachePayload = {
|
|
671
|
+
...buildAddTradeItemPayload(data),
|
|
672
|
+
tradeItemId: pushTargetId,
|
|
673
|
+
};
|
|
674
|
+
await supabase
|
|
675
|
+
.from("floriday_trade_items_cache")
|
|
676
|
+
.update({
|
|
677
|
+
data: cachePayload,
|
|
678
|
+
fetched_at: new Date().toISOString(),
|
|
679
|
+
})
|
|
680
|
+
.eq("connection_id", activeId)
|
|
681
|
+
.eq("floriday_id", pushTargetId);
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
/* niet kritiek; sync corrigeert */
|
|
685
|
+
}
|
|
686
|
+
setSyncState("saved");
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
setSyncState("local");
|
|
690
|
+
}
|
|
691
|
+
lastSavedRef.current = serialized;
|
|
692
|
+
}
|
|
693
|
+
catch (e) {
|
|
694
|
+
const msg = e.message || "Onbekende fout";
|
|
695
|
+
setSyncError(msg);
|
|
696
|
+
setSyncState("error");
|
|
697
|
+
toast.error(`Sync naar Floriday mislukt: ${msg}`);
|
|
698
|
+
}
|
|
699
|
+
}, 800);
|
|
700
|
+
return () => window.clearTimeout(t);
|
|
701
|
+
}, [
|
|
702
|
+
data,
|
|
703
|
+
draftId,
|
|
704
|
+
draftFloridayId,
|
|
705
|
+
activeId,
|
|
706
|
+
draftLoading,
|
|
707
|
+
publishedLoading,
|
|
708
|
+
isEditingPublished,
|
|
709
|
+
saveWarehousePreference,
|
|
710
|
+
tradeItemId,
|
|
711
|
+
]);
|
|
405
712
|
if (connLoading || draftLoading || publishedLoading) {
|
|
406
713
|
return (_jsx("main", { className: "p-6", children: _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
|
|
407
714
|
? "Artikel laden..."
|
|
@@ -412,7 +719,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
412
719
|
if (!activeConnection) {
|
|
413
720
|
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-connectie gevonden. Voeg er eerst een toe in Instellingen." }) }));
|
|
414
721
|
}
|
|
415
|
-
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || !vbnValid, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), isEditingDraft ? "Concept bijwerken" : "Opslaan als concept"] })), isEditingDraft && (_jsxs(AlertDialog, { 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: "Lokaal artikel verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit artikel is nog niet gepubliceerd naar Floriday. Verwijderen is definitief." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteDraft, children: "Verwijderen" })] })] })] }))] }), 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
|
|
722
|
+
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || (!draftId && !vbnValid), children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), isEditingDraft ? "Concept bijwerken" : "Opslaan als concept"] })), isEditingDraft && (_jsxs(AlertDialog, { 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: "Lokaal artikel verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit artikel is nog niet gepubliceerd naar Floriday. Verwijderen is definitief." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteDraft, children: "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-amber-100 text-amber-800", children: "Concept \u2014 nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), 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" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: 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: 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(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
416
723
|
const isOpen = i === step;
|
|
417
724
|
const isValid = stepValidity[i];
|
|
418
725
|
const isVisited = i < step || isEditing;
|
|
@@ -431,7 +738,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
431
738
|
? "border-primary bg-primary text-primary-foreground"
|
|
432
739
|
: status === "invalid"
|
|
433
740
|
? "border-destructive bg-destructive text-destructive-foreground"
|
|
434
|
-
: "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, 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 }) }), isLastStep && !isEditingPublished && (_jsxs("div", { className: "rounded-lg border bg-muted/30 p-4 flex items-start gap-3", children: [_jsx("img", { src: floridayLogo
|
|
741
|
+
: "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, 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 }) }), 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
|
|
435
742
|
? "Het artikel wordt aangemaakt in Floriday en is binnen enkele minuten zichtbaar voor klanten."
|
|
436
743
|
: "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) && 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: 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
|
|
437
744
|
? "Wijzigingen opslaan"
|
|
@@ -475,6 +782,8 @@ function summaryFor(key, d) {
|
|
|
475
782
|
: "Alle klanten";
|
|
476
783
|
case "fotos":
|
|
477
784
|
return `${d.images.length} foto('s)`;
|
|
785
|
+
case "warehouse":
|
|
786
|
+
return d.warehouseId ? "Gekozen" : "Niet gekozen";
|
|
478
787
|
case "beladingen":
|
|
479
788
|
return d.packingConfigurations.length
|
|
480
789
|
? `${d.packingConfigurations.length} configuratie(s)`
|
|
@@ -487,6 +796,10 @@ function summaryFor(key, d) {
|
|
|
487
796
|
return "Binnenkort beschikbaar";
|
|
488
797
|
case "beschikbaarheid":
|
|
489
798
|
return d.yearRound ? "Jaarrond" : `${d.weeks.length} week/weken`;
|
|
799
|
+
case "aanbieden":
|
|
800
|
+
if (!d.offerForSale)
|
|
801
|
+
return "Niet aanbieden";
|
|
802
|
+
return `${d.saleStockPieces} st • € ${d.salePricePerPiece.toFixed(2)} p/st`;
|
|
490
803
|
default:
|
|
491
804
|
return "";
|
|
492
805
|
}
|
|
@@ -508,6 +821,8 @@ function StepBody(p) {
|
|
|
508
821
|
return _jsx(StepKlanten, { ...p });
|
|
509
822
|
case "fotos":
|
|
510
823
|
return _jsx(StepFotos, { ...p });
|
|
824
|
+
case "warehouse":
|
|
825
|
+
return _jsx(StepWarehouse, { ...p });
|
|
511
826
|
case "beladingen":
|
|
512
827
|
return _jsx(StepBeladingen, { ...p });
|
|
513
828
|
case "verpakking":
|
|
@@ -516,6 +831,8 @@ function StepBody(p) {
|
|
|
516
831
|
return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Binnenkort beschikbaar" }));
|
|
517
832
|
case "beschikbaarheid":
|
|
518
833
|
return _jsx(StepBeschikbaarheid, { ...p });
|
|
834
|
+
case "aanbieden":
|
|
835
|
+
return _jsx(StepAanbieden, { ...p });
|
|
519
836
|
}
|
|
520
837
|
}
|
|
521
838
|
// ============================================================
|
|
@@ -571,6 +888,9 @@ function codeOf(r) {
|
|
|
571
888
|
// FeatureType heeft `id`.
|
|
572
889
|
return String(r.data?.code ?? r.data?.id ?? r.record_id);
|
|
573
890
|
}
|
|
891
|
+
function compareByCode(a, b) {
|
|
892
|
+
return String(a ?? "").localeCompare(String(b ?? ""), "nl", { numeric: true, sensitivity: "base" });
|
|
893
|
+
}
|
|
574
894
|
// Floricode `product_group_id` is een 8-cijferige string. De eerste twee
|
|
575
895
|
// cijfers bepalen het hoofdsegment, maar Floricode gebruikt méér dan één
|
|
576
896
|
// bucket per Floriday-productgroep:
|
|
@@ -889,12 +1209,13 @@ function CharacteristicField({ field, value, onChange, onRemove, }) {
|
|
|
889
1209
|
// Fallback: /gateway/code-list voor niet-VBN karakteristieken.
|
|
890
1210
|
const hasSpecValues = field.allowedValues.length > 0;
|
|
891
1211
|
const codeListQuery = useCodeList(hasSpecValues ? null : field.listName);
|
|
892
|
-
const
|
|
1212
|
+
const rawValues = hasSpecValues
|
|
893
1213
|
? field.allowedValues
|
|
894
1214
|
: (codeListQuery.data?.values ?? []).map((v) => ({
|
|
895
1215
|
code: v.code,
|
|
896
1216
|
label: v.description ?? v.code,
|
|
897
1217
|
}));
|
|
1218
|
+
const values = [...rawValues].sort((a, b) => compareByCode(a.code, b.code));
|
|
898
1219
|
const CLEAR = "__clear__";
|
|
899
1220
|
const invalidExistingValue = Boolean(value && values.length > 0 && !values.some((v) => v.code === value));
|
|
900
1221
|
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Label, { children: [field.label, field.required && _jsx("span", { className: "ml-1 text-destructive", children: "*" }), " ", _jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: ["(", field.code, ")"] })] }), onRemove && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0 text-muted-foreground hover:text-destructive", onClick: onRemove, "aria-label": `Verwijder ${field.label}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), !hasSpecValues && codeListQuery.isLoading ? (_jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Waardelijst ", field.listName, " wordt geladen\u2026"] })) : values.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Select, { value: value || undefined, onValueChange: (v) => onChange(v === CLEAR ? "" : v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer een door Floriday toegestane waarde\u2026" }) }), _jsxs(SelectContent, { children: [value && (_jsx(SelectItem, { value: CLEAR, children: _jsx("span", { className: "text-muted-foreground", children: "\u2014 Wissen \u2014" }) })), values.slice(0, 1000).map((v) => (_jsxs(SelectItem, { value: v.code, children: [v.label, " ", _jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: ["(", v.code, ")"] })] }, v.code)))] })] }), invalidExistingValue && (_jsxs("p", { className: "text-xs text-destructive", children: ["Huidige waarde \"", value, "\" komt niet voor in de door Floriday toegestane lijst. Kies een geldige waarde."] }))] })) : (_jsx("p", { className: "rounded-md border border-dashed border-muted-foreground/30 bg-muted/40 p-2 text-xs text-muted-foreground", children: "Geen toegestane waarden bekend voor deze karakteristiek. Laat leeg \u2014 vrije invoer wordt door Floriday niet geaccepteerd." }))] }));
|
|
@@ -1028,6 +1349,49 @@ function StepFotos({ data, update, activeId }) {
|
|
|
1028
1349
|
e.target.value = "";
|
|
1029
1350
|
} })] }), data.images.length > 0 && (_jsx("div", { className: "grid grid-cols-4 gap-2", children: data.images.map((img, idx) => (_jsxs("div", { className: "relative group rounded-md overflow-hidden border", children: [_jsx("img", { src: img.previewUrl || img.url, alt: img.name, className: "h-24 w-full object-cover" }), _jsx("button", { type: "button", onClick: () => update("images", data.images.filter((_, i) => i !== idx)), className: "absolute top-1 right-1 rounded-full bg-background/80 p-1 opacity-0 group-hover:opacity-100", children: _jsx(X, { className: "h-3 w-3" }) })] }, img.url))) }))] }));
|
|
1030
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Warehouse-stap — verplicht in Floriday Suppliers API 2026v1.
|
|
1354
|
+
* Toont een select gevuld uit `floriday_warehouses_cache`. Beheer van
|
|
1355
|
+
* warehouses (aanmaken/bewerken) gebeurt in Instellingen → Warehouses.
|
|
1356
|
+
*/
|
|
1357
|
+
function StepWarehouse({ data, update, activeId }) {
|
|
1358
|
+
const [warehouses, setWarehouses] = useState([]);
|
|
1359
|
+
const [loading, setLoading] = useState(false);
|
|
1360
|
+
useEffect(() => {
|
|
1361
|
+
if (!activeId)
|
|
1362
|
+
return;
|
|
1363
|
+
let cancelled = false;
|
|
1364
|
+
setLoading(true);
|
|
1365
|
+
supabase
|
|
1366
|
+
.from("floriday_warehouses_cache")
|
|
1367
|
+
.select("floriday_id, data")
|
|
1368
|
+
.eq("connection_id", activeId)
|
|
1369
|
+
.then(({ data: rows }) => {
|
|
1370
|
+
if (cancelled)
|
|
1371
|
+
return;
|
|
1372
|
+
const items = (rows ?? [])
|
|
1373
|
+
// Alleen eigen voorraadlocaties tonen — geen externe FloraHolland-
|
|
1374
|
+
// aflever-/veilinglocaties (isExternal=true).
|
|
1375
|
+
.filter((r) => r.data?.isExternal !== true && r.data?.isDeleted !== true)
|
|
1376
|
+
.map((r) => {
|
|
1377
|
+
const label = warehouseDisplayLabel(r.data, String(r.floriday_id));
|
|
1378
|
+
const sub = warehouseSubLabel(r.data);
|
|
1379
|
+
return {
|
|
1380
|
+
id: String(r.floriday_id),
|
|
1381
|
+
name: label,
|
|
1382
|
+
sub: label === sub ? undefined : sub,
|
|
1383
|
+
};
|
|
1384
|
+
})
|
|
1385
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1386
|
+
setWarehouses(items);
|
|
1387
|
+
setLoading(false);
|
|
1388
|
+
});
|
|
1389
|
+
return () => {
|
|
1390
|
+
cancelled = true;
|
|
1391
|
+
};
|
|
1392
|
+
}, [activeId]);
|
|
1393
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { children: "Voorraadlocatie (verplicht)" }), _jsxs(Select, { value: data.warehouseId || undefined, onValueChange: (v) => update("warehouseId", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? "Laden…" : "Kies een voorraadlocatie" }) }), _jsx(SelectContent, { children: warehouses.map((w) => (_jsx(SelectItem, { value: w.id, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { children: w.name }), w.sub && (_jsx("span", { className: "text-xs text-muted-foreground", children: w.sub }))] }) }, w.id))) })] })] }), warehouses.length === 0 && !loading && (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Nog geen eigen voorraadlocaties gevonden. Ga naar", " ", _jsx(Link, { to: "/instellingen/warehouses", className: "underline", children: "Instellingen \u2192 Warehouses" }), " ", "om er \u00E9\u00E9n aan te maken."] }))] }));
|
|
1394
|
+
}
|
|
1031
1395
|
/**
|
|
1032
1396
|
* Beladingen — Floriday Suppliers API 2026v1
|
|
1033
1397
|
*
|
|
@@ -1085,7 +1449,9 @@ function StepBeladingen({ data, update, packagingTypes, loadCarriers, floridayCu
|
|
|
1085
1449
|
return (_jsxs("div", { className: "space-y-4", children: [data.packingConfigurations.length === 0 ? (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen beladingsconfiguratie toegevoegd." }) })) : (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: "Primair" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Fust" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Ladingdrager" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Stuks / laag / drager" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Type" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { className: "divide-y", children: data.packingConfigurations.map((p) => {
|
|
1086
1450
|
const pkgLabel = packagingTypes.find((r) => codeOf(r) === p.packagingType);
|
|
1087
1451
|
const lcLabel = loadCarriers.find((r) => codeOf(r) === p.loadCarrier);
|
|
1088
|
-
return (_jsxs("tr", { className: "hover:bg-muted/30", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "radio", name: "primary-packing", checked: Boolean(p.isPrimary), onChange: () => setPrimary(p.id), "aria-label": "Als primaire belading instellen" }) }), _jsxs("td", { className: "px-3 py-2", children: [_jsx("div", { className: "font-medium", children: pkgLabel
|
|
1452
|
+
return (_jsxs("tr", { className: "hover:bg-muted/30", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "radio", name: "primary-packing", checked: Boolean(p.isPrimary), onChange: () => setPrimary(p.id), "aria-label": "Als primaire belading instellen" }) }), _jsxs("td", { className: "px-3 py-2", children: [_jsx("div", { className: "font-medium", children: pkgLabel
|
|
1453
|
+
? `${codeOf(pkgLabel)}: ${labelOf(pkgLabel)}`
|
|
1454
|
+
: p.packagingType || "—" }), p.source === "SUPPLIER" && (_jsx("div", { className: "text-xs text-muted-foreground", children: p.packagingSupplierName ?? p.packagingSupplierId }))] }), _jsx("td", { className: "px-3 py-2", children: LOAD_CARRIER_LABELS[p.loadCarrier] ?? p.loadCarrier ?? "—" }), _jsxs("td", { className: "px-3 py-2 tabular-nums", children: [p.unitsPerPackage, " / ", p.packagesPerLayer, " /", " ", p.layersPerLoadCarrier] }), _jsx("td", { className: "px-3 py-2", children: p.isCustomerSpecific ? (_jsxs("span", { className: "rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-900", children: ["Klantspecifiek (", p.customerOrganizationIds.length, ")"] })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: "Regulier" })) }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => openEdit(p.id), children: "Bewerk" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) })] }, p.id));
|
|
1089
1455
|
}) })] }) })), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Belading toevoegen"] }), _jsx(PackingConfigDialog, { open: dialogOpen, onOpenChange: (o) => {
|
|
1090
1456
|
setDialogOpen(o);
|
|
1091
1457
|
if (!o)
|
|
@@ -1099,7 +1465,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1099
1465
|
const emptyItem = () => ({
|
|
1100
1466
|
id: crypto.randomUUID(),
|
|
1101
1467
|
source: "VBN",
|
|
1102
|
-
loadCarrier: "",
|
|
1468
|
+
loadCarrier: "AUCTION_TROLLEY",
|
|
1103
1469
|
packagingType: "",
|
|
1104
1470
|
unitsPerPackage: 1,
|
|
1105
1471
|
packagesPerLayer: 1,
|
|
@@ -1122,9 +1488,10 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1122
1488
|
const patch = (k, v) => setDraft((d) => ({ ...d, [k]: v }));
|
|
1123
1489
|
const filteredPkg = useMemo(() => {
|
|
1124
1490
|
const q = pkgSearch.trim().toLowerCase();
|
|
1491
|
+
const sorted = [...packagingTypes].sort((a, b) => compareByCode(codeOf(a), codeOf(b)));
|
|
1125
1492
|
if (!q)
|
|
1126
|
-
return
|
|
1127
|
-
return
|
|
1493
|
+
return sorted.slice(0, 200);
|
|
1494
|
+
return sorted
|
|
1128
1495
|
.filter((r) => labelOf(r).toLowerCase().includes(q) ||
|
|
1129
1496
|
codeOf(r).toLowerCase().includes(q))
|
|
1130
1497
|
.slice(0, 200);
|
|
@@ -1184,7 +1551,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1184
1551
|
? (packagingTypes.find((r) => codeOf(r) === draft.packagingType)
|
|
1185
1552
|
? `${labelOf(packagingTypes.find((r) => codeOf(r) === draft.packagingType))} (${draft.packagingType})`
|
|
1186
1553
|
: draft.packagingType)
|
|
1187
|
-
: "Selecteer fust..." }), _jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[380px] p-0", align: "start", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { placeholder: "Zoek op naam of code...", value: pkgSearch, onChange: (e) => setPkgSearch(e.target.value) }) }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredPkg.length === 0 ? (_jsx("div", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: "Geen resultaten" })) : (filteredPkg.map((r) => (_jsxs("button", { type: "button", onClick: () => patch("packagingType", codeOf(r)), className: `flex w-full items-start gap-2 px-3 py-2 text-left text-sm hover:bg-muted ${draft.packagingType === codeOf(r) ? "bg-muted" : ""}`, children: [_jsx("span", { className: "font-mono text-xs text-muted-foreground w-14 shrink-0", children: codeOf(r) }), _jsx("span", { className: "truncate", children: labelOf(r) })] }, r.record_id)))) })] })] })] }), _jsx(
|
|
1554
|
+
: "Selecteer fust..." }), _jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[380px] p-0", align: "start", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { placeholder: "Zoek op naam of code...", value: pkgSearch, onChange: (e) => setPkgSearch(e.target.value) }) }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredPkg.length === 0 ? (_jsx("div", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: "Geen resultaten" })) : (filteredPkg.map((r) => (_jsxs("button", { type: "button", onClick: () => patch("packagingType", codeOf(r)), className: `flex w-full items-start gap-2 px-3 py-2 text-left text-sm hover:bg-muted ${draft.packagingType === codeOf(r) ? "bg-muted" : ""}`, children: [_jsx("span", { className: "font-mono text-xs text-muted-foreground w-14 shrink-0", children: codeOf(r) }), _jsx("span", { className: "truncate", children: labelOf(r) })] }, r.record_id)))) })] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Ladingdrager" }), _jsxs(Select, { value: draft.loadCarrier || "AUCTION_TROLLEY", onValueChange: (v) => patch("loadCarrier", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een ladingdrager" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "AUCTION_TROLLEY", children: "Stapelwagen" }), _jsx(SelectItem, { value: "DANISH_TROLLEY", children: "Deense container" }), _jsx(SelectItem, { value: "EURO_TROLLEY", children: "Euro-rolcontainer" }), _jsx(SelectItem, { value: "PALLET", children: "Pallet" }), _jsx(SelectItem, { value: "EURO_PALLET", children: "Euro-pallet" }), _jsx(SelectItem, { value: "NONE", children: "Geen ladingdrager" })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Stuks per fust" }), _jsx(Input, { type: "number", min: 1, value: draft.unitsPerPackage, onChange: (e) => patch("unitsPerPackage", Number(e.target.value) || 1) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Fusten per laag" }), _jsx(Input, { type: "number", min: 1, value: draft.packagesPerLayer, onChange: (e) => patch("packagesPerLayer", Number(e.target.value) || 1) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Lagen per drager" }), _jsx(Input, { type: "number", min: 1, value: draft.layersPerLoadCarrier, onChange: (e) => patch("layersPerLoadCarrier", Number(e.target.value) || 1) })] })] }), _jsxs("div", { className: "rounded-md border p-3 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Klantspecifieke belading" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Alleen zichtbaar voor geselecteerde klantorganisaties." })] }), _jsx(Switch, { checked: draft.isCustomerSpecific, onCheckedChange: (v) => patch("isCustomerSpecific", Boolean(v)) })] }), draft.isCustomerSpecific && (_jsx("div", { className: "max-h-40 overflow-y-auto rounded border divide-y", children: floridayCustomers.length === 0 ? (_jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "Geen klantorganisaties geladen." })) : (floridayCustomers.map((c) => (_jsxs("label", { className: "flex items-center gap-2 px-3 py-2 text-sm hover:bg-muted cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: draft.customerOrganizationIds.includes(c.id), onChange: () => toggleCustomer(c.id) }), _jsx("span", { className: "truncate", children: c.name })] }, c.id)))) }))] }), _jsxs("div", { className: "rounded-md border p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Foto van dit fust (optioneel)" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "JPG of PNG \u00B7 min. 640 \u00D7 640 px \u00B7 max. 5 MB" })] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: uploadingPhoto || !activeId, onClick: () => photoInputRef.current?.click(), children: uploadingPhoto ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Uploaden"] })) : (_jsxs(_Fragment, { children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), " Foto kiezen"] })) }), _jsx("input", { ref: photoInputRef, type: "file", accept: "image/png,image/jpeg", className: "hidden", onChange: (e) => {
|
|
1188
1555
|
onUploadPhoto(e.target.files?.[0]);
|
|
1189
1556
|
e.target.value = "";
|
|
1190
1557
|
} })] }), (draft.photoPreviewUrl || draft.photoUrl) && (_jsxs("div", { className: "relative inline-block", children: [_jsx("img", { src: draft.photoPreviewUrl || draft.photoUrl, alt: "Belading foto", className: "h-24 rounded-md border object-cover" }), _jsx("button", { type: "button", onClick: () => setDraft((d) => ({
|
|
@@ -1195,13 +1562,10 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1195
1562
|
})), className: "absolute top-1 right-1 rounded-full bg-background/80 p-1", children: _jsx(X, { className: "h-3 w-3" }) })] }))] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: Boolean(draft.isPrimary), onChange: (e) => patch("isPrimary", e.target.checked) }), "Als primaire belading gebruiken"] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { type: "button", disabled: !canSave, onClick: () => onSave(draft), children: "Opslaan" })] })] }) }));
|
|
1196
1563
|
}
|
|
1197
1564
|
function StepVerpakking({ data, update }) {
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
{ id: crypto.randomUUID(), code: "", description: "", quantity: 1 },
|
|
1201
|
-
]);
|
|
1565
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
1566
|
+
const addItem = (item) => update("additionalPackaging", [...data.additionalPackaging, item]);
|
|
1202
1567
|
const remove = (id) => update("additionalPackaging", data.additionalPackaging.filter((p) => p.id !== id));
|
|
1203
|
-
|
|
1204
|
-
return (_jsxs("div", { className: "space-y-3", children: [data.additionalPackaging.map((p) => (_jsxs("div", { className: "grid grid-cols-[1fr_2fr_100px_auto] gap-2 items-end", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Code" }), _jsx(Input, { value: p.code, onChange: (e) => edit(p.id, { code: e.target.value }), placeholder: "bv. HOES" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Omschrijving" }), _jsx(Input, { value: p.description ?? "", onChange: (e) => edit(p.id, { description: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Aantal" }), _jsx(Input, { type: "number", min: 1, value: p.quantity, onChange: (e) => edit(p.id, { quantity: Number(e.target.value) }) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: add, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Verpakkingsmateriaal toevoegen"] })] }));
|
|
1568
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("p", { className: "text-xs text-muted-foreground bg-muted/40 rounded-md p-2 border", children: ["Voeg extra verpakkingsmateriaal (hoes, steker, label, potfolie, \u2026) uit de VRS-catalogus toe. Materialen met een geldige Floriday-UUID worden automatisch meegestuurd naar Floriday als", _jsx("code", { className: "mx-1", children: "additionalPackagingMaterialIds" }), "op het TradeItem."] }), data.additionalPackaging.length > 0 && (_jsx("div", { className: "border rounded-md divide-y", children: data.additionalPackaging.map((p) => (_jsxs("div", { className: "grid grid-cols-[1fr_1fr_1fr_auto] gap-3 items-center px-3 py-2 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Leverancier" }), _jsx("div", { children: p.packagingSupplierName })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Type" }), _jsx("div", { children: p.materialTypeName })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Materiaal" }), _jsx("div", { children: p.packagingMaterialName })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))) })), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Verpakkingsmateriaal toevoegen"] }), _jsx(AdditionalPackagingDialog, { open: dialogOpen, onOpenChange: setDialogOpen, onSave: addItem })] }));
|
|
1205
1569
|
}
|
|
1206
1570
|
function StepBeschikbaarheid({ data, update }) {
|
|
1207
1571
|
const weeks = Array.from({ length: 52 }, (_, i) => i + 1);
|
|
@@ -1217,6 +1581,22 @@ function StepBeschikbaarheid({ data, update }) {
|
|
|
1217
1581
|
? "border-primary bg-primary text-primary-foreground"
|
|
1218
1582
|
: "hover:border-primary/40"}`, children: w }, w))) })] }))] }));
|
|
1219
1583
|
}
|
|
1584
|
+
// ------------------------------------------------------------
|
|
1585
|
+
// Stap "Aanbieden & voorraad" — Direct Sale via CustomerOffer
|
|
1586
|
+
// ------------------------------------------------------------
|
|
1587
|
+
function StepAanbieden({ data, update }) {
|
|
1588
|
+
const priceStr = Number.isFinite(data.salePricePerPiece)
|
|
1589
|
+
? String(data.salePricePerPiece)
|
|
1590
|
+
: "";
|
|
1591
|
+
const stockStr = Number.isFinite(data.saleStockPieces)
|
|
1592
|
+
? String(data.saleStockPieces)
|
|
1593
|
+
: "";
|
|
1594
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm text-muted-foreground", children: ["Zet dit product direct in de verkoop bij je gekoppelde Floriday-klanten. We sturen een ", _jsx("code", { children: "TradeItem" }), " + ", _jsx("code", { children: "CustomerOffer" }), " met vaste prijs per stuk en publiceren de opgegeven voorraad via de continuous-stock feed."] }), _jsxs("div", { className: "flex items-center justify-between rounded-md border p-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Direct aanbieden voor verkoop" }), _jsx("div", { className: "text-xs text-muted-foreground", children: data.offerForSale
|
|
1595
|
+
? "Aanbod wordt aangemaakt direct na publiceren."
|
|
1596
|
+
: "Alleen artikel publiceren, geen aanbod aanmaken." })] }), _jsx(Switch, { checked: data.offerForSale, onCheckedChange: (v) => update("offerForSale", Boolean(v)) })] }), data.offerForSale && (_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Voorraad (stuks)" }), _jsx(Input, { type: "number", inputMode: "numeric", min: 0, step: 1, value: stockStr, onChange: (e) => update("saleStockPieces", Math.max(0, Math.floor(Number(e.target.value) || 0))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt als fysieke voorraad geboekt en via continuous-stock naar Floriday gepusht." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Verkoopprijs per stuk (EUR)" }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", value: priceStr, onChange: (e) => update("salePricePerPiece", Number(e.target.value) || 0) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Vaste prijs per stuk voor Direct Sale." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aanbod geldig vanaf" }), _jsx(Input, { type: "date", value: data.offerStartDate, onChange: (e) => update("offerStartDate", e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aanbod loopt automatisch tot 1 jaar na deze datum." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Zichtbaar voor" }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: data.customerOrganizationIds.length
|
|
1597
|
+
? `${data.customerOrganizationIds.length} geselecteerde klant(en)`
|
|
1598
|
+
: "Alle gekoppelde Floriday-klanten" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Klantselectie beheer je in de stap ", _jsx("em", { children: "Klanten" }), "."] })] })] }))] }));
|
|
1599
|
+
}
|
|
1220
1600
|
// ============================================================
|
|
1221
1601
|
// Shared
|
|
1222
1602
|
// ============================================================
|
|
@@ -1225,5 +1605,8 @@ function DropdownOrInput({ label, value, onChange, records, render, valueGetter,
|
|
|
1225
1605
|
if (records.length === 0) {
|
|
1226
1606
|
return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: label }), _jsx(Input, { value: value, onChange: (e) => onChange(e.target.value) }), emptyHint && _jsx("p", { className: "text-xs text-muted-foreground", children: emptyHint })] }));
|
|
1227
1607
|
}
|
|
1228
|
-
return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: label }), _jsxs(Select, { value: value || undefined, onValueChange: onChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer..." }) }), _jsx(SelectContent, { children: records
|
|
1608
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: label }), _jsxs(Select, { value: value || undefined, onValueChange: onChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer..." }) }), _jsx(SelectContent, { children: [...records]
|
|
1609
|
+
.sort((a, b) => compareByCode(v(a), v(b)))
|
|
1610
|
+
.slice(0, 300)
|
|
1611
|
+
.map((r) => (_jsx(SelectItem, { value: v(r), children: render(r) }, r.record_id))) })] })] }));
|
|
1229
1612
|
}
|