@flowselections/floriday-voorraad 1.0.110 → 1.0.115

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 (44) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +63 -7
  3. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/FustenPage.js +10 -6
  5. package/dist-lib/components/voorraad/PriceStatusBadge.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/PriceStatusBadge.js +6 -1
  7. package/dist-lib/components/voorraad/PublishProgressCard.d.ts +5 -0
  8. package/dist-lib/components/voorraad/PublishProgressCard.d.ts.map +1 -1
  9. package/dist-lib/components/voorraad/PublishProgressCard.js +16 -1
  10. package/dist-lib/components/voorraad/VoorraadPage.d.ts +13 -0
  11. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  12. package/dist-lib/components/voorraad/VoorraadPage.js +36 -54
  13. package/dist-lib/hooks/useNoPriceProducts.d.ts +10 -0
  14. package/dist-lib/hooks/useNoPriceProducts.d.ts.map +1 -0
  15. package/dist-lib/hooks/useNoPriceProducts.js +56 -0
  16. package/dist-lib/hooks/useStockPushJobs.d.ts.map +1 -1
  17. package/dist-lib/hooks/useStockPushJobs.js +6 -1
  18. package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -1
  19. package/dist-lib/hooks/useStockSyncHealth.js +2 -9
  20. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  21. package/dist-lib/hooks/useVoorraadData.js +45 -24
  22. package/dist-lib/integrations/supabase/types.d.ts +818 -122
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/integrations/supabase/types.js +1 -0
  25. package/dist-lib/lib/__tests__/fust-dimensions.test.d.ts +2 -0
  26. package/dist-lib/lib/__tests__/fust-dimensions.test.d.ts.map +1 -0
  27. package/dist-lib/lib/__tests__/fust-dimensions.test.js +27 -0
  28. package/dist-lib/lib/__tests__/publish-114.test.d.ts +2 -0
  29. package/dist-lib/lib/__tests__/publish-114.test.d.ts.map +1 -0
  30. package/dist-lib/lib/__tests__/publish-114.test.js +31 -0
  31. package/dist-lib/lib/__tests__/request-guard.test.d.ts +2 -0
  32. package/dist-lib/lib/__tests__/request-guard.test.d.ts.map +1 -0
  33. package/dist-lib/lib/__tests__/request-guard.test.js +50 -0
  34. package/dist-lib/lib/fusten.d.ts +14 -3
  35. package/dist-lib/lib/fusten.d.ts.map +1 -1
  36. package/dist-lib/lib/fusten.js +38 -7
  37. package/dist-lib/lib/publish-errors.d.ts +6 -1
  38. package/dist-lib/lib/publish-errors.d.ts.map +1 -1
  39. package/dist-lib/lib/publish-errors.js +22 -2
  40. package/dist-lib/lib/request-guard.d.ts +46 -0
  41. package/dist-lib/lib/request-guard.d.ts.map +1 -0
  42. package/dist-lib/lib/request-guard.js +100 -0
  43. package/package.json +1 -1
  44. package/sql/floriday-voorraad-schema.sql +186 -27
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA+MA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,+BA0tEL"}
1
+ {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA+MA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,+BAwwEL"}
@@ -1195,6 +1195,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1195
1195
  }
1196
1196
  // Publiceren begint altijd met een lokale opslag, zodat een afgebroken of
1197
1197
  // mislukte publicatie nooit tot verlies van ingevoerde gegevens leidt.
1198
+ let savedLocalId = draftId ?? null;
1198
1199
  try {
1199
1200
  const localId = await upsertLocalTradeItem({
1200
1201
  id: draftId,
@@ -1203,6 +1204,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1203
1204
  floridayTradeItemId: (publishedTradeItemId || draftFloridayId || null),
1204
1205
  sync: { dirty: true, last_error: null },
1205
1206
  });
1207
+ if (localId)
1208
+ savedLocalId = localId;
1206
1209
  if (localId && localId !== draftId)
1207
1210
  setDraftId(localId);
1208
1211
  }
@@ -1210,6 +1213,50 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1210
1213
  toast.error(`Lokaal opslaan mislukt: ${e.message}`);
1211
1214
  return;
1212
1215
  }
