@flowselections/floriday-voorraad 1.0.19 → 1.0.21

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/ArtikelCompositionCard.d.ts +7 -0
  2. package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -0
  3. package/dist-lib/components/voorraad/ArtikelCompositionCard.js +196 -0
  4. package/dist-lib/components/voorraad/ArtikelWizard.d.ts +2 -1
  5. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/ArtikelWizard.js +259 -26
  7. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/EditArtikelPage.js +28 -85
  9. package/dist-lib/components/voorraad/FloridayStatusBadge.d.ts +4 -0
  10. package/dist-lib/components/voorraad/FloridayStatusBadge.d.ts.map +1 -0
  11. package/dist-lib/components/voorraad/FloridayStatusBadge.js +8 -0
  12. package/dist-lib/components/voorraad/StockBulkButtons.js +7 -6
  13. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +5 -0
  14. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  15. package/dist-lib/components/voorraad/ValidationOverviewTable.js +26 -56
  16. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +4 -0
  17. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -1
  18. package/dist-lib/components/voorraad/VoorraadDashboard.js +14 -6
  19. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  20. package/dist-lib/components/voorraad/VoorraadStockCard.js +11 -0
  21. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  22. package/dist-lib/components/voorraad/VoorraadTable.js +5 -12
  23. package/dist-lib/hooks/useFloridayContinuousStock.d.ts +13 -0
  24. package/dist-lib/hooks/useFloridayContinuousStock.d.ts.map +1 -0
  25. package/dist-lib/hooks/useFloridayContinuousStock.js +109 -0
  26. package/dist-lib/hooks/useFlorydayCustomers.d.ts +11 -4
  27. package/dist-lib/hooks/useFlorydayCustomers.d.ts.map +1 -1
  28. package/dist-lib/hooks/useFlorydayCustomers.js +85 -38
  29. package/dist-lib/hooks/useLocalTradeItems.d.ts +26 -2
  30. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  31. package/dist-lib/hooks/useLocalTradeItems.js +119 -79
  32. package/dist-lib/hooks/useVoorraadData.d.ts +5 -5
  33. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  34. package/dist-lib/hooks/useVoorraadData.js +127 -50
  35. package/dist-lib/integrations/supabase/auth-middleware.d.ts +662 -168
  36. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  37. package/dist-lib/integrations/supabase/client.d.ts +662 -168
  38. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  39. package/dist-lib/integrations/supabase/client.server.d.ts +662 -168
  40. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  41. package/dist-lib/integrations/supabase/types.d.ts +678 -163
  42. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  43. package/dist-lib/lib/floricode-required-features.functions.d.ts +662 -168
  44. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/floriday-client.d.ts +29 -12
  46. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  47. package/dist-lib/lib/floriday-client.js +36 -0
  48. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +662 -168
  49. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  50. package/dist-lib/lib/floriday-gateway.functions.d.ts +3220 -750
  51. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  52. package/dist-lib/lib/floriday-gateway.server.d.ts +14 -0
  53. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  54. package/dist-lib/lib/floriday-gateway.server.js +3 -0
  55. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  56. package/dist-lib/lib/floriday-payload.js +32 -8
  57. package/dist-lib/lib/floriday-warehouse.functions.d.ts +1932 -450
  58. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/floriday-writes.functions.d.ts +1333 -345
  60. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/merge-products.d.ts +8 -0
  62. package/dist-lib/lib/merge-products.d.ts.map +1 -0
  63. package/dist-lib/lib/merge-products.js +17 -0
  64. package/package.json +3 -3
@@ -8,7 +8,7 @@ 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, buildPrimaryPackingBase, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
11
+ import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, buildPrimaryPackingBase, emptyWizardData, floridayCacheToWizardData, isValidGtin13, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
12
12
  import { AdditionalPackagingDialog } from "./AdditionalPackagingDialog";
13
13
  import { publishCustomerOfferFn } from "../../lib/floriday-customer-offer.functions";
14
14
  import { upsertSupplyLineForTradeItem } from "../../lib/floriday-supply-line";
@@ -21,6 +21,7 @@ import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
21
21
  import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
22
22
  import { PriceStatusBadge } from "../../components/voorraad/PriceStatusBadge";
23
23
  import { OrderHistoryCard } from "../../components/voorraad/OrderHistoryCard";
24
+ import { ArtikelCompositionCard } from "../../components/voorraad/ArtikelCompositionCard";
24
25
  import { stockKeyFor } from "../../hooks/useTradeItemStock";
