@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.
Files changed (64) hide show
  1. package/dist-lib/components/voorraad/AdditionalPackagingDialog.d.ts +9 -0
  2. package/dist-lib/components/voorraad/AdditionalPackagingDialog.d.ts.map +1 -0
  3. package/dist-lib/components/voorraad/AdditionalPackagingDialog.js +51 -0
  4. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  5. package/dist-lib/components/voorraad/ArtikelWizard.js +372 -30
  6. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  7. package/dist-lib/components/voorraad/EditArtikelPage.js +78 -4
  8. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +8 -0
  9. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/OrderHistoryCard.js +133 -0
  11. package/dist-lib/components/voorraad/PriceStatusBadge.d.ts +7 -0
  12. package/dist-lib/components/voorraad/PriceStatusBadge.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/PriceStatusBadge.js +67 -0
  14. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +2 -1
  15. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  16. package/dist-lib/components/voorraad/VoorraadStockCard.js +5 -4
  17. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  18. package/dist-lib/components/voorraad/VoorraadTable.js +6 -1
  19. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  20. package/dist-lib/hooks/useLocalTradeItems.js +4 -0
  21. package/dist-lib/hooks/useTradeItemStock.d.ts +14 -1
  22. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  23. package/dist-lib/hooks/useTradeItemStock.js +76 -17
  24. package/dist-lib/hooks/useTradeItemSupplyLine.d.ts +21 -0
  25. package/dist-lib/hooks/useTradeItemSupplyLine.d.ts.map +1 -0
  26. package/dist-lib/hooks/useTradeItemSupplyLine.js +49 -0
  27. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  28. package/dist-lib/hooks/useVoorraadData.js +35 -3
  29. package/dist-lib/integrations/supabase/auth-middleware.d.ts +473 -3
  30. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  31. package/dist-lib/integrations/supabase/client.d.ts +473 -3
  32. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  33. package/dist-lib/integrations/supabase/client.server.d.ts +473 -3
  34. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  35. package/dist-lib/integrations/supabase/types.d.ts +481 -3
  36. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  37. package/dist-lib/lib/floricode-required-features.functions.d.ts +480 -4
  38. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/floricode-required-features.functions.js +20 -2
  40. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +11264 -0
  41. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -0
  42. package/dist-lib/lib/floriday-customer-offer.functions.js +175 -0
  43. package/dist-lib/lib/floriday-gateway.functions.d.ts +13155 -15
  44. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/floriday-gateway.functions.js +150 -0
  46. package/dist-lib/lib/floriday-gateway.server.d.ts +22 -0
  47. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  48. package/dist-lib/lib/floriday-gateway.server.js +3 -0
  49. package/dist-lib/lib/floriday-payload.d.ts +60 -6
  50. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  51. package/dist-lib/lib/floriday-payload.js +104 -7
  52. package/dist-lib/lib/floriday-supply-line.d.ts +53 -0
  53. package/dist-lib/lib/floriday-supply-line.d.ts.map +1 -0
  54. package/dist-lib/lib/floriday-supply-line.js +109 -0
  55. package/dist-lib/lib/floriday-warehouse.functions.d.ts +1420 -10
  56. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/floriday-writes.functions.d.ts +947 -7
  58. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/floriday-writes.functions.js +6 -1
  60. package/dist-lib/lib/vrs-catalog.d.ts +27 -0
  61. package/dist-lib/lib/vrs-catalog.d.ts.map +1 -0
  62. package/dist-lib/lib/vrs-catalog.js +98 -0
  63. package/package.json +3 -2
  64. 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
- setData({ ...empty, ...row.data });
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
- setData(floridayCacheToWizardData(row.data));
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
- listFloricode("PackagingType", undefined, 100).then(setPackagingTypes).catch(() => { });
127
- listFloricode("LoadCarrier", undefined, 600).then(setLoadCarriers).catch(() => { });
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({ data: { ...payload, tradeItemId }, updated_at: new Date().toISOString() })
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
- toast.success("Artikel aangemaakt in Floriday");
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
- if (!vbnValid) {
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(ProductLinkCard, { tradeItemKey: stockKeyFor({ tradeItemId: tradeItemId ?? null, draftId: draftId ?? null }), productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
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 values = hasSpecValues
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 ?? []).map((r) => ({
1060
- id: String(r.floriday_id),
1061
- name: String(r.data?.name ?? r.floriday_id),
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: "Warehouse (verplicht)" }), _jsxs(Select, { value: data.warehouseId || undefined, onValueChange: (v) => update("warehouseId", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? "Laden…" : "Kies een warehouse" }) }), _jsx(SelectContent, { children: warehouses.map((w) => (_jsx(SelectItem, { value: w.id, children: w.name }, w.id))) })] })] }), warehouses.length === 0 && !loading && (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Nog geen warehouses gesynchroniseerd. Ga naar", " ", _jsx(Link, { to: "/instellingen/warehouses", className: "underline", children: "Instellingen \u2192 Warehouses" }), " ", "om er \u00E9\u00E9n aan te maken."] }))] }));
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 ? labelOf(pkgLabel) : 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: lcLabel ? labelOf(lcLabel) : 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));
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 packagingTypes.slice(0, 200);
1168
- return packagingTypes
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(DropdownOrInput, { label: "Ladingdrager", value: draft.loadCarrier, onChange: (v) => patch("loadCarrier", v), records: loadCarriers, render: (r) => `${labelOf(r)} (${codeOf(r)})`, valueGetter: codeOf, emptyHint: "Geen LoadCarrier-records gesynchroniseerd." })] }), _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) => {
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 add = () => update("additionalPackaging", [
1240
- ...data.additionalPackaging,
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
- const edit = (id, patch) => update("additionalPackaging", data.additionalPackaging.map((p) => (p.id === id ? { ...p, ...patch } : p)));
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.slice(0, 300).map((r) => (_jsx(SelectItem, { value: v(r), children: render(r) }, r.record_id))) })] })] }));
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
  }