1216
+ /**
1217
+ * Na een mislukte eerste publicatie: zet de ingevoerde voorraad en prijs op
1218
+ * het lokale artikel, alleen als daar nog niets staat (nooit overschrijven).
1219
+ */
1220
+ const keepLocalInputAfterFailure = async () => {
1221
+ if (!savedLocalId)
1222
+ return;
1223
+ try {
1224
+ const { data: prod } = await supabase
1225
+ .from("products")
1226
+ .select("sale_price")
1227
+ .eq("id", savedLocalId)
1228
+ .maybeSingle();
1229
+ if (data.salePricePerPiece > 0 && !(Number(prod?.sale_price) > 0)) {
1230
+ await supabase
1231
+ .from("products")
1232
+ .update({ sale_price: data.salePricePerPiece })
1233
+ .eq("id", savedLocalId);
1234
+ }
1235
+ if (data.saleStockPieces > 0) {
1236
+ const { data: cur } = await supabase.rpc("get_product_stock", {
1237
+ p_product_id: savedLocalId,
1238
+ });
1239
+ const row = Array.isArray(cur) ? cur[0] : cur;
1240
+ if (!(Number(row?.physical_stock) > 0)) {
1241
+ const { error } = await supabase.rpc("set_product_stock", {
1242
+ p_product_id: savedLocalId,
1243
+ p_physical_stock: Math.trunc(data.saleStockPieces),
1244
+ p_minimum_stock: null,
1245
+ p_incoming_stock: null,
1246
+ p_notes: "Ingevoerd in wizard; publiceren mislukt",
1247
+ p_change_source: "NEW_ARTICLE",
1248
+ });
1249
+ if (error)
1250
+ throw new Error(error.message);
1251
+ }
1252
+ }
1253
+ }
1254
+ catch (e) {
1255
+ toast.error("Voorraad of prijs kon niet lokaal bewaard worden", {
1256
+ description: e.message,
1257
+ });
1258
+ }
1259
+ };
1213
1260
  try {
1214
1261
  const serverConnection = await getFloridayReadinessFn();
1215
1262
  const browserEnvironment = activeConnection?.active_environment;
@@ -1304,6 +1351,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1304
1351
  return { tradeItemId: createdId };
1305
1352
  }, stepOpts);