25
26
  const empty = emptyWizardData;
26
27
  const LOAD_CARRIER_LABELS = {
@@ -36,12 +37,12 @@ const STEPS = [
36
37
  { key: "artikel", label: "Artikel" },
37
38
  { key: "paspoort", label: "Plantenpaspoort", onlyForPlants: true },
38
39
  { key: "eigenschappen", label: "Eigenschappen" },
39
- { key: "klanten", label: "Klanten" },
40
+ { key: "klanten", label: "Klanten", onlyForCustomerSpecific: true },
40
41
  { key: "fotos", label: "Foto's" },
41
42
  { key: "warehouse", label: "Warehouse" },
42
43
  { key: "beladingen", label: "Beladingen" },
43
44
  { key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
44
- { key: "samenstelling", label: "Samenstelling", disabled: true },
45
+ { key: "samenstelling", label: "Samenstelling" },
45
46
  { key: "beschikbaarheid", label: "Verwachte beschikbaarheid" },
46
47
  { key: "aanbieden", label: "Aanbieden & voorraad" },
47
48
  ];
@@ -50,6 +51,8 @@ function visibleSteps(d) {
50
51
  const meta = s;
51
52
  if (meta.onlyForPlants && d.productGroup === "CUT_FLOWER")
52
53
  return false;
54
+ if (meta.onlyForCustomerSpecific && d.articleType !== "CUSTOMER_SPECIFIC")
55
+ return false;
53
56
  return true;
54
57
  });
55
58
  }
@@ -67,7 +70,7 @@ function warehouseSubLabel(raw) {
67
70
  const city = addr.city ?? "";
68
71
  return [line, city].filter(Boolean).join(", ") || undefined;
69
72
  }
70
- export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
73
+ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, } = {}) {
71
74
  const navigate = useNavigate();
72
75
  const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
73
76
  const { customers: floridayCustomers } = useFlorydayCustomers(activeId);
@@ -79,11 +82,13 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
79
82
  const [draftFloridayId, setDraftFloridayId] = useState(null);
80
83
  const [draftLoading, setDraftLoading] = useState(Boolean(initialDraftId));
81
84
  const [publishedLoading, setPublishedLoading] = useState(Boolean(tradeItemId));
85
+ const [productLoading, setProductLoading] = useState(Boolean(productId));
82
86
  const [syncState, setSyncState] = useState("idle");
83
87
  const [syncError, setSyncError] = useState(null);
84
88
  const isEditingDraft = Boolean(initialDraftId);
85
89
  const isEditingPublished = Boolean(tradeItemId);
86
- const isEditing = isEditingDraft || isEditingPublished;
90
+ const isEditingProduct = Boolean(productId);
91
+ const isEditing = isEditingDraft || isEditingPublished || isEditingProduct;
87
92
  const saveWarehousePreference = useCallback(async (targetTradeItemId, warehouseId) => {
88
93
  if (!activeId || !targetTradeItemId || !warehouseId)
89
94
  return;
@@ -147,6 +152,87 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
147
152
  cancelled = true;
148
153
  };
149
154
  }, [initialDraftId, activeId]);
