@flowselections/floriday-voorraad 1.0.16 → 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 +372 -30
- 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/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/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/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.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +35 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +473 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +473 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +473 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +481 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +480 -4
- 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-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 +13155 -15
- 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 +60 -6
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +104 -7
- 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 +1420 -10
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +947 -7
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.js +6 -1
- 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,7 +8,10 @@ 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, buildUpdateTradeItemPayload, 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";
|
|
@@ -16,8 +19,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";
|
|
18
21
|
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
22
|
+
import { PriceStatusBadge } from "../../components/voorraad/PriceStatusBadge";
|
|
23
|
+
import { OrderHistoryCard } from "../../components/voorraad/OrderHistoryCard";
|
|
19
24
|
import { stockKeyFor } from "../../hooks/useTradeItemStock";
|
|
20
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
|
+
};
|
|
21
34
|
const STEPS = [
|
|
22
35
|
{ key: "vbn", label: "VBN-code" },
|
|
23
36
|
{ key: "artikel", label: "Artikel" },
|
|
@@ -30,6 +43,7 @@ const STEPS = [
|
|
|
30
43
|
{ key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
|
|
31
44
|
{ key: "samenstelling", label: "Samenstelling", disabled: true },
|
|
32
45
|
{ key: "beschikbaarheid", label: "Verwachte beschikbaarheid" },
|
|
46
|
+
{ key: "aanbieden", label: "Aanbieden & voorraad" },
|
|
33
47
|
];
|
|
34
48
|
function visibleSteps(d) {
|
|
35
49
|
return STEPS.filter((s) => {
|
|
@@ -39,6 +53,20 @@ function visibleSteps(d) {
|
|
|
39
53
|
return true;
|
|
40
54
|
});
|
|
41
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
|
+
}
|
|
42
70
|
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
43
71
|
const navigate = useNavigate();
|
|
44
72
|
const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
|
|
@@ -48,11 +76,43 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
48
76
|
const [submitting, setSubmitting] = useState(false);
|
|
49
77
|
const [publishToFloriday, setPublishToFloriday] = useState(true);
|
|
50
78
|
const [draftId, setDraftId] = useState(initialDraftId);
|
|
79
|
+
const [draftFloridayId, setDraftFloridayId] = useState(null);
|
|
51
80
|
const [draftLoading, setDraftLoading] = useState(Boolean(initialDraftId));
|
|
52
81
|
const [publishedLoading, setPublishedLoading] = useState(Boolean(tradeItemId));
|
|
82
|
+
const [syncState, setSyncState] = useState("idle");
|
|
83
|
+
const [syncError, setSyncError] = useState(null);
|
|
53
84
|
const isEditingDraft = Boolean(initialDraftId);
|
|
54
85
|
const isEditingPublished = Boolean(tradeItemId);
|
|
55
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]);
|
|
56
116
|
// Load existing draft when editing
|
|
57
117
|
useEffect(() => {
|
|
58
118
|
if (!initialDraftId)
|
|
@@ -60,10 +120,23 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
60
120
|
let cancelled = false;
|
|
61
121
|
setDraftLoading(true);
|
|
62
122
|
getLocalTradeItem(initialDraftId)
|
|
63
|
-
.then((row) => {
|
|
123
|
+
.then(async (row) => {
|
|
64
124
|
if (cancelled || !row)
|
|
65
125
|
return;
|
|
66
|
-
|
|
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);
|
|
67
140
|
})
|
|
68
141
|
.catch((e) => toast.error(`Lokaal artikel laden mislukt: ${e.message}`))
|
|
69
142
|
.finally(() => {
|
|
@@ -73,7 +146,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
73
146
|
return () => {
|
|
74
147
|
cancelled = true;
|
|
75
148
|
};
|
|
76
|
-
}, [initialDraftId]);
|
|
149
|
+
}, [initialDraftId, activeId]);
|
|
77
150
|
// Load existing published trade item from the shared cache
|
|
78
151
|
useEffect(() => {
|
|
79
152
|
if (!tradeItemId || !activeId)
|
|
@@ -88,6 +161,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
88
161
|
.eq("connection_id", activeId)
|
|
89
162
|
.eq("floriday_id", tradeItemId)
|
|
90
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();
|
|
91
170
|
if (cancelled)
|
|
92
171
|
return;
|
|
93
172
|
if (error) {
|
|
@@ -98,7 +177,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
98
177
|
toast.error("Artikel niet gevonden in de cache");
|
|
99
178
|
return;
|
|
100
179
|
}
|
|
101
|
-
|
|
180
|
+
const wizardData = floridayCacheToWizardData(row.data);
|
|
181
|
+
setData({
|
|
182
|
+
...wizardData,
|
|
183
|
+
warehouseId: pref?.warehouse_id ?? wizardData.warehouseId,
|
|
184
|
+
});
|
|
102
185
|
setPublishToFloriday(true);
|
|
103
186
|
}
|
|
104
187
|
catch (e) {
|
|
@@ -123,8 +206,16 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
123
206
|
useEffect(() => {
|
|
124
207
|
// Kleine resources volledig laden
|
|
125
208
|
listFloricode("FeatureType", undefined, 300).then(setFeatureTypes).catch(() => { });
|
|
126
|
-
|
|
127
|
-
|
|
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(() => { });
|
|
128
219
|
// VbnProduct (51k records) wordt server-side per productgroep geladen in StepVbn.
|
|
129
220
|
supabase
|
|
130
221
|
.from("collections")
|
|
@@ -167,6 +258,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
167
258
|
}
|
|
168
259
|
return set.size ? set : null;
|
|
169
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
|
+
});
|
|
170
267
|
// Bij wissel van VBN: gooi features weg die niet meer bij het schema horen.
|
|
171
268
|
const lastPrunedVbnRef = useRef("");
|
|
172
269
|
useEffect(() => {
|
|
@@ -302,6 +399,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
302
399
|
setSubmitting(true);
|
|
303
400
|
try {
|
|
304
401
|
if (isEditingPublished && tradeItemId) {
|
|
402
|
+
await saveWarehousePreference(tradeItemId, data.warehouseId);
|
|
305
403
|
// PUT vervangt het bestaande trade item in Floriday
|
|
306
404
|
// PUT /trade-items/{id} — body volgens TradeItemUpdate (zonder tradeItemId + supplierArticleCode)
|
|
307
405
|
const payload = buildUpdateTradeItemPayload(data);
|
|
@@ -311,9 +409,16 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
311
409
|
}
|
|
312
410
|
// Optimistisch: cache-rij meteen bijwerken zodat de tabel niet 5 min wacht
|
|
313
411
|
try {
|
|
412
|
+
const cachePayload = {
|
|
413
|
+
...buildAddTradeItemPayload(data),
|
|
414
|
+
tradeItemId,
|
|
415
|
+
};
|
|
314
416
|
await supabase
|
|
315
417
|
.from("floriday_trade_items_cache")
|
|
316
|
-
.update({
|
|
418
|
+
.update({
|
|
419
|
+
data: cachePayload,
|
|
420
|
+
fetched_at: new Date().toISOString(),
|
|
421
|
+
})
|
|
317
422
|
.eq("connection_id", activeId)
|
|
318
423
|
.eq("floriday_id", tradeItemId);
|
|
319
424
|
}
|
|
@@ -321,6 +426,30 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
321
426
|
/* niet kritiek, sync corrigeert binnen 5 min */
|
|
322
427
|
}
|
|
323
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
|
+
}
|
|
324
453
|
}
|
|
325
454
|
else if (publishToFloriday) {
|
|
326
455
|
const payload = buildAddTradeItemPayload(data);
|
|
@@ -328,6 +457,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
328
457
|
if (!res?.attemptId) {
|
|
329
458
|
throw new Error("Gateway gaf geen attemptId terug — aanmaken niet bevestigd");
|
|
330
459
|
}
|
|
460
|
+
const createdTradeItemId = res.tradeItemId ?? payload.tradeItemId;
|
|
461
|
+
await saveWarehousePreference(createdTradeItemId, data.warehouseId);
|
|
331
462
|
// Als dit een lokaal artikel was, ruim de draft-rij op
|
|
332
463
|
if (draftId) {
|
|
333
464
|
try {
|
|
@@ -337,7 +468,91 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
337
468
|
// Niet kritiek; rij verdwijnt na volgende reload
|
|
338
469
|
}
|
|
339
470
|
}
|
|
340
|
-
|
|
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
|
+
}
|
|
341
556
|
}
|
|
342
557
|
else {
|
|
343
558
|
const newId = await upsertLocalTradeItem({
|
|
@@ -383,7 +598,11 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
383
598
|
toast.error("Geen actieve Floriday-connectie geselecteerd");
|
|
384
599
|
return;
|
|
385
600
|
}
|
|
386
|
-
|
|
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) {
|
|
387
606
|
toast.error("Kies eerst een VBN-code voordat je een concept opslaat");
|
|
388
607
|
setStep(0);
|
|
389
608
|
return;
|
|
@@ -395,6 +614,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
395
614
|
connectionId: activeId,
|
|
396
615
|
data,
|
|
397
616
|
});
|
|
617
|
+
if (draftFloridayId && data.warehouseId) {
|
|
618
|
+
await saveWarehousePreference(draftFloridayId, data.warehouseId);
|
|
619
|
+
}
|
|
398
620
|
setDraftId(newId);
|
|
399
621
|
toast.success(draftId ? "Concept bijgewerkt" : "Concept opgeslagen");
|
|
400
622
|
navigate({ to: "/voorraad" });
|
|
@@ -406,6 +628,87 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
406
628
|
setSubmitting(false);
|
|
407
629
|
}
|
|
408
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
|
+
]);
|
|
409
712
|
if (connLoading || draftLoading || publishedLoading) {
|
|
410
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
|
|
411
714
|
? "Artikel laden..."
|
|
@@ -416,7 +719,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
416
719
|
if (!activeConnection) {
|
|
417
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." }) }));
|
|
418
721
|
}
|
|
419
|
-
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, 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 }), _jsx(
|
|
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) => {
|
|
420
723
|
const isOpen = i === step;
|
|
421
724
|
const isValid = stepValidity[i];
|
|
422
725
|
const isVisited = i < step || isEditing;
|
|
@@ -493,6 +796,10 @@ function summaryFor(key, d) {
|
|
|
493
796
|
return "Binnenkort beschikbaar";
|
|
494
797
|
case "beschikbaarheid":
|
|
495
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`;
|
|
496
803
|
default:
|
|
497
804
|
return "";
|
|
498
805
|
}
|
|
@@ -524,6 +831,8 @@ function StepBody(p) {
|
|
|
524
831
|
return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Binnenkort beschikbaar" }));
|
|
525
832
|
case "beschikbaarheid":
|
|
526
833
|
return _jsx(StepBeschikbaarheid, { ...p });
|
|
834
|
+
case "aanbieden":
|
|
835
|
+
return _jsx(StepAanbieden, { ...p });
|
|
527
836
|
}
|
|
528
837
|
}
|
|
529
838
|
// ============================================================
|
|
@@ -579,6 +888,9 @@ function codeOf(r) {
|
|
|
579
888
|
// FeatureType heeft `id`.
|
|
580
889
|
return String(r.data?.code ?? r.data?.id ?? r.record_id);
|
|
581
890
|
}
|
|
891
|
+
function compareByCode(a, b) {
|
|
892
|
+
return String(a ?? "").localeCompare(String(b ?? ""), "nl", { numeric: true, sensitivity: "base" });
|
|
893
|
+
}
|
|
582
894
|
// Floricode `product_group_id` is een 8-cijferige string. De eerste twee
|
|
583
895
|
// cijfers bepalen het hoofdsegment, maar Floricode gebruikt méér dan één
|
|
584
896
|
// bucket per Floriday-productgroep:
|
|
@@ -897,12 +1209,13 @@ function CharacteristicField({ field, value, onChange, onRemove, }) {
|
|
|
897
1209
|
// Fallback: /gateway/code-list voor niet-VBN karakteristieken.
|
|
898
1210
|
const hasSpecValues = field.allowedValues.length > 0;
|
|
899
1211
|
const codeListQuery = useCodeList(hasSpecValues ? null : field.listName);
|
|
900
|
-
const
|
|
1212
|
+
const rawValues = hasSpecValues
|
|
901
1213
|
? field.allowedValues
|
|
902
1214
|
: (codeListQuery.data?.values ?? []).map((v) => ({
|
|
903
1215
|
code: v.code,
|
|
904
1216
|
label: v.description ?? v.code,
|
|
905
1217
|
}));
|
|
1218
|
+
const values = [...rawValues].sort((a, b) => compareByCode(a.code, b.code));
|
|
906
1219
|
const CLEAR = "__clear__";
|
|
907
1220
|
const invalidExistingValue = Boolean(value && values.length > 0 && !values.some((v) => v.code === value));
|
|
908
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." }))] }));
|
|
@@ -1056,10 +1369,20 @@ function StepWarehouse({ data, update, activeId }) {
|
|
|
1056
1369
|
.then(({ data: rows }) => {
|
|
1057
1370
|
if (cancelled)
|
|
1058
1371
|
return;
|
|
1059
|
-
const items = (rows ?? [])
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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));
|
|
1063
1386
|
setWarehouses(items);
|
|
1064
1387
|
setLoading(false);
|
|
1065
1388
|
});
|
|
@@ -1067,7 +1390,7 @@ function StepWarehouse({ data, update, activeId }) {
|
|
|
1067
1390
|
cancelled = true;
|
|
1068
1391
|
};
|
|
1069
1392
|
}, [activeId]);
|
|
1070
|
-
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { children: "
|
|
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."] }))] }));
|
|
1071
1394
|
}
|
|
1072
1395
|
/**
|
|
1073
1396
|
* Beladingen — Floriday Suppliers API 2026v1
|
|
@@ -1126,7 +1449,9 @@ function StepBeladingen({ data, update, packagingTypes, loadCarriers, floridayCu
|
|
|
1126
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) => {
|
|
1127
1450
|
const pkgLabel = packagingTypes.find((r) => codeOf(r) === p.packagingType);
|
|
1128
1451
|
const lcLabel = loadCarriers.find((r) => codeOf(r) === p.loadCarrier);
|
|
1129
|
-
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));
|
|
1130
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) => {
|
|
1131
1456
|
setDialogOpen(o);
|
|
1132
1457
|
if (!o)
|
|
@@ -1140,7 +1465,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1140
1465
|
const emptyItem = () => ({
|
|
1141
1466
|
id: crypto.randomUUID(),
|
|
1142
1467
|
source: "VBN",
|
|
1143
|
-
loadCarrier: "",
|
|
1468
|
+
loadCarrier: "AUCTION_TROLLEY",
|
|
1144
1469
|
packagingType: "",
|
|
1145
1470
|
unitsPerPackage: 1,
|
|
1146
1471
|
packagesPerLayer: 1,
|
|
@@ -1163,9 +1488,10 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1163
1488
|
const patch = (k, v) => setDraft((d) => ({ ...d, [k]: v }));
|
|
1164
1489
|
const filteredPkg = useMemo(() => {
|
|
1165
1490
|
const q = pkgSearch.trim().toLowerCase();
|
|
1491
|
+
const sorted = [...packagingTypes].sort((a, b) => compareByCode(codeOf(a), codeOf(b)));
|
|
1166
1492
|
if (!q)
|
|
1167
|
-
return
|
|
1168
|
-
return
|
|
1493
|
+
return sorted.slice(0, 200);
|
|
1494
|
+
return sorted
|
|
1169
1495
|
.filter((r) => labelOf(r).toLowerCase().includes(q) ||
|
|
1170
1496
|
codeOf(r).toLowerCase().includes(q))
|
|
1171
1497
|
.slice(0, 200);
|
|
@@ -1225,7 +1551,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1225
1551
|
? (packagingTypes.find((r) => codeOf(r) === draft.packagingType)
|
|
1226
1552
|
? `${labelOf(packagingTypes.find((r) => codeOf(r) === draft.packagingType))} (${draft.packagingType})`
|
|
1227
1553
|
: draft.packagingType)
|
|
1228
|
-
: "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) => {
|
|
1229
1555
|
onUploadPhoto(e.target.files?.[0]);
|
|
1230
1556
|
e.target.value = "";
|
|
1231
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) => ({
|
|
@@ -1236,13 +1562,10 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
|
|
|
1236
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" })] })] }) }));
|
|
1237
1563
|
}
|
|
1238
1564
|
function StepVerpakking({ data, update }) {
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
{ id: crypto.randomUUID(), code: "", description: "", quantity: 1 },
|
|
1242
|
-
]);
|
|
1565
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
1566
|
+
const addItem = (item) => update("additionalPackaging", [...data.additionalPackaging, item]);
|
|
1243
1567
|
const remove = (id) => update("additionalPackaging", data.additionalPackaging.filter((p) => p.id !== id));
|
|
1244
|
-
|
|
1245
|
-
return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-muted-foreground bg-muted/40 rounded-md p-2 border", children: "Bestaat je artikel uit een hoes, steker of andere toegevoegde waarde? Voeg deze items lokaal toe voor exportdocumentatie. Floriday Suppliers API 2026v1 kent hiervoor g\u00E9\u00E9n veld op TradeItem \u2014 deze data blijft binnen deze module." }), 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 })] }));
|
|
1246
1569
|
}
|
|
1247
1570
|
function StepBeschikbaarheid({ data, update }) {
|
|
1248
1571
|
const weeks = Array.from({ length: 52 }, (_, i) => i + 1);
|
|
@@ -1258,6 +1581,22 @@ function StepBeschikbaarheid({ data, update }) {
|
|
|
1258
1581
|
? "border-primary bg-primary text-primary-foreground"
|
|
1259
1582
|
: "hover:border-primary/40"}`, children: w }, w))) })] }))] }));
|
|
1260
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
|
+
}
|
|
1261
1600
|
// ============================================================
|
|
1262
1601
|
// Shared
|
|
1263
1602
|
// ============================================================
|
|
@@ -1266,5 +1605,8 @@ function DropdownOrInput({ label, value, onChange, records, render, valueGetter,
|
|
|
1266
1605
|
if (records.length === 0) {
|
|
1267
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 })] }));
|
|
1268
1607
|
}
|
|
1269
|
-
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))) })] })] }));
|
|
1270
1612
|
}
|