1306
1353
  if (!core.ok) {
1354
+ // 1.0.114: lokale invoer nooit verliezen door een mislukte publicatie.
1355
+ if (!publishedTradeItemId)
1356
+ await keepLocalInputAfterFailure();
1307
1357
  await handleStepFailure(core.error);
1308
1358
  if (jobId)
1309
1359
  await finishPublishJob(jobId);
@@ -2592,9 +2642,10 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
2592
2642
  source: "VBN",
2593
2643
  loadCarrier: "AUCTION_TROLLEY",
2594
2644
  packagingType: "",
2595
- unitsPerPackage: 1,
2596
- packagesPerLayer: 1,
2597
- layersPerLoadCarrier: 1,
2645
+ // Geen standaardwaarden: de gebruiker vult alle drie aantallen zelf in.
2646
+ unitsPerPackage: 0,
2647
+ packagesPerLayer: 0,
2648
+ layersPerLoadCarrier: 0,
2598
2649
  isCustomerSpecific: false,
2599
2650
  customerOrganizationIds: [],
2600
2651
  isPrimary: isFirst,
@@ -2635,7 +2686,12 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
2635
2686
  const materialInvalid = draft.source === "SUPPLIER" &&
2636
2687
  !!(draft.packagingMaterialId ?? "").trim() &&
2637
2688
  !materialIsUuid;
2638
- const canSave = draft.loadCarrier.length > 0 &&
2689
+ // 1.0.114: aantallen worden nooit vooraf ingevuld; alle drie verplicht ≥ 1.
2690
+ const countsComplete = draft.unitsPerPackage >= 1 &&
2691
+ draft.packagesPerLayer >= 1 &&
2692
+ draft.layersPerLoadCarrier >= 1;
2693
+ const canSave = countsComplete &&
2694
+ draft.loadCarrier.length > 0 &&
2639
2695
  (hasCustomPkg || draft.packagingType.length > 0) &&
2640
2696
  !materialInvalid &&
2641
2697
  (draft.source === "VBN" || Boolean(draft.packagingSupplierId)) &&
@@ -2693,7 +2749,7 @@ function PackingConfigDialog({ open, onOpenChange, initial, isFirst, packagingTy
2693
2749
  ? (packagingTypes.find((r) => codeOf(r) === draft.packagingType)
2694
2750
  ? `${labelOf(packagingTypes.find((r) => codeOf(r) === draft.packagingType))} (${draft.packagingType})`
2695
2751
  : draft.packagingType)
2696
- : "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) => {
2752
+ : "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, placeholder: "Invullen", value: draft.unitsPerPackage > 0 ? draft.unitsPerPackage : "", onChange: (e) => patch("unitsPerPackage", Math.max(0, Math.floor(Number(e.target.value) || 0))) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Fusten per laag" }), _jsx(Input, { type: "number", min: 1, placeholder: "Invullen", value: draft.packagesPerLayer > 0 ? draft.packagesPerLayer : "", onChange: (e) => patch("packagesPerLayer", Math.max(0, Math.floor(Number(e.target.value) || 0))) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Lagen per drager" }), _jsx(Input, { type: "number", min: 1, placeholder: "Invullen", value: draft.layersPerLoadCarrier > 0 ? draft.layersPerLoadCarrier : "", onChange: (e) => patch("layersPerLoadCarrier", Math.max(0, Math.floor(Number(e.target.value) || 0))) })] }), !countsComplete && (_jsx("p", { className: "col-span-3 text-xs text-muted-foreground", children: "Vul stuks per fust, fusten per laag en lagen per drager in; er wordt niets vooraf ingevuld." }))] }), _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) => {
2697
2753
  onUploadPhoto(e.target.files?.[0]);
2698
2754
  e.target.value = "";
2699
2755
  } })] }), (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) => ({
@@ -2859,9 +2915,9 @@ function StepAanbieden({ data, update, priceGroups, parentProductId }) {
2859
2915
  }
2860
2916
  update("priceGroupPrices", next);
2861
2917
  };
2862
- 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 basisprijs per stuk en publiceren de opgegeven voorraad via de continuous-stock feed. Prijzen per prijsgroep worden als aparte", " ", _jsx("code", { children: "weekly-supply-lines" }), " naar Floriday gepusht."] }), _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
2918
+ return (_jsxs("div", { className: "space-y-6", children: [_jsx("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. Na publiceren staat het artikel bij Floriday, krijgen je klanten een aanbod met de basisprijs per stuk, en wordt je voorraad doorgegeven. Prijzen per prijsgroep worden apart doorgegeven als prijs per week." }), _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
2863
2919
  ? "Aanbod wordt aangemaakt direct na publiceren."
2864
- : "Alleen artikel publiceren, geen aanbod aanmaken." })] }), _jsx(Switch, { checked: data.offerForSale, onCheckedChange: (v) => update("offerForSale", Boolean(v)) })] }), data.offerForSale && (_jsxs(_Fragment, { children: [_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: "Basisprijs per stuk (EUR, excl. btw)" }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", value: priceStr, onChange: (e) => update("salePricePerPiece", Number(e.target.value) || 0) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Wordt gepusht als ", _jsx("code", { children: "weekly-base-supply" }), " \u2014 de standaard catalogusprijs voor klanten zonder eigen prijsgroep."] })] }), _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
2920
+ : "Alleen artikel publiceren, geen aanbod aanmaken." })] }), _jsx(Switch, { checked: data.offerForSale, onCheckedChange: (v) => update("offerForSale", Boolean(v)) })] }), data.offerForSale && (_jsxs(_Fragment, { children: [_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 liggende voorraad geboekt en daarna aan Floriday doorgegeven. Mislukt het publiceren, dan blijft dit aantal hier bewaard." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Basisprijs per stuk (EUR, excl. btw)" }), _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: "De standaardprijs per week voor klanten zonder eigen prijsgroep. Prijzen per prijsgroep vul je hieronder apart in." })] }), _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
2865
2921
  ? `${data.customerOrganizationIds.length} geselecteerde klant(en)`