155
+ // Load existing ERP product directly into the same Floriday field wizard.
156
+ useEffect(() => {
157
+ if (!productId)
158
+ return;
159
+ let cancelled = false;
160
+ setProductLoading(true);
161
+ (async () => {
162
+ try {
163
+ const { data: product, error } = await supabase
164
+ .from("products")
165
+ .select("id,product,barcode,article_number,quantity,sale_price,image_url,plants_per_crate,crates_per_layer,layers_per_cart,floriday_active,floriday_trade_item_id,floriday_payload")
166
+ .eq("id", productId)
167
+ .maybeSingle();
168
+ if (cancelled)
169
+ return;
170
+ if (error)
171
+ throw error;
172
+ if (!product) {
173
+ toast.error("Product niet gevonden");
174
+ return;
175
+ }
176
+ const barcode = String(product.barcode ?? "").trim();
177
+ const validGtin = isValidGtin13(barcode);
178
+ const imageUrl = String(product.image_url ?? "").trim();
179
+ const ppc = Number(product.plants_per_crate) || 0;
180
+ const cpl = Number(product.crates_per_layer) || 0;
181
+ const lpc = Number(product.layers_per_cart) || 0;
182
+ const payload = (product.floriday_payload ?? {});
183
+ const payloadHasPacking = Array.isArray(payload.packingConfigurations) && payload.packingConfigurations.length > 0;
184
+ const seededPacking = payloadHasPacking
185
+ ? payload.packingConfigurations
186
+ : ppc > 0 || cpl > 0 || lpc > 0
187
+ ? [
188
+ {
189
+ id: typeof crypto !== "undefined" && "randomUUID" in crypto
190
+ ? crypto.randomUUID()
191
+ : `pk-${Date.now()}`,
192
+ source: "VBN",
193
+ loadCarrier: "DANISH_TROLLEY",
194
+ packagingType: "",
195
+ unitsPerPackage: ppc || 1,
196
+ packagesPerLayer: cpl || 1,
197
+ layersPerLoadCarrier: lpc || 1,
198
+ isPrimary: true,
199
+ isCustomerSpecific: false,
200
+ customerOrganizationIds: [],
201
+ },
202
+ ]
203
+ : [];
204
+ setData({
205
+ ...empty,
206
+ ...payload,
207
+ name: product.product ?? payload.name ?? "",
208
+ articleCode: String(product.article_number ?? payload.articleCode ?? "").trim(),
209
+ gtin: validGtin ? barcode : (payload.gtin ?? ""),
210
+ salePricePerPiece: Number(product.sale_price ?? payload.salePricePerPiece) || 0,
211
+ saleStockPieces: Number(product.quantity ?? payload.saleStockPieces) || 0,
212
+ images: Array.isArray(payload.images) && payload.images.length > 0
213
+ ? payload.images
214
+ : imageUrl
215
+ ? [{ url: imageUrl, name: product.product ?? undefined, primary: true }]
216
+ : [],
217
+ linkedProductId: product.id,
218
+ packingConfigurations: seededPacking,
219
+ });
220
+ setDraftId(product.id);
221
+ setDraftFloridayId((product.floriday_trade_item_id ?? null));
222
+ }
223
+ catch (e) {
224
+ if (!cancelled)
225
+ toast.error(`Product laden mislukt: ${e.message}`);
226
+ }
227
+ finally {
228
+ if (!cancelled)
229
+ setProductLoading(false);
230
+ }
231
+ })();
232
+ return () => {
233
+ cancelled = true;
234
+ };
235
+ }, [productId]);
150
236
  // Load existing published trade item from the shared cache
151
237
  useEffect(() => {
152
238
  if (!tradeItemId || !activeId)
@@ -603,7 +689,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
603
689
  // wijzigt (o.a. beladingen) opgeslagen worden — anders raken tussentijdse
604
690
  // bewerkingen verloren zodra ze terug navigeren.
605
691
  if (!draftId && !vbnValid) {
606
- toast.error("Kies eerst een VBN-code voordat je een concept opslaat");
692
+ toast.error("Kies eerst een VBN-code voordat je een product opslaat");
607
693
  setStep(0);
608
694
  return;
609
695
  }
@@ -618,7 +704,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
618
704
  await saveWarehousePreference(draftFloridayId, data.warehouseId);
619
705
  }
620
706
  setDraftId(newId);
621
- toast.success(draftId ? "Concept bijgewerkt" : "Concept opgeslagen");
707
+ toast.success(draftId ? "Artikel bijgewerkt" : "Artikel opgeslagen");
622
708
  navigate({ to: "/voorraad" });
623
709
  }
624
710
  catch (e) {
@@ -638,7 +724,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
638
724
  useEffect(() => {
639
725
  if (!activeId)
640
726
  return;
641
- if (draftLoading || publishedLoading)
727
+ if (draftLoading || publishedLoading || productLoading)
642
728
  return;
643
729
  const pushTargetId = (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null);
644
730
  if (!draftId && !pushTargetId)
@@ -705,30 +791,36 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
705
791
  activeId,
706
792
  draftLoading,
707
793
  publishedLoading,
794
+ productLoading,
708
795
  isEditingPublished,
709
796
  saveWarehousePreference,
710
797
  tradeItemId,
711
798
  ]);
712
- if (connLoading || draftLoading || publishedLoading) {
799
+ if (connLoading || draftLoading || publishedLoading || productLoading) {
713
800
  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
714
801
  ? "Artikel laden..."
715
802
  : draftLoading
716
803
  ? "Lokaal artikel laden..."
717
- : "Floriday-connectie laden..."] }) }));
804
+ : productLoading
805
+ ? "Product laden..."
806
+ : "Floriday-connectie laden..."] }) }));
718
807
  }
719
808
  if (!activeConnection) {
720
809
  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." }) }));
721
810
  }
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) => {
811
+ 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" })), "Product opslaan"] })), (isEditingDraft || isEditingProduct) && (_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: "Product verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit product wordt permanent uit de voorraad verwijderd. Als het product actief op Floriday staat moet je het daar apart deactiveren." })] }), _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-muted text-muted-foreground", children: "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(ArtikelCompositionCard, { productId: linkedProductId, floridayId: tradeItemId ?? draftFloridayId ?? null }), _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) => {
723
812
  const isOpen = i === step;
724
813
  const isValid = stepValidity[i];
725
814
  const isVisited = i < step || isEditing;
726
- // Status: valid blauwe check; visited+invalid oranje !; untouched → grijs.
727
- const status = isValid
728
- ? "valid"
729
- : isVisited
730
- ? "invalid"
731
- : "untouched";
815
+ // Status: alleen valid tonen als de stap ook bezocht is; anders
816
+ // krijgen optioneel-gevalideerde stappen (jaarrond default, geen
817
+ // extra verpakking, samenstelling read-only) onterecht een vinkje
818
+ // voordat de gebruiker ze überhaupt heeft geopend.
819
+ const status = !isVisited
820
+ ? "untouched"
821
+ : isValid
822
+ ? "valid"
823
+ : "invalid";
732
824
  const isLastStep = i === steps.length - 1;
733
825
  const navDisabled = !isEditing && i > step && !firstStepUnlocked;
734
826
  const summary = summaryFor(s.key, data);
@@ -1221,18 +1313,51 @@ function CharacteristicField({ field, value, onChange, onRemove, }) {
1221
1313
  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." }))] }));
1222
1314
  }
1223
1315
  function StepKlanten({ data, update, floridayCustomers }) {
1316
+ const [search, setSearch] = useState("");
1317
+ const [onlySelected, setOnlySelected] = useState(false);
1318
+ const selectedSet = useMemo(() => new Set(data.customerOrganizationIds), [data.customerOrganizationIds]);
1319
+ const filtered = useMemo(() => {
1320
+ const q = search.trim().toLowerCase();
1321
+ return floridayCustomers.filter((c) => {
1322
+ if (onlySelected && !selectedSet.has(c.id))
1323
+ return false;
1324
+ if (!q)
1325
+ return true;
1326
+ const hay = [c.name, c.commercialName, c.city, c.country, c.gln, c.id]
1327
+ .filter(Boolean)
1328
+ .join(" ")
1329
+ .toLowerCase();
1330
+ return hay.includes(q);
1331
+ });
1332
+ }, [floridayCustomers, search, onlySelected, selectedSet]);
1224
1333
  if (floridayCustomers.length === 0) {
1225
1334
  return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen klantorganisaties geladen vanuit Floriday. Controleer de connectie of probeer later opnieuw." }));
1226
1335
  }
1227
1336
  const toggle = (id) => {
1228
- if (data.customerOrganizationIds.includes(id)) {
1337
+ if (selectedSet.has(id)) {
1229
1338
  update("customerOrganizationIds", data.customerOrganizationIds.filter((c) => c !== id));
1230
1339
  }
1231
1340
  else {
1232
1341
  update("customerOrganizationIds", [...data.customerOrganizationIds, id]);
1233
1342
  }
1234
1343
  };
1235
- return (_jsx("div", { className: "max-h-72 overflow-y-auto rounded-md border divide-y", children: floridayCustomers.map((c) => (_jsxs("label", { className: "flex items-start gap-3 px-3 py-2.5 text-sm hover:bg-muted cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: data.customerOrganizationIds.includes(c.id), onChange: () => toggle(c.id), className: "h-4 w-4 mt-0.5" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium truncate", children: c.name }), _jsx("div", { className: "text-[11px] font-mono text-muted-foreground truncate select-all", children: c.id })] })] }, c.id))) }));
1344
+ const initials = (name) => name
1345
+ .split(/\s+/)
1346
+ .filter(Boolean)
1347
+ .slice(0, 2)
1348
+ .map((w) => w[0]?.toUpperCase() ?? "")
1349
+ .join("") || "?";
1350
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[240px] flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, stad of GLN\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("button", { type: "button", onClick: () => setOnlySelected((v) => !v), className: `rounded-md border px-3 py-1.5 text-xs ${onlySelected
1351
+ ? "border-primary bg-primary/5 text-primary"
1352
+ : "hover:border-primary/40"}`, children: ["Alleen geselecteerd (", selectedSet.size, ")"] }), selectedSet.size > 0 && (_jsx("button", { type: "button", onClick: () => update("customerOrganizationIds", []), className: "rounded-md border px-3 py-1.5 text-xs text-destructive hover:border-destructive/40 hover:bg-destructive/5", children: "Selectie wissen" }))] }), _jsx("div", { className: "max-h-[420px] divide-y overflow-y-auto rounded-md border", children: filtered.length === 0 ? (_jsxs("div", { className: "p-4 text-center text-sm text-muted-foreground", children: ["Geen klanten gevonden voor \"", search, "\"."] })) : (filtered.map((c) => {
1353
+ const checked = selectedSet.has(c.id);
1354
+ const subtitle = [c.city, c.country].filter(Boolean).join(" · ");
1355
+ return (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors ${checked ? "bg-primary/5" : "hover:bg-muted/60"}`, children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => toggle(c.id), className: "h-4 w-4 shrink-0" }), c.logoUrl ? (
1356
+ // eslint-disable-next-line @next/next/no-img-element
1357
+ _jsx("img", { src: c.logoUrl, alt: "", className: "h-9 w-9 shrink-0 rounded-full border object-cover" })) : (_jsx("div", { className: "grid h-9 w-9 shrink-0 place-items-center rounded-full border bg-muted text-xs font-semibold text-muted-foreground", children: initials(c.name) })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium", children: c.name }), !c.hasRealName && (_jsx("span", { className: "shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground", children: "Onbekend" }))] }), (subtitle || c.commercialName) && (_jsx("div", { className: "truncate text-xs text-muted-foreground", children: c.commercialName && c.commercialName !== c.name
1358
+ ? `${c.commercialName}${subtitle ? ` · ${subtitle}` : ""}`
1359
+ : subtitle }))] }), c.gln && (_jsxs("span", { className: "ml-auto hidden shrink-0 font-mono text-[11px] text-muted-foreground sm:inline", children: ["GLN ", c.gln] }))] }, c.id));
1360
+ })) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [filtered.length, " van ", floridayCustomers.length, " klanten getoond"] })] }));
1236
1361
  }
1237
1362
  function StepFotos({ data, update, activeId }) {
1238
1363
  const inputRef = useRef(null);
@@ -1569,17 +1694,125 @@ function StepVerpakking({ data, update }) {
1569
1694
  }
1570
1695
  function StepBeschikbaarheid({ data, update }) {
1571
1696
  const weeks = Array.from({ length: 52 }, (_, i) => i + 1);
1572
- const toggleWeek = (w) => {
1573
- if (data.weeks.includes(w)) {
1574
- update("weeks", data.weeks.filter((x) => x !== w));
1697
+ // Drag-selectie: bij mousedown bepalen we of we in "select"- of "deselect"-
1698
+ // modus zitten (op basis van de startcel) en breiden we de selectie uit
1699
+ // terwijl de gebruiker over de weken sleept, zoals in Floriday.
1700
+ const dragStateRef = useRef(null);
1701
+ const [dragHover, setDragHover] = useState(null);
1702
+ const setSelection = (list) => {
1703
+ const unique = Array.from(new Set(list.filter((w) => w >= 1 && w <= 52)))
1704
+ .sort((a, b) => a - b);
1705
+ update("weeks", unique);
1706
+ };
1707
+ const applyDrag = (current) => {
1708
+ const state = dragStateRef.current;
1709
+ if (!state)
1710
+ return;
1711
+ const [a, b] = state.anchor <= current ? [state.anchor, current] : [current, state.anchor];
1712
+ const range = Array.from({ length: b - a + 1 }, (_, i) => a + i);
1713
+ if (state.mode === "select") {
1714
+ setSelection([...state.baseline, ...range]);
1575
1715
  }
1576
1716
  else {
1577
- update("weeks", [...data.weeks, w].sort((a, b) => a - b));
1717
+ const rm = new Set(range);
1718
+ setSelection(state.baseline.filter((w) => !rm.has(w)));
1578
1719
  }
1579
1720
  };
1580
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("button", { type: "button", onClick: () => update("yearRound", true), className: `rounded-md border px-3 py-1.5 text-sm ${data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Jaarrond" }), _jsx("button", { type: "button", onClick: () => update("yearRound", false), className: `rounded-md border px-3 py-1.5 text-sm ${!data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Specifieke periode" })] }), !data.yearRound && (_jsxs("div", { children: [_jsx(Label, { className: "mb-2 block", children: "Selecteer weken" }), _jsx("div", { className: "grid grid-cols-13 gap-1", style: { gridTemplateColumns: "repeat(13, minmax(0, 1fr))" }, children: weeks.map((w) => (_jsx("button", { type: "button", onClick: () => toggleWeek(w), className: `rounded border px-1 py-1 text-xs ${data.weeks.includes(w)
1581
- ? "border-primary bg-primary text-primary-foreground"
1582
- : "hover:border-primary/40"}`, children: w }, w))) })] }))] }));
1721
+ const onWeekMouseDown = (w, e) => {
1722
+ if (e.button !== 0)
1723
+ return;
1724
+ e.preventDefault();
1725
+ const mode = data.weeks.includes(w) ? "deselect" : "select";
1726
+ dragStateRef.current = { mode, anchor: w, baseline: data.weeks };
1727
+ setDragHover(w);
1728
+ applyDrag(w);
1729
+ };
1730
+ const onWeekMouseEnter = (w) => {
1731
+ if (!dragStateRef.current)
1732
+ return;
1733
+ setDragHover(w);
1734
+ applyDrag(w);
1735
+ };
1736
+ // Auto-scroll wanneer je tijdens het slepen dicht bij de linker- of
1737
+ // rechterrand van de scroll-container komt.
1738
+ const scrollRef = useRef(null);
1739
+ const autoScrollRafRef = useRef(null);
1740
+ const pointerXRef = useRef(null);
1741
+ useEffect(() => {
1742
+ const end = () => {
1743
+ dragStateRef.current = null;
1744
+ setDragHover(null);
1745
+ pointerXRef.current = null;
1746
+ if (autoScrollRafRef.current != null) {
1747
+ cancelAnimationFrame(autoScrollRafRef.current);
1748
+ autoScrollRafRef.current = null;
1749
+ }
1750
+ };
1751
+ const onMove = (e) => {
1752
+ if (!dragStateRef.current)
1753
+ return;
1754
+ pointerXRef.current = e.clientX;
1755
+ };
1756
+ window.addEventListener("mouseup", end);
1757
+ window.addEventListener("mouseleave", end);
1758
+ window.addEventListener("mousemove", onMove);
1759
+ return () => {
1760
+ window.removeEventListener("mouseup", end);
1761
+ window.removeEventListener("mouseleave", end);
1762
+ window.removeEventListener("mousemove", onMove);
1763
+ };
1764
+ }, []);
1765
+ const startAutoScrollLoop = () => {
1766
+ if (autoScrollRafRef.current != null)
1767
+ return;
1768
+ const EDGE = 60; // px vanaf de rand waar auto-scroll begint
1769
+ const MAX_SPEED = 18; // px per frame
1770
+ const step = () => {
1771
+ const el = scrollRef.current;
1772
+ const state = dragStateRef.current;
1773
+ const x = pointerXRef.current;
1774
+ if (!el || !state || x == null) {
1775
+ autoScrollRafRef.current = null;
1776
+ return;
1777
+ }
1778
+ const rect = el.getBoundingClientRect();
1779
+ let delta = 0;
1780
+ if (x < rect.left + EDGE) {
1781
+ const t = Math.min(1, (rect.left + EDGE - x) / EDGE);
1782
+ delta = -MAX_SPEED * t;
1783
+ }
1784
+ else if (x > rect.right - EDGE) {
1785
+ const t = Math.min(1, (x - (rect.right - EDGE)) / EDGE);
1786
+ delta = MAX_SPEED * t;
1787
+ }
1788
+ if (delta !== 0) {
1789
+ el.scrollLeft += delta;
1790
+ // Bepaal welke week nu onder de cursor ligt en pas de selectie aan
1791
+ // zodat het bereik meegroeit terwijl er wordt gescrolld.
1792
+ const target = document.elementFromPoint(x, rect.top + rect.height / 2);
1793
+ const weekAttr = target?.closest?.("[data-week]");
1794
+ const w = weekAttr ? Number(weekAttr.getAttribute("data-week")) : NaN;
1795
+ if (Number.isFinite(w)) {
1796
+ setDragHover(w);
1797
+ applyDrag(w);
1798
+ }
1799
+ }
1800
+ autoScrollRafRef.current = requestAnimationFrame(step);
1801
+ };
1802
+ autoScrollRafRef.current = requestAnimationFrame(step);
1803
+ };
1804
+ const onWeekMouseDownWithScroll = (w, e) => {
1805
+ onWeekMouseDown(w, e);
1806
+ pointerXRef.current = e.clientX;
1807
+ startAutoScrollLoop();
1808
+ };
1809
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("button", { type: "button", onClick: () => update("yearRound", true), className: `rounded-md border px-3 py-1.5 text-sm ${data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Jaarrond" }), _jsx("button", { type: "button", onClick: () => update("yearRound", false), className: `rounded-md border px-3 py-1.5 text-sm ${!data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Specifieke periode" })] }), !data.yearRound && (_jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [_jsx(Label, { className: "shrink-0", children: "Selecteer weken" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Sleep om meerdere weken te selecteren", data.weeks.length > 0 && ` — ${data.weeks.length} week/weken`] }), _jsx("button", { type: "button", onClick: () => setSelection([]), disabled: data.weeks.length === 0, className: "rounded-md border px-2.5 py-1 text-xs text-destructive hover:border-destructive/40 hover:bg-destructive/5 disabled:cursor-not-allowed disabled:opacity-40", children: "Selectie leegmaken" })] })] }), _jsx("div", { ref: scrollRef, className: "overflow-x-auto pb-2", children: _jsx("div", { className: "flex select-none gap-1 min-w-max", children: weeks.map((w) => {
1810
+ const selected = data.weeks.includes(w);
1811
+ const isHover = dragHover === w;
1812
+ return (_jsx("button", { type: "button", "data-week": w, onMouseDown: (e) => onWeekMouseDownWithScroll(w, e), onMouseEnter: () => onWeekMouseEnter(w), className: `h-9 w-11 shrink-0 rounded border text-xs transition-colors ${selected
1813
+ ? "border-primary bg-primary text-primary-foreground"
1814
+ : "hover:border-primary/40"} ${isHover ? "ring-2 ring-primary/40" : ""}`, children: w }, w));
1815
+ }) }) })] }))] }));
1583
1816
  }