2866
2922
  : "Alle gekoppelde Floriday-klanten" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Klantselectie beheer je in de stap ", _jsx("em", { children: "Klanten" }), "."] })] })] }), _jsxs("div", { className: "space-y-2 rounded-md border p-4", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Prijzen per Floriday-prijsgroep" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Laat leeg om voor die groep g\u00E9\u00E9n afwijkende prijs te publiceren. Basisprijs blijft dan gelden." })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [priceGroups.length, " groep", priceGroups.length === 1 ? "" : "en"] })] }), priceGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4", children: "Geen actieve prijsgroepen gevonden voor deze Floriday-koppeling." })) : (_jsx("div", { className: "grid gap-3 sm:grid-cols-2 pt-2", children: priceGroups.map((g) => {
2867
2923
  const val = data.priceGroupPrices?.[g.id];
@@ -1 +1 @@
1
- {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AA4KA,wBAAgB,UAAU,gCA6PzB"}
1
+ {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAgLA,wBAAgB,UAAU,gCAiQzB"}
@@ -5,30 +5,30 @@ import { Button, Card, Input, Badge, toast } from "@flowselections/core";
5
5
  import { supabase } from "../../integrations/supabase/client.js";
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
7
7
  import { useFusten } from "../../hooks/useFusten.js";
8
- import { fetchFloricodeFustMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
8
+ import { fetchFloricodeFustMap, fetchFloridayCustomPackageMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
9
9
  /** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
10
- function DimensionCell({ fust, floricode, onSave, }) {
10
+ function DimensionCell({ fust, floricode, customPackages, onSave, }) {
11
11
  const [editing, setEditing] = useState(false);
12
12
  const [draft, setDraft] = useState({
13
13
  length_mm: fust.length_mm?.toString() ?? "",
14
14
  width_mm: fust.width_mm?.toString() ?? "",
15
15
  height_mm: fust.height_mm?.toString() ?? "",
16
16
  });
17
- const resolved = resolveFustDimensions(fust, floricode);
17
+ const resolved = resolveFustDimensions(fust, floricode, customPackages);
18
18
  const label = formatFustDimensions(resolved);
19
19
  const toNum = (v) => {
20
20
  const n = Math.trunc(Number(v));
21
21
  return Number.isFinite(n) && n > 0 ? n : null;
22
22
  };
23
23
  if (!editing) {
24
- return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Floricode" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
24
+ return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Floricode" : resolved.source === "floriday" ? "Floriday eigen fust" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
25
25
  setDraft({
26
26
  length_mm: fust.length_mm?.toString() ?? "",
27
27
  width_mm: fust.width_mm?.toString() ?? "",
28
28
  height_mm: fust.height_mm?.toString() ?? "",
29
29
  });
30
30
  setEditing(true);
31
- }, children: resolved.source === "handmatig" ? "Wijzigen" : resolved.source === "floricode" ? "Handmatig aanvullen" : "Afmeting toevoegen" })] }));
31
+ }, children: resolved.source === "handmatig" ? "Wijzigen" : resolved.source === "floricode" || resolved.source === "floriday" ? "Handmatig aanvullen" : "Afmeting toevoegen" })] }));
32
32
  }
33
33
  return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1", children: [["length_mm", "width_mm", "height_mm"].map((k, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [i > 0 && _jsx("span", { className: "text-muted-foreground", children: "\u00D7" }), _jsx(Input, { type: "number", className: "h-8 w-20 text-right", placeholder: ["L", "B", "H"][i], value: draft[k], onChange: (e) => setDraft((d) => ({ ...d, [k]: e.target.value })) })] }, k))), _jsx("span", { className: "text-xs text-muted-foreground", children: "mm" })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", className: "h-7", onClick: async () => {
34
34
  await onSave({
@@ -73,7 +73,11 @@ export function FustenPage() {
73
73
  fetchFloricodeFustMap()
74
74
  .then((m) => { setFloricode(m); setFloricodeError(null); })
75
75
  .catch((e) => setFloricodeError(e.message));
76
+ fetchFloridayCustomPackageMap()
77
+ .then(setCustomPackages)
78
+ .catch(() => setCustomPackages(new Map()));
76
79
  }, []);
80
+ const [customPackages, setCustomPackages] = useState(null);
77
81
  const [busyId, setBusyId] = useState(null);
78
82
  const [amounts, setAmounts] = useState({});
79
83
  const [negative, setNegative] = useState([]);
@@ -115,7 +119,7 @@ export function FustenPage() {
115
119
  void loadNegative();
116
120
  }
117
121
  };
118
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer de ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [n.name.trim(), ": tekort van ", n.shortage.toLocaleString("nl-NL"), " fusten", n.duplicate_names > 0 ? " (dit fusttype bestaat meerdere keren)" : ""] }, n.fust_type_id))) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, floricode: floricode, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
122
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer de ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [n.name.trim(), ": tekort van ", n.shortage.toLocaleString("nl-NL"), " fusten", n.duplicate_names > 0 ? " (dit fusttype bestaat meerdere keren)" : ""] }, n.fust_type_id))) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, floricode: floricode, customPackages: customPackages, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
119
123
  ? "text-rose-600"
120
124
  : ""}`, children: f.stock })) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
121
125
  const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
@@ -1 +1 @@
1
- {"version":3,"file":"PriceStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PriceStatusBadge.tsx"],"names":[],"mappings":"AAuFA,KAAK,KAAK,GAAG;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,KAAK,sCAmG/E"}
1
+ {"version":3,"file":"PriceStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PriceStatusBadge.tsx"],"names":[],"mappings":"AA2FA,KAAK,KAAK,GAAG;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,KAAK,sCAmG/E"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isTabHidden } from "../../lib/request-guard.js";
2
3
  import { CircleDollarSign, Clock, MinusCircle, Loader2 } from "lucide-react";
3
4
  import { useEffect, useState } from "react";
4
5
  import { useTradeItemSupplyLine } from "../../hooks/useTradeItemSupplyLine.js";
@@ -36,7 +37,11 @@ function useLastSentPrice(tradeItemId, productId) {
36
37
  : null);
37
38
  };
38
39
  void load();
39
- const t = setInterval(load, 30000);
40
+ // Alleen opvragen als het tabblad zichtbaar is (1.0.111).
41
+ const t = setInterval(() => {
42
+ if (!isTabHidden())
43
+ void load();
44
+ }, 30000);
40
45
  return () => {
41
46
  cancelled = true;
42
47
  clearInterval(t);
@@ -9,4 +9,9 @@ export declare function PublishProgressCard({ job, steps, onRetry, retrying, onF
9
9
  retrying?: boolean;
10
10
  onFixStep?: (stepKey: string) => void;
11
11
  }): import("react").JSX.Element | null;
12
+ /**
13
+ * Een job die nog op "running" staat terwijl geen stap meer loopt en er een
14
+ * stap mislukt is, telt als mislukt (scherm nooit blijven hangen op "Bezig").
15
+ */
16
+ export declare function effectiveJobStatus(jobStatus: string, steps: Array<Pick<PublishStepRow, "status" | "step_key">>): string;
12
17
  //# sourceMappingURL=PublishProgressCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PublishProgressCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishProgressCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAoBhF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE;IACD,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,sCAyDA"}
1
+ {"version":3,"file":"PublishProgressCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublishProgressCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAoBhF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE;IACD,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,sCA0DA;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC,GACxD,MAAM,CAMR"}
@@ -26,5 +26,20 @@ export function PublishProgressCard({ job, steps, onRetry, retrying, onFixStep,
26
26
  return null;
27
27
  const visible = steps.filter((s) => s.status !== "skipped");
28
28
  const failed = visible.filter((s) => s.status === "failed");
29
- return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: JOB_TITLE[job.status] ?? "Publicatie" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Referentie ", job.reference_code] })] }), job.status !== "running" && failed.length > 0 && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onRetry, disabled: retrying, children: [retrying ? (_jsx(Loader2, { className: "mr-1 h-3 w-3 animate-spin" })) : (_jsx(RefreshCw, { className: "mr-1 h-3 w-3" })), "Opnieuw proberen"] }))] }), _jsx("ul", { className: "space-y-2", children: visible.map((s) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx("span", { className: "mt-0.5", children: _jsx(StatusIcon, { status: s.status }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: s.status === "done" ? "text-muted-foreground" : "", children: s.label }), s.status === "failed" && s.error_message && (_jsx("div", { className: "text-xs text-destructive", children: s.error_message }))] }), s.status === "failed" && onFixStep && (_jsx(Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onFixStep(s.step_key), children: "Oplossen" }))] }, s.id))) })] }));
29
+ const status = effectiveJobStatus(job.status, visible);
30
+ return (_jsxs(Card, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: JOB_TITLE[status] ?? "Publicatie" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Referentie ", job.reference_code] })] }), status !== "running" && failed.length > 0 && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onRetry, disabled: retrying, children: [retrying ? (_jsx(Loader2, { className: "mr-1 h-3 w-3 animate-spin" })) : (_jsx(RefreshCw, { className: "mr-1 h-3 w-3" })), "Opnieuw proberen"] }))] }), _jsx("ul", { className: "space-y-2", children: visible.map((s) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx("span", { className: "mt-0.5", children: _jsx(StatusIcon, { status: s.status }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: s.status === "done" ? "text-muted-foreground" : "", children: s.label }), s.status === "failed" && s.error_message && (_jsx("div", { className: "text-xs text-destructive", children: s.error_message }))] }), s.status === "failed" && onFixStep && (_jsx(Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onFixStep(s.step_key), children: "Oplossen" }))] }, s.id))) })] }));
31
+ }
32
+ /**
33
+ * Een job die nog op "running" staat terwijl geen stap meer loopt en er een
34
+ * stap mislukt is, telt als mislukt (scherm nooit blijven hangen op "Bezig").
35
+ */
36
+ export function effectiveJobStatus(jobStatus, steps) {
37
+ if (jobStatus !== "running")
38
+ return jobStatus;
39
+ const anyRunning = steps.some((s) => s.status === "running");
40
+ if (anyRunning)
41
+ return jobStatus;
42
+ if (steps.some((s) => s.status === "failed" && s.step_key === "trade-item"))
43
+ return "failed";
44
+ return jobStatus;
30
45
  }
@@ -1,2 +1,15 @@
1
+ import type { TradeItem } from "../../lib/floriday-client.js";
2
+ /**
3
+ * 1.0.114: "Op voorraad" telt liggende voorraad (fysiek > 0), niet vrij
4
+ * beschikbaar. Reserveringen voor orders op "Nog te picken" lieten anders alles
5
+ * als "Niet op voorraad" zien zonder dat er iets was afgeboekt.
6
+ */
7
+ export declare function countStock(items: Array<Pick<TradeItem, "quantity" | "availableQuantity" | "minQuantity">>): {
8
+ total: number;
9
+ inStock: number;
10
+ outOfStock: number;
11
+ belowMin: number;
12
+ fullyReserved: number;
13
+ };
1
14
  export declare function VoorraadPage(): import("react").JSX.Element;
2
15
  //# sourceMappingURL=VoorraadPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA2GA,wBAAgB,YAAY,gCAsX3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAiF3D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;EAczG;AAED,wBAAgB,YAAY,gCAiW3B"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useState } from "react";
2
+ import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
+ import { useNoPriceProducts } from "../../hooks/useNoPriceProducts.js";
4
+ import { pageRequestGuard } from "../../lib/request-guard.js";
5
+ const EMPTY_TRIPS = [];
3
6
  import { Link } from "@tanstack/react-router";
4
7
  import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, useAuthState, } from "@flowselections/core";
5
8
  import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
6
9
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
7
10
  import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
8
- import { supabase } from "../../integrations/supabase/client.js";
9
11
  import { useVoorraadData } from "../../hooks/useVoorraadData.js";
10
12
  import { StockSyncStatus } from "./StockSyncStatus.js";
11
13
  import { StockIntegrityAlert } from "./StockIntegrityAlert.js";
@@ -42,6 +44,30 @@ function KpiCard({ icon, label, count, tone, active, onClick, }) {
42
44
  function availableOf(i) {
43
45
  return i.availableQuantity ?? i.quantity ?? 0;
44
46
  }
47
+ /**
48
+ * 1.0.114: "Op voorraad" telt liggende voorraad (fysiek > 0), niet vrij
49
+ * beschikbaar. Reserveringen voor orders op "Nog te picken" lieten anders alles
50
+ * als "Niet op voorraad" zien zonder dat er iets was afgeboekt.
51
+ */
52
+ export function countStock(items) {
53
+ let inStock = 0;
54
+ let outOfStock = 0;
55
+ let belowMin = 0;
56
+ let fullyReserved = 0;
57
+ for (const i of items) {
58
+ const physical = Number(i.quantity ?? 0);
59
+ const avail = availableOf(i);
60
+ if (physical > 0)
61
+ inStock++;
62
+ else
63
+ outOfStock++;
64
+ if (physical > 0 && avail <= 0)
65
+ fullyReserved++;
66
+ if ((i.minQuantity ?? 0) > 0 && avail < (i.minQuantity ?? 0))
67
+ belowMin++;
68
+ }
69
+ return { total: items.length, inStock, outOfStock, belowMin, fullyReserved };
70
+ }
45
71
  export function VoorraadPage() {
46
72
  const auth = useAuthState();
47
73
  const conn = useFloridayConnection();
@@ -58,41 +84,10 @@ export function VoorraadPage() {
58
84
  const [drawerIds, setDrawerIds] = useState([]);
59
85
  const [drawerOpen, setDrawerOpen] = useState(false);
60
86
  // Artikelen zonder geldige prijs per stuk (eenheid "stuks", prijs > 0), op id.
61
- const [noPriceIds, setNoPriceIds] = useState(new Set());
62
- const [noPriceBreakdown, setNoPriceBreakdown] = useState({ empty: 0, zero: 0, unit: 0 });
87
+ // Los van de identiteit van `items`: dat effect veroorzaakte een opvraag-lus (1.0.111).
88
+ const { noPriceIds, noPriceBreakdown } = useNoPriceProducts();
63
89
  const [noPriceOnly, setNoPriceOnly] = useState(false);
64
- useEffect(() => {
65
- let cancelled = false;
66
- void supabase
67
- .from("products")
68
- .select("id, unit, sale_price")
69
- .then(({ data }) => {
70
- if (cancelled)
71
- return;
72
- const s = new Set();
73
- const b = { empty: 0, zero: 0, unit: 0 };
74
- for (const p of (data ?? [])) {
75
- const isPiece = (p.unit ?? "").trim().toLowerCase() === "stuks";
76
- if (!isPiece) {
77
- s.add(p.id);
78
- b.unit++;
79
- }
80
- else if (p.sale_price == null) {
81
- s.add(p.id);
82
- b.empty++;
83
- }
84
- else if (!(Number(p.sale_price) > 0)) {
85
- s.add(p.id);
86
- b.zero++;
87
- }
88
- }
89
- setNoPriceIds(s);
90
- setNoPriceBreakdown(b);
91
- });
92
- return () => {
93
- cancelled = true;
94
- };
95
- }, [items]);
90
+ const guardTrips = useSyncExternalStore(pageRequestGuard.subscribe, () => pageRequestGuard.trips(), () => EMPTY_TRIPS);
96
91
  const toggleSelect = (id) => setSelected((cur) => {
97
92
  const next = new Set(cur);
98
93
  if (next.has(id))
@@ -109,30 +104,17 @@ export function VoorraadPage() {
109
104
  setDrawerIds(ids);
110
105
  setDrawerOpen(true);
111
106
  };
112
- const counts = useMemo(() => {
113
- let inStock = 0;
114
- let outOfStock = 0;
115
- let belowMin = 0;
116
- for (const i of items) {
117
- const avail = availableOf(i);
118
- if (avail > 0)
119
- inStock++;
120
- else
121
- outOfStock++;
122
- if ((i.minQuantity ?? 0) > 0 && avail < i.minQuantity)
123
- belowMin++;
124
- }
125
- return { total: items.length, inStock, outOfStock, belowMin };
126
- }, [items]);
107
+ const counts = useMemo(() => countStock(items), [items]);
127
108
  const filtered = useMemo(() => {
128
109
  const q = search.trim().toLowerCase();
129
110
  return items.filter((i) => {
130
111
  if (noPriceOnly && !(i.productId && noPriceIds.has(i.productId)))
131
112
  return false;
132
113
  const avail = availableOf(i);
133
- if (stockFilter === "in_stock" && avail <= 0)
114
+ const physicalQty = Number(i.quantity ?? 0);
115
+ if (stockFilter === "in_stock" && physicalQty <= 0)
134
116
  return false;
135
- if (stockFilter === "out_of_stock" && avail > 0)
117
+ if (stockFilter === "out_of_stock" && physicalQty > 0)
136
118
  return false;
137
119
  if (stockFilter === "below_min" &&
138
120
  !((i.minQuantity ?? 0) > 0 && avail < i.minQuantity))
@@ -162,7 +144,7 @@ export function VoorraadPage() {
162
144
  { id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
163
145
  { id: "below_min", label: "Onder minimum", count: counts.belowMin },
164
146
  ];
165
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? " staat" : "en staan", " niet in Floriday zonder geldige prijs:", " ", [
147
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), guardTrips.length > 0 && (_jsxs("div", { role: "alert", className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: ["Deze pagina vroeg te vaak gegevens op en is daarmee gestopt. Open de pagina opnieuw.", _jsxs("details", { className: "text-xs opacity-80", children: [_jsx("summary", { className: "cursor-pointer", children: "Details voor beheer" }), guardTrips.map((t) => (_jsxs("div", { children: [t.table, ": ", t.count, " aanvragen binnen 10 s om ", new Date(t.at).toLocaleTimeString("nl-NL")] }, t.table)))] })] })), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? " staat" : "en staan", " niet in Floriday zonder geldige prijs:", " ", [
166
148
  noPriceBreakdown.empty > 0 ? `${noPriceBreakdown.empty} zonder verkoopprijs` : null,
167
149
  noPriceBreakdown.zero > 0 ? `${noPriceBreakdown.zero} met prijs € 0,00` : null,
168
150
  noPriceBreakdown.unit > 0 ? `${noPriceBreakdown.unit} met een andere eenheid dan stuks` : null,
@@ -0,0 +1,10 @@
1
+ export type NoPriceBreakdown = {
2
+ empty: number;
3
+ zero: number;
4
+ unit: number;
5
+ };
6
+ export declare function useNoPriceProducts(): {
7
+ noPriceIds: Set<string>;
8
+ noPriceBreakdown: NoPriceBreakdown;
9
+ };
10
+ //# sourceMappingURL=useNoPriceProducts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNoPriceProducts.d.ts","sourceRoot":"","sources":["../../src/hooks/useNoPriceProducts.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,wBAAgB,kBAAkB;;;EA+CjC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Artikelen zonder geldige prijs per stuk. Eén keer bij openen, daarna alleen na een
3
+ * echte wijziging aan products (realtime), gebundeld tot hooguit 1 aanvraag per 5 s.
4
+ * Bewust los van de identiteit van de voorraadlijst (die veroorzaakte een opvraag-lus).
5
+ */
6
+ import { useEffect, useState } from "react";
7
+ import { supabase } from "../integrations/supabase/client.js";
8
+ import { createBundler, pageRequestGuard } from "../lib/request-guard.js";
9
+ export function useNoPriceProducts() {
10
+ const [ids, setIds] = useState(new Set());
11
+ const [breakdown, setBreakdown] = useState({ empty: 0, zero: 0, unit: 0 });
12
+ useEffect(() => {
13
+ let cancelled = false;
14
+ const load = async () => {
15
+ if (!pageRequestGuard.allow("products"))
16
+ return;
17
+ const { data } = await supabase.from("products").select("id, unit, sale_price");
18
+ if (cancelled)
19
+ return;
20
+ const s = new Set();
21
+ const b = { empty: 0, zero: 0, unit: 0 };
22
+ for (const p of (data ?? [])) {
23
+ const isPiece = (p.unit ?? "").trim().toLowerCase() === "stuks";
24
+ if (!isPiece) {
25
+ s.add(p.id);
26
+ b.unit++;
27
+ }
28
+ else if (p.sale_price == null) {
29
+ s.add(p.id);
30
+ b.empty++;
31
+ }
32
+ else if (!(Number(p.sale_price) > 0)) {
33
+ s.add(p.id);
34
+ b.zero++;
35
+ }
36
+ }
37
+ setIds((cur) => (cur.size === s.size && [...s].every((x) => cur.has(x)) ? cur : s));
38
+ setBreakdown((cur) => cur.empty === b.empty && cur.zero === b.zero && cur.unit === b.unit ? cur : b);
39
+ };
40
+ void load();
41
+ const bundler = createBundler(() => void load(), 5000);
42
+ const channel = supabase
43
+ .channel(`voorraad-noprice-${Math.random().toString(36).slice(2)}`)
44
+ .on("postgres_changes", { event: "*", schema: "public", table: "products" }, () => bundler.trigger())
45
+ .subscribe();
46
+ const onVis = () => bundler.flushIfPending();
47
+ document.addEventListener("visibilitychange", onVis);
48
+ return () => {
49
+ cancelled = true;
50
+ bundler.cancel();
51
+ document.removeEventListener("visibilitychange", onVis);
52
+ supabase.removeChannel(channel);
53
+ };
54
+ }, []);
55
+ return { noPriceIds: ids, noPriceBreakdown: breakdown };
56
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useStockPushJobs.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockPushJobs.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAwBD,wBAAgB,gBAAgB,CAAC,KAAK,SAAK,yEAqD1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;aACT,OAAO;aAAW,MAAM,GAAG,IAAI;UAoB3D"}
1
+ {"version":3,"file":"useStockPushJobs.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockPushJobs.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAwBD,wBAAgB,gBAAgB,CAAC,KAAK,SAAK,yEAuD1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;aACT,OAAO;aAAW,MAAM,GAAG,IAAI;UAsB3D"}
@@ -3,6 +3,7 @@
3
3
  * inclusief de artikelnaam uit de Floriday-cache. Bedoeld om een testpush te
4
4
  * kunnen volgen zonder databasekennis.
5
5
  */
6
+ import { MIN_POLL_MS } from "../lib/request-guard.js";
6
7
  import { useQuery } from "@tanstack/react-query";
7
8
  import { supabase } from "../integrations/supabase/client.js";
8
9
  /** Floriday levert namen soms als taalobject ({ nl: "…", en: "…" }). */
@@ -28,7 +29,9 @@ function nameFromCache(data) {
28
29
  export function useStockPushJobs(limit = 10) {
29
30
  return useQuery({
30
31
  queryKey: ["stock-push-jobs", limit],
31
- refetchInterval: 15000,
32
+ refetchInterval: MIN_POLL_MS,
33
+ refetchIntervalInBackground: false,
34
+ refetchOnWindowFocus: false,
32
35
  queryFn: async () => {
33
36
  const { data, error } = await supabase
34
37
  .from("floriday_continuous_stock_queue")
@@ -82,6 +85,8 @@ export function useCentralAuthBlockade() {
82
85
  return useQuery({
83
86
  queryKey: ["central-auth-blockade"],
84
87
  refetchInterval: 60000,
88
+ refetchIntervalInBackground: false,
89
+ refetchOnWindowFocus: false,
85
90
  queryFn: async () => {
86
91
  const since = new Date(Date.now() - 30 * 60000).toISOString();
87
92
  const { data, error } = await supabase
@@ -1 +1 @@
1
- {"version":3,"file":"useStockSyncHealth.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockSyncHealth.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,kDAAkD;IAClD,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAS3E;AAED,wBAAgB,kBAAkB,2EAuGjC"}
1
+ {"version":3,"file":"useStockSyncHealth.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockSyncHealth.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,kDAAkD;IAClD,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAS3E;AAED,wBAAgB,kBAAkB,2EAyGjC"}