1584
1817
  // ------------------------------------------------------------
1585
1818
  // Stap "Aanbieden & voorraad" — Direct Sale via CustomerOffer
@@ -1 +1 @@
1
- {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAeA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CAyHrD"}
1
+ {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAWA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CA4ErD"}
@@ -1,33 +1,40 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { useNavigate } from "@tanstack/react-router";
4
3
  import { Card } from "@flowselections/core";
5
4
  import { Loader2 } from "lucide-react";
6
5
  import { ArtikelWizard } from "./ArtikelWizard";
7
6
  import { supabase } from "../../integrations/supabase/client";
8
- import { useFloridayConnection } from "../../hooks/useFloridayConnection";
9
- import { upsertLocalTradeItem } from "../../hooks/useLocalTradeItems";
10
- import { emptyWizardData, isValidGtin13 } from "../../lib/floriday-payload";
11
7
  export function EditArtikelPage({ id }) {
12
8
  const [resolved, setResolved] = useState(null);
13
- const [promoting, setPromoting] = useState(false);
14
- const { activeId } = useFloridayConnection();
15
- const navigate = useNavigate();
16
9
  useEffect(() => {
17
10
  let cancelled = false;
18
11
  setResolved(null);
19
12
  (async () => {
20
- const draftRes = await supabase
21
- .from("trade_item_drafts")
13
+ // 1. Volwaardig product (canonieke bron sinds de dedup-migratie).
14
+ const prodRes = await supabase
15
+ .from("products")
22
16
  .select("id")
23
17
  .eq("id", id)
24
18
  .maybeSingle();
25
19
  if (cancelled)
26
20
  return;
27
- if (draftRes.data) {
28
- setResolved({ kind: "draft", id });
21
+ if (prodRes.data) {
22
+ setResolved({ kind: "product", id });
29
23
  return;
30
24
  }
25
+ // 2. Product met dit Floriday-artikel-ID.
26
+ const byFloriday = await supabase
27
+ .from("products")
28
+ .select("id")
29
+ .eq("floriday_trade_item_id", id)
30
+ .maybeSingle();
31
+ if (cancelled)
32
+ return;
33
+ if (byFloriday.data?.id) {
34
+ setResolved({ kind: "product", id: byFloriday.data.id });
35
+ return;
36
+ }
37
+ // 3. Alleen nog in de Floriday-cache aanwezig (nog niet lokaal gespiegeld).
31
38
  const pubRes = await supabase
32
39
  .from("floriday_trade_items_cache")
33
40
  .select("floriday_id")
@@ -39,87 +46,23 @@ export function EditArtikelPage({ id }) {
39
46
  setResolved({ kind: "published", id });
40
47
  return;
41
48
  }
42
- // Fallback: ERP-only product → promote to editable draft transparently.
43
- const prodRes = await supabase
44
- .from("products")
45
- .select("id,product,barcode,quantity,sale_price")
46
- .eq("id", id)
47
- .maybeSingle();
48
- if (cancelled)
49
- return;
50
- if (!prodRes.data) {
51
- setResolved({ kind: "notfound" });
52
- return;
53
- }
54
- if (!activeId) {
55
- // No Floriday connection yet — cannot create a draft (connection_id required).
56
- setResolved({ kind: "notfound" });
57
- return;
58
- }
59
- setPromoting(true);
60
- try {
61
- // Look for an existing draft already linked to this product.
62
- const existing = await supabase
63
- .from("trade_item_drafts")
64
- .select("id,data")
65
- .eq("connection_id", activeId)
66
- .contains("data", { linkedProductId: id })
67
- .maybeSingle();
68
- if (cancelled)
69
- return;
70
- if (existing.data) {
71
- navigate({
72
- to: "/voorraad/$id",
73
- params: { id: existing.data.id },
74
- replace: true,
75
- });
76
- return;
77
- }
78
- const prod = prodRes.data;
79
- const barcode = String(prod.barcode ?? "").trim();
80
- const validGtin = isValidGtin13(barcode);
81
- const seed = {
82
- ...emptyWizardData,
83
- name: prod.product ?? "",
84
- articleCode: validGtin ? "" : barcode,
85
- gtin: validGtin ? barcode : "",
86
- salePricePerPiece: Number(prod.sale_price) || 0,
87
- saleStockPieces: Number(prod.quantity) || 0,
88
- linkedProductId: id,
89
- };
90
- const newId = await upsertLocalTradeItem({
91
- connectionId: activeId,
92
- data: seed,
93
- });
94
- if (cancelled)
95
- return;
96
- navigate({
97
- to: "/voorraad/$id",
98
- params: { id: newId },
99
- replace: true,
100
- });
101
- }
102
- catch {
103
- if (!cancelled)
104
- setResolved({ kind: "notfound" });
105
- }
106
- finally {
107
- if (!cancelled)
108
- setPromoting(false);
109
- }
49
+ setResolved({
50
+ kind: "notfound",
51
+ reason: "Dit artikel bestaat niet (meer) in de voorraad.",
52
+ });
110
53
  })();
111
54
  return () => {
112
55
  cancelled = true;
113
56
  };
114
- }, [id, activeId, navigate]);
115
- if (!resolved || promoting) {
116
- 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" }), promoting ? "Artikel voorbereiden voor bewerken..." : "Artikel zoeken..."] }) }));
57
+ }, [id]);
58
+ if (!resolved) {
59
+ 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" }), "Artikel laden\u2026"] }) }));
117
60
  }
118
61
  if (resolved.kind === "notfound") {
119
- return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6 text-sm", children: "Artikel niet gevonden. Het is mogelijk verwijderd of nog niet gesynchroniseerd vanuit Floriday." }) }));
62
+ return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 text-sm space-y-1", children: [_jsx("div", { children: "Artikel niet gevonden." }), resolved.reason && (_jsx("div", { className: "text-muted-foreground", children: resolved.reason }))] }) }));
120
63
  }
121
- if (resolved.kind === "draft") {
122
- return _jsx(ArtikelWizard, { draftId: resolved.id });
64
+ if (resolved.kind === "product") {
65
+ return _jsx(ArtikelWizard, { productId: resolved.id });
123
66
  }
124
67
  return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
125
68
  }
@@ -0,0 +1,4 @@
1
+ export declare function FloridayStatusBadge({ active }: {
2
+ active: boolean | undefined;
3
+ }): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=FloridayStatusBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloridayStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayStatusBadge.tsx"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,2CAa9E"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Badge } from "@flowselections/core";
3
+ export function FloridayStatusBadge({ active }) {
4
+ if (active) {
5
+ return (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Actief op Floriday" }));
6
+ }
7
+ return (_jsx(Badge, { className: "bg-slate-500/15 text-slate-700 border-slate-500/30", children: "Niet actief op Floriday" }));
8
+ }