@flowselections/floriday-voorraad 1.0.143 → 1.0.147

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 (91) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts +3 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  3. package/dist-lib/components/voorraad/ArtikelWizard.js +57 -53
  4. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  5. package/dist-lib/components/voorraad/EditArtikelPage.js +3 -4
  6. package/dist-lib/components/voorraad/FustAdministratie.d.ts +12 -0
  7. package/dist-lib/components/voorraad/FustAdministratie.d.ts.map +1 -0
  8. package/dist-lib/components/voorraad/FustAdministratie.js +69 -0
  9. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  10. package/dist-lib/components/voorraad/FustenPage.js +9 -14
  11. package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts +6 -0
  12. package/dist-lib/components/voorraad/KoppelingStatusPanel.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/KoppelingStatusPanel.js +47 -0
  14. package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -2
  15. package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -1
  16. package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +29 -13
  17. package/dist-lib/components/voorraad/ProductSalePriceField.d.ts.map +1 -1
  18. package/dist-lib/components/voorraad/ProductSalePriceField.js +9 -8
  19. package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -1
  20. package/dist-lib/components/voorraad/StepSamenstelling.js +35 -10
  21. package/dist-lib/components/voorraad/TelmomentButton.d.ts +8 -0
  22. package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -0
  23. package/dist-lib/components/voorraad/TelmomentButton.js +65 -0
  24. package/dist-lib/components/voorraad/TrayStickerCard.d.ts +5 -1
  25. package/dist-lib/components/voorraad/TrayStickerCard.d.ts.map +1 -1
  26. package/dist-lib/components/voorraad/TrayStickerCard.js +64 -66
  27. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +0 -5
  28. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  29. package/dist-lib/components/voorraad/ValidationOverviewTable.js +10 -4
  30. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
  31. package/dist-lib/components/voorraad/ValidationPage.js +2 -1
  32. package/dist-lib/components/voorraad/VoorraadPage.d.ts +1 -13
  33. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  34. package/dist-lib/components/voorraad/VoorraadPage.js +103 -140
  35. package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts +6 -0
  36. package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts.map +1 -0
  37. package/dist-lib/components/voorraad/VoorraadSturingCard.js +47 -0
  38. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts +5 -0
  39. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -0
  40. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +263 -0
  41. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts +7 -0
  42. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts.map +1 -0
  43. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.js +54 -0
  44. package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts +21 -0
  45. package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts.map +1 -0
  46. package/dist-lib/components/voorraad/artikel/opslaan-context.js +15 -0
  47. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts +5 -0
  48. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -0
  49. package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +20 -0
  50. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts +6 -0
  51. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -0
  52. package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +43 -0
  53. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts +16 -0
  54. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts.map +1 -0
  55. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.js +95 -0
  56. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +21 -0
  57. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -0
  58. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +38 -0
  59. package/dist-lib/hooks/useVoorraadLijst.d.ts +21 -0
  60. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -0
  61. package/dist-lib/hooks/useVoorraadLijst.js +71 -0
  62. package/dist-lib/index.d.ts.map +1 -1
  63. package/dist-lib/index.js +2 -0
  64. package/dist-lib/integrations/supabase/types.d.ts +989 -0
  65. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  66. package/dist-lib/lib/artikel-pagina.d.ts +57 -0
  67. package/dist-lib/lib/artikel-pagina.d.ts.map +1 -0
  68. package/dist-lib/lib/artikel-pagina.js +93 -0
  69. package/dist-lib/lib/count-stock.d.ts +17 -0
  70. package/dist-lib/lib/count-stock.d.ts.map +1 -0
  71. package/dist-lib/lib/count-stock.js +26 -0
  72. package/dist-lib/lib/floriday-import-validator.d.ts +9 -0
  73. package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -1
  74. package/dist-lib/lib/floriday-import-validator.js +13 -0
  75. package/dist-lib/lib/fust-administratie.d.ts +29 -0
  76. package/dist-lib/lib/fust-administratie.d.ts.map +1 -0
  77. package/dist-lib/lib/fust-administratie.js +63 -0
  78. package/dist-lib/lib/fusten.d.ts +4 -0
  79. package/dist-lib/lib/fusten.d.ts.map +1 -1
  80. package/dist-lib/lib/pending-photos.d.ts +20 -0
  81. package/dist-lib/lib/pending-photos.d.ts.map +1 -0
  82. package/dist-lib/lib/pending-photos.js +90 -0
  83. package/dist-lib/lib/voorraad-custom-fields.d.ts +16 -1
  84. package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -1
  85. package/dist-lib/lib/voorraad-custom-fields.js +12 -6
  86. package/dist-lib/lib/voorraad-lijst.d.ts +91 -0
  87. package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -0
  88. package/dist-lib/lib/voorraad-lijst.js +82 -0
  89. package/package.json +1 -1
  90. package/sql/AGENTS.md +7 -0
  91. package/sql/floriday-voorraad-schema.sql +1627 -62
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
4
- import { Loader2, Save } from "lucide-react";
3
+ import { useOpslagDeelnemer } from "./artikel/opslaan-context.js";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
5
5
  import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields.js";
6
6
  import { fetchProductCustomValues, saveProductCustomValues, } from "../../lib/voorraad-custom-fields.js";
7
7
  /** Eigen voorraadvelden met `show_in_detail`, tonen en bewerken op de productpagina. */
8
8
  export function ProductCustomFieldsCard({ productId }) {
9
9
  const { fields, loading } = useVoorraadCustomFields();
10
10
  const [values, setValues] = useState({});
11
+ const [opened, setOpened] = useState({});
11
12
  const [loadingValues, setLoadingValues] = useState(true);
12
13
  const [saving, setSaving] = useState(false);
13
14
  useEffect(() => {
@@ -15,12 +16,16 @@ export function ProductCustomFieldsCard({ productId }) {
15
16
  setLoadingValues(true);
16
17
  fetchProductCustomValues(productId)
17
18
  .then((v) => {
18
- if (!cancelled)
19
+ if (!cancelled) {
19
20
  setValues(v);
21
+ setOpened(v);
22
+ }
20
23
  })
21
24
  .catch(() => {
22
- if (!cancelled)
25
+ if (!cancelled) {
23
26
  setValues({});
27
+ setOpened({});
28
+ }
24
29
  })
25
30
  .finally(() => {
26
31
  if (!cancelled)
@@ -31,25 +36,36 @@ export function ProductCustomFieldsCard({ productId }) {
31
36
  };
32
37
  }, [productId]);
33
38
  const detailFields = fields.filter((f) => f.show_in_detail);
34
- if (loading || loadingValues) {
35
- return null;
36
- }
37
- if (detailFields.length === 0)
38
- return null;
39
39
  const set = (key, value) => setValues((prev) => ({ ...prev, [key]: value }));
40
+ const gewijzigd = detailFields.filter((f) => JSON.stringify(values[f.field_key] ?? null) !== JSON.stringify(opened[f.field_key] ?? null));
41
+ useOpslagDeelnemer("eigen_velden", gewijzigd.length > 0, () => save());
40
42
  const save = async () => {
43
+ if (gewijzigd.length === 0)
44
+ return { ok: true };
41
45
  setSaving(true);
42
46
  try {
43
- await saveProductCustomValues(productId, Object.fromEntries(detailFields.map((f) => [f.field_key, values[f.field_key] ?? null])));
44
- toast.success("Eigen velden opgeslagen");
47
+ const res = await saveProductCustomValues(productId, Object.fromEntries(gewijzigd.map((f) => [f.field_key, opened[f.field_key] ?? null])), Object.fromEntries(gewijzigd.map((f) => [f.field_key, values[f.field_key] ?? null])));
48
+ if (!res.ok) {
49
+ const namen = res.conflicten
50
+ .map((c) => detailFields.find((f) => f.field_key === c.veld)?.field_label ?? c.veld)
51
+ .join(", ");
52
+ const fresh = await fetchProductCustomValues(productId);
53
+ setOpened(fresh);
54
+ setValues(fresh);
55
+ return { ok: false, melding: `Eigen velden niet opgeslagen: iemand anders wijzigde intussen ${namen}. De nieuwste waarden zijn geladen; vul je wijziging opnieuw in.` };
56
+ }
57
+ setOpened((prev) => ({ ...prev, ...Object.fromEntries(gewijzigd.map((f) => [f.field_key, values[f.field_key] ?? null])) }));
58
+ return { ok: true };
45
59
  }
46
60
  catch (e) {
47
- toast.error(`Opslaan mislukt: ${e.message}`);
61
+ return { ok: false, melding: `Eigen velden: opslaan mislukt (${e.message}).` };
48
62
  }
49
63
  finally {
50
64
  setSaving(false);
51
65
  }
52
66
  };
67
+ if (loading || loadingValues || detailFields.length === 0)
68
+ return null;
53
69
  return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Eigen velden" }), _jsx(CardDescription, { children: "Extra voorraadvelden zoals ingesteld bij Instellingen \u2192 Voorraadvelden." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: detailFields.map((f) => {
54
70
  const v = values[f.field_key];
55
71
  return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: `cf-${f.field_key}`, children: f.field_label }), f.field_type === "boolean" ? (_jsx("div", { className: "pt-1", children: _jsx(Checkbox, { id: `cf-${f.field_key}`, checked: v === true, onCheckedChange: (c) => set(f.field_key, c === true) }) })) : f.field_type === "select" ? (_jsxs(Select, { value: typeof v === "string" ? v : "", onValueChange: (val) => set(f.field_key, val), children: [_jsx(SelectTrigger, { id: `cf-${f.field_key}`, children: _jsx(SelectValue, { placeholder: "Kies\u2026" }) }), _jsx(SelectContent, { children: f.options.map((o) => (_jsx(SelectItem, { value: o, children: o }, o))) })] })) : (_jsx(Input, { id: `cf-${f.field_key}`, type: f.field_type === "number"
@@ -61,5 +77,5 @@ export function ProductCustomFieldsCard({ productId }) {
61
77
  ? null
62
78
  : Number(e.target.value)
63
79
  : e.target.value) }))] }, f.id));
64
- }) }), _jsxs(Button, { onClick: save, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Save, { className: "mr-2 h-4 w-4" })), "Eigen velden opslaan"] })] })] }));
80
+ }) }), gewijzigd.length > 0 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Niet opgeslagen wijzigingen; bewaar met \"Opslaan\" bovenaan." }))] })] }));
65
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ProductSalePriceField.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductSalePriceField.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,sCAkGA;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCAqCtE"}
1
+ {"version":3,"file":"ProductSalePriceField.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductSalePriceField.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,sCAoGA;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCAqCtE"}
@@ -58,18 +58,19 @@ export function ProductSalePriceField({ productId, onSaved, }) {
58
58
  setSaving(true);
59
59
  setError(null);
60
60
  try {
61
- const { data, error: err } = await supabase
62
- .from("products")
63
- .update({ sale_price: parsed })
64
- .eq("id", productId)
65
- .select("id");
61
+ // Zelfde opslagroute als "Opslaan" op de artikelpagina: conflictcontrole per veld.
62
+ const { data, error: err } = await supabase.rpc("voorraad_artikel_opslaan", {
63
+ p_id: productId, p_oud: { sale_price: saved }, p_nieuw: { sale_price: parsed },
64
+ });
66
65
  if (err)
67
66
  throw new Error(err.message);
68
- if (!data || data.length === 0)
69
- throw new Error("geen rechten om dit artikel te wijzigen");
67
+ if (!data?.ok) {
68
+ const hun = data?.conflicten?.[0]?.hun;
69
+ throw new Error(`iemand anders heeft de prijs intussen gewijzigd${hun != null ? ` naar € ${hun}` : ""}. Laad het artikel opnieuw`);
70
+ }
70
71
  setSaved(parsed);
71
72
  onSaved?.(parsed);
72
- toast.success("Prijs opgeslagen. Het artikel gaat opnieuw naar Floriday.");
73
+ toast.success("Prijs opgeslagen (alleen lokaal).");
73
74
  }
74
75
  catch (e) {
75
76
  const msg = `Prijs niet opgeslagen: ${e?.message ?? e}`;
@@ -1 +1 @@
1
- {"version":3,"file":"StepSamenstelling.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StepSamenstelling.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAsB5E,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC9C;AA6FD,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAU,EACV,QAAQ,EACR,kBAAkB,GACnB,EAAE,KAAK,+BA+aP"}
1
+ {"version":3,"file":"StepSamenstelling.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StepSamenstelling.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAwB5E,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC9C;AA6FD,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAU,EACV,QAAQ,EACR,kBAAkB,GACnB,EAAE,KAAK,+BAqcP"}
@@ -94,6 +94,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
94
94
  const [loading, setLoading] = useState(true);
95
95
  const [saving, setSaving] = useState(false);
96
96
  const [rows, setRows] = useState([]);
97
+ const [teVerwijderen, setTeVerwijderen] = useState([]);
97
98
  const [names, setNames] = useState(new Map());
98
99
  const [activeTab, setActiveTab] = useState("__base__");
99
100
  const hasParent = Boolean(productId || floridayId);
@@ -131,8 +132,10 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
131
132
  component_trade_item_floriday_id: r.component_trade_item_floriday_id ?? null,
132
133
  quantity: Number(r.quantity ?? 1),
133
134
  sort_order: Number(r.sort_order ?? 0),
135
+ _orig: { quantity: Number(r.quantity ?? 1), sort_order: Number(r.sort_order ?? 0) },
134
136
  }));
135
137
  setRows(list);
138
+ setTeVerwijderen([]);
136
139
  await resolveNames(list);
137
140
  setLoading(false);
138
141
  }, [productId, floridayId, hasParent]);
@@ -230,13 +233,9 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
230
233
  const row = rows.find((r) => r.id === id);
231
234
  if (!row)
232
235
  return;
233
- if (!row._new) {
234
- const { error } = await supabase.from("product_bom_items").delete().eq("id", id);
235
- if (error) {
236
- toast.error("Verwijderen mislukt");
237
- return;
238
- }
239
- }
236
+ // Verwijderen gebeurt pas bij "Opslaan" (bewuste actie met conflictcontrole).
237
+ if (!row._new)
238
+ setTeVerwijderen((l) => [...l, row]);
240
239
  setRows((prev) => prev.filter((r) => r.id !== id));
241
240
  };
242
241
  const saveAll = async () => {
@@ -245,7 +244,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
245
244
  return;
246
245
  }
247
246
  const dirty = rows.filter((r) => r._dirty);
248
- if (dirty.length === 0)
247
+ if (dirty.length === 0 && teVerwijderen.length === 0)
249
248
  return;
250
249
  setSaving(true);
251
250
  // Verhoudingscontrole: alle lagen meetellen, nooit blokkeren.
@@ -264,6 +263,32 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
264
263
  // Controle mislukt: opslaan gaat gewoon door.
265
264
  }
266
265
  try {
266
+ // Conflictcontrole: bestaande regels die je wijzigt of verwijdert moeten nog
267
+ // dezelfde stand hebben als bij openen.
268
+ const gecontroleerd = [...dirty.filter((r) => !r._new), ...teVerwijderen].filter((r) => r._orig);
269
+ if (gecontroleerd.length) {
270
+ const { data: nu, error: nuErr } = await supabase
271
+ .from("product_bom_items")
272
+ .select("id,quantity,sort_order")
273
+ .in("id", gecontroleerd.map((r) => r.id));
274
+ if (nuErr)
275
+ throw nuErr;
276
+ const huidig = new Map((nu ?? []).map((r) => [r.id, r]));
277
+ const afwijkend = gecontroleerd.filter((r) => {
278
+ const h = huidig.get(r.id);
279
+ return !h || Number(h.quantity) !== r._orig.quantity || Number(h.sort_order) !== r._orig.sort_order;
280
+ });
281
+ if (afwijkend.length) {
282
+ toast.error("Iemand anders heeft deze samenstelling intussen gewijzigd. Er is niets opgeslagen; laad de samenstelling opnieuw.");
283
+ return;
284
+ }
285
+ }
286
+ for (const r of teVerwijderen) {
287
+ const { error } = await supabase.from("product_bom_items").delete().eq("id", r.id);
288
+ if (error)
289
+ throw error;
290
+ }
291
+ setTeVerwijderen([]);
267
292
  const inserts = dirty
268
293
  .filter((r) => r._new)
269
294
  .map((r) => ({
@@ -309,7 +334,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
309
334
  if (error)
310
335
  throw error;
311
336
  }
312
- setRows((prev) => prev.map((r) => ({ ...r, _dirty: false })));
337
+ setRows((prev) => prev.map((r) => ({ ...r, _dirty: false, _orig: { quantity: r.quantity, sort_order: r.sort_order } })));
313
338
  toast.success("Samenstelling opgeslagen");
314
339
  }
315
340
  catch (e) {
@@ -319,7 +344,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
319
344
  setSaving(false);
320
345
  }
321
346
  };
322
- const hasUnsaved = rows.some((r) => r._dirty);
347
+ const hasUnsaved = rows.some((r) => r._dirty) || teVerwijderen.length > 0;
323
348
  if (!hasParent) {
324
349
  return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Sla het artikel eerst op om een samenstelling te configureren." }));
325
350
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
3
+ * zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
4
+ */
5
+ export declare function TelmomentButton({ onSaved }: {
6
+ onSaved?: () => void;
7
+ }): import("react").JSX.Element;
8
+ //# sourceMappingURL=TelmomentButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelmomentButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TelmomentButton.tsx"],"names":[],"mappings":"AA6BA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BAkGpE"}
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ClipboardCheck } from "lucide-react";
4
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast, } from "@flowselections/core";
5
+ import { supabase } from "../../integrations/supabase/client.js";
6
+ import { TELMOMENT_LABEL } from "../../lib/fust-administratie.js";
7
+ function lokaleInvoer(d) {
8
+ const p = (n) => String(n).padStart(2, "0");
9
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
10
+ }
11
+ /**
12
+ * Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
13
+ * zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
14
+ */
15
+ export function TelmomentButton({ onSaved }) {
16
+ const [open, setOpen] = useState(false);
17
+ const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
18
+ const [regels, setRegels] = useState(null);
19
+ const [alWeg, setAlWeg] = useState(new Set());
20
+ const [busy, setBusy] = useState(false);
21
+ const laden = async (m = moment) => {
22
+ setRegels(null);
23
+ const { data, error } = await supabase.rpc("voorraad_telmoment_lijst", { p_telmoment: new Date(m).toISOString(), p_keys: null });
24
+ if (error) {
25
+ toast.error(error.message);
26
+ setRegels([]);
27
+ return;
28
+ }
29
+ setRegels((data?.regels ?? []));
30
+ setAlWeg(new Set());
31
+ };
32
+ const opslaan = async () => {
33
+ setBusy(true);
34
+ try {
35
+ const { data, error } = await supabase.rpc("voorraad_telmoment_opslaan", {
36
+ p_telmoment: new Date(moment).toISOString(), p_keys: null, p_al_weg: [...alWeg], p_notitie: null,
37
+ });
38
+ if (error)
39
+ throw new Error(error.message);
40
+ toast.success(`Telmoment vastgelegd: ${data?.buiten_telling ?? 0} orderregels tellen niet mee.`);
41
+ setOpen(false);
42
+ onSaved?.();
43
+ }
44
+ catch (e) {
45
+ toast.error(e.message);
46
+ }
47
+ finally {
48
+ setBusy(false);
49
+ }
50
+ };
51
+ const groepen = ["teldag_open", "vertrokken", "voor_teldag"];
52
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => { setOpen(true); void laden(); }, children: [_jsx(ClipboardCheck, { className: "mr-2 h-4 w-4" }), "Telmoment"] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Telmoment vastleggen" }) }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsxs("label", { className: "text-xs text-muted-foreground space-y-1", children: [_jsx("span", { className: "block", children: "Moment van tellen" }), _jsx(Input, { type: "datetime-local", className: "h-9 w-56", value: moment, onChange: (e) => setMoment(e.target.value) })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void laden(), children: "Ververs lijst" })] }), _jsxs("div", { className: "max-h-[50vh] overflow-y-auto space-y-4 text-sm", children: [regels === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), regels !== null && regels.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Geen openstaande orderregels tot en met de teldag." })), regels !== null && groepen.map((g) => {
53
+ const lijst = regels.filter((r) => r.categorie === g);
54
+ if (lijst.length === 0)
55
+ return null;
56
+ return (_jsxs("div", { children: [_jsxs("p", { className: "font-medium", children: [TELMOMENT_LABEL[g], " (", lijst.length, ")"] }), _jsx("ul", { className: "mt-1 divide-y divide-border rounded-md border border-border", children: lijst.map((r) => (_jsxs("li", { className: "flex items-center gap-3 px-3 py-1.5", children: [g === "teldag_open" && (_jsx("input", { type: "checkbox", "aria-label": "Is al weg", checked: alWeg.has(r.order_item_id), onChange: (e) => setAlWeg((s) => {
57
+ const n = new Set(s);
58
+ if (e.target.checked)
59
+ n.add(r.order_item_id);
60
+ else
61
+ n.delete(r.order_item_id);
62
+ return n;
63
+ }) })), _jsx("span", { className: "w-28 text-muted-foreground", children: r.order_number ?? "—" }), _jsxs("span", { className: "flex-1 truncate", children: [r.artikel ?? "—", " \u00B7 ", r.klant ?? ""] }), _jsx("span", { className: "tabular-nums", children: r.aantal })] }, r.order_item_id))) })] }, g));
64
+ })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => void opslaan(), disabled: busy || regels === null, children: busy ? "Opslaan…" : "Telmoment opslaan" })] })] }) })] }));
65
+ }
@@ -1,4 +1,8 @@
1
- /** Traysticker-afbeelding per product — los van de Floriday-productfoto (`products.image_url`). */
1
+ /**
2
+ * Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
3
+ * Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
4
+ * met controle tegen de stand bij openen.
5
+ */
2
6
  export declare function TrayStickerCard({ productId }: {
3
7
  productId: string;
4
8
  }): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"AAyBA,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,+BA+HnE"}
1
+ {"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"AA4BA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,+BAoInE"}
@@ -1,100 +1,98 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, toast, } from "@flowselections/core";
4
- import { Loader2, Trash2, Upload } from "lucide-react";
3
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@flowselections/core";
4
+ import { Loader2, Trash2, Undo2, Upload } from "lucide-react";
5
5
  import { supabase } from "../../integrations/supabase/client.js";
6
+ import { useOpslagDeelnemer } from "./artikel/opslaan-context.js";
6
7
  const BUCKET = "product-tray-stickers";
7
8
  // Bucket is privé (publieke buckets zijn geblokkeerd): lange signed URL voor de logistiek-engine.
8
9
  const SIGNED_URL_SECONDS = 60 * 60 * 24 * 365 * 10;
9
- /** Traysticker-afbeelding per product — los van de Floriday-productfoto (`products.image_url`). */
10
+ /**
11
+ * Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
12
+ * Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
13
+ * met controle tegen de stand bij openen.
14
+ */
10
15
  export function TrayStickerCard({ productId }) {
11
16
  const [row, setRow] = useState(null);
12
17
  const [loading, setLoading] = useState(true);
13
- const [busy, setBusy] = useState(false);
18
+ const [wachtend, setWachtend] = useState(null);
14
19
  const inputRef = useRef(null);
15
- useEffect(() => {
16
- let cancelled = false;
17
- setLoading(true);
18
- supabase
20
+ const laad = async () => {
21
+ const { data } = await supabase
19
22
  .from("product_tray_stickers")
20
23
  .select("*")
21
24
  .eq("product_id", productId)
22
- .maybeSingle()
23
- .then(({ data }) => {
24
- if (!cancelled)
25
- setRow(data ?? null);
25
+ .maybeSingle();
26
+ setRow(data ?? null);
27
+ };
28
+ useEffect(() => {
29
+ let cancelled = false;
30
+ setLoading(true);
31
+ void laad().finally(() => {
26
32
  if (!cancelled)
27
33
  setLoading(false);
28
34
  });
29
35
  return () => {
30
36
  cancelled = true;
31
37
  };
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
39
  }, [productId]);
33
- const upload = async (file) => {
34
- setBusy(true);
40
+ useEffect(() => () => {
41
+ if (wachtend?.soort === "nieuw")
42
+ URL.revokeObjectURL(wachtend.voorbeeld);
43
+ }, [wachtend]);
44
+ const opslaan = async () => {
45
+ if (!wachtend)
46
+ return { ok: true };
47
+ let pad = null;
35
48
  try {
36
- const ext = file.name.includes(".") ? file.name.slice(file.name.lastIndexOf(".")) : "";
37
- const path = `${productId}/${crypto.randomUUID()}${ext}`;
38
- const up = await supabase.storage.from(BUCKET).upload(path, file, {
39
- contentType: file.type || undefined,
49
+ let next = null;
50
+ if (wachtend.soort === "nieuw") {
51
+ const file = wachtend.file;
52
+ const ext = file.name.includes(".") ? file.name.slice(file.name.lastIndexOf(".")) : "";
53
+ pad = `${productId}/${crypto.randomUUID()}${ext}`;
54
+ const up = await supabase.storage.from(BUCKET).upload(pad, file, { contentType: file.type || undefined });
55
+ if (up.error)
56
+ throw up.error;
57
+ const signed = await supabase.storage.from(BUCKET).createSignedUrl(pad, SIGNED_URL_SECONDS);
58
+ if (signed.error || !signed.data)
59
+ throw signed.error ?? new Error("Geen URL");
60
+ next = { product_id: productId, bestand_url: signed.data.signedUrl, bestandspad: pad, bestandsnaam: file.name, bestandsgrootte: file.size };
61
+ }
62
+ const { data, error } = await supabase.rpc("voorraad_traysticker_opslaan", {
63
+ p_product_id: productId,
64
+ p_oud_pad: row?.bestandspad ?? null,
65
+ p_nieuw: next,
40
66
  });
41
- if (up.error)
42
- throw up.error;
43
- const signed = await supabase.storage.from(BUCKET).createSignedUrl(path, SIGNED_URL_SECONDS);
44
- if (signed.error || !signed.data)
45
- throw signed.error ?? new Error("Geen URL");
46
- const next = {
47
- product_id: productId,
48
- bestand_url: signed.data.signedUrl,
49
- bestandspad: path,
50
- bestandsnaam: file.name,
51
- bestandsgrootte: file.size,
52
- };
53
- const { error } = await supabase
54
- .from("product_tray_stickers")
55
- .upsert(next, { onConflict: "product_id" });
56
67
  if (error)
57
68
  throw error;
69
+ if (!data.ok) {
70
+ if (pad)
71
+ await supabase.storage.from(BUCKET).remove([pad]);
72
+ setWachtend(null);
73
+ await laad();
74
+ return { ok: false, melding: "Traysticker niet opgeslagen: iemand anders wijzigde hem intussen. De nieuwste stand is geladen." };
75
+ }
58
76
  if (row?.bestandspad)
59
77
  await supabase.storage.from(BUCKET).remove([row.bestandspad]);
60
78
  setRow(next);
61
- toast.success("Traysticker-afbeelding opgeslagen");
62
- }
63
- catch (e) {
64
- toast.error(`Uploaden mislukt: ${e.message}`);
65
- }
66
- finally {
67
- setBusy(false);
68
- if (inputRef.current)
69
- inputRef.current.value = "";
70
- }
71
- };
72
- const remove = async () => {
73
- if (!row)
74
- return;
75
- setBusy(true);
76
- try {
77
- const { error } = await supabase
78
- .from("product_tray_stickers")
79
- .delete()
80
- .eq("product_id", productId);
81
- if (error)
82
- throw error;
83
- if (row.bestandspad)
84
- await supabase.storage.from(BUCKET).remove([row.bestandspad]);
85
- setRow(null);
86
- toast.success("Traysticker-afbeelding verwijderd");
79
+ setWachtend(null);
80
+ return { ok: true };
87
81
  }
88
82
  catch (e) {
89
- toast.error(`Verwijderen mislukt: ${e.message}`);
90
- }
91
- finally {
92
- setBusy(false);
83
+ if (pad)
84
+ await supabase.storage.from(BUCKET).remove([pad]);
85
+ return { ok: false, melding: `Traysticker: opslaan mislukt (${e.message}).` };
93
86
  }
94
87
  };
95
- return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Traysticker-afbeelding" }), _jsx(CardDescription, { children: "Afbeelding die op het traysticker-etiket bij de pakbon op het pickstation wordt geprint. Dit staat los van de productfoto uit Floriday." })] }), _jsxs(CardContent, { className: "space-y-4", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : row ? (_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("img", { src: row.bestand_url, alt: "Traysticker", className: "h-24 w-24 rounded border object-contain bg-muted" }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("div", { children: row.bestandsnaam }), row.bestandsgrootte != null && _jsxs("div", { children: [Math.round(row.bestandsgrootte / 1024), " KB"] })] })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen traysticker-afbeelding ge\u00FCpload." })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", className: "hidden", onChange: (e) => {
88
+ useOpslagDeelnemer("traysticker", wachtend !== null, opslaan);
89
+ const toonUrl = wachtend?.soort === "nieuw" ? wachtend.voorbeeld : wachtend?.soort === "weg" ? null : row?.bestand_url;
90
+ const toonNaam = wachtend?.soort === "nieuw" ? wachtend.file.name : row?.bestandsnaam;
91
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Traysticker-afbeelding" }), _jsx(CardDescription, { children: "Afbeelding die op het traysticker-etiket bij de pakbon op het pickstation wordt geprint. Dit staat los van de productfoto uit Floriday." })] }), _jsxs(CardContent, { className: "space-y-4", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : toonUrl ? (_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("img", { src: toonUrl, alt: "Traysticker", className: "h-24 w-24 rounded border object-contain bg-muted" }), _jsx("div", { className: "text-sm text-muted-foreground", children: _jsx("div", { children: toonNaam }) })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: wachtend?.soort === "weg" ? "Wordt verwijderd bij Opslaan." : "Nog geen traysticker-afbeelding." })), wachtend && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Niet opgeslagen wijziging; bewaar met \"Opslaan\" bovenaan." })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", className: "hidden", onChange: (e) => {
96
92
  const f = e.target.files?.[0];
97
93
  if (f)
98
- void upload(f);
99
- } }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { onClick: () => inputRef.current?.click(), disabled: busy || loading, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Upload, { className: "mr-2 h-4 w-4" }), row ? "Vervangen" : "Afbeelding uploaden"] }), row && (_jsxs(Button, { variant: "outline", onClick: remove, disabled: busy, children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }))] })] })] }));
94
+ setWachtend({ soort: "nieuw", file: f, voorbeeld: URL.createObjectURL(f) });
95
+ if (inputRef.current)
96
+ inputRef.current.value = "";
97
+ } }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => inputRef.current?.click(), disabled: loading, children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), row || wachtend?.soort === "nieuw" ? "Andere kiezen" : "Afbeelding kiezen"] }), row && wachtend?.soort !== "weg" && (_jsxs(Button, { variant: "outline", onClick: () => setWachtend({ soort: "weg" }), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] })), wachtend && (_jsxs(Button, { variant: "ghost", onClick: () => setWachtend(null), children: [_jsx(Undo2, { className: "mr-2 h-4 w-4" }), "Ongedaan maken"] }))] })] })] }));
100
98
  }
@@ -1,9 +1,4 @@
1
1
  import type { TradeItem } from "../../lib/floriday-client.js";
2
- /**
3
- * Overzicht van producten die (nog) niet actief op Floriday staan. Er is geen concept-status
4
- * meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
5
- * activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
6
- */
7
2
  export declare function ValidationOverviewTable({ items, connectionId, onChanged, }: {
8
3
  items: TradeItem[];
9
4
  connectionId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAQ3D;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BAwKA"}
1
+ {"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoB3D,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BAwKA"}
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import { useNavigate } from "@tanstack/react-router";
4
4
  import { Badge, Button } from "@flowselections/core";
5
5
  import { Link2, Loader2, Pencil, Send } from "lucide-react";
6
- import { validateDraftForPublish } from "../../lib/floriday-import-validator.js";
6
+ import { conceptMetArtikel, validateDraftForPublish } from "../../lib/floriday-import-validator.js";
7
7
  import { updateDraftMeta } from "../../hooks/useLocalTradeItems.js";
8
8
  import { floriday } from "../../lib/floriday-client.js";
9
9
  import { relinkProducts } from "../../lib/floriday-relink.js";
@@ -13,6 +13,12 @@ import { toast } from "@flowselections/core";
13
13
  * meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
14
14
  * activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
15
15
  */
16
+ /** Eigen naam en artikelnummer van het artikel (niet het Floriday-concept). */
17
+ function artikelVan(it) {
18
+ const raw = (it.raw ?? {});
19
+ const s = (v) => (typeof v === "string" && v.trim() ? v : null);
20
+ return { name: s(raw.product) ?? s(it.name), articleNumber: s(raw.article_number) ?? s(it.articleNumber) };
21
+ }
16
22
  export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
17
23
  const navigate = useNavigate();
18
24
  const [relinking, setRelinking] = useState(false);
@@ -53,14 +59,14 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
53
59
  item.raw?.floriday_payload ??
54
60
  item.raw?.data ??
55
61
  {};
56
- const v = validateDraftForPublish(payload);
62
+ const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(item)));
57
63
  if (!v.ok) {
58
64
  toast.error("Nog niet compleet: " + v.missing.join(", "));
59
65
  return;
60
66
  }
61
67
  try {
62
68
  const { buildAddTradeItemPayload } = await import("../../lib/floriday-payload.js");
63
- const built = buildAddTradeItemPayload(payload);
69
+ const built = buildAddTradeItemPayload(conceptMetArtikel(payload, artikelVan(item)));
64
70
  const res = await floriday.createTradeItem(connectionId, built);
65
71
  await updateDraftMeta(productId, {
66
72
  floridayLinkStatus: "published",
@@ -85,7 +91,7 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
85
91
  it.raw?.floriday_payload ??
86
92
  it.raw?.data ??
87
93
  {};
88
- const v = validateDraftForPublish(payload);
94
+ const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(it)));
89
95
  return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-3 font-mono text-xs", children: it.articleNumber || "—" }), _jsx("td", { className: "p-3", children: it.name }), _jsx("td", { className: "p-3", children: _jsx(Badge, { className: "bg-slate-500/15 text-slate-700 border-slate-500/30", children: "Niet actief op Floriday" }) }), _jsx("td", { className: "p-3 text-xs text-muted-foreground max-w-[260px]", children: reasonFor(it) }), _jsx("td", { className: "p-3", children: v.missing.length === 0 ? (_jsx("span", { className: "text-xs text-emerald-700", children: "Klaar om te activeren" })) : (_jsxs("span", { className: "text-xs text-muted-foreground", title: v.missing.join(", "), children: ["Nog nodig: ", v.missing.slice(0, 3).join(", ").toLowerCase(), v.missing.length > 3 ? ` en nog ${v.missing.length - 3} andere` : "", "."] })) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
90
96
  const target = it.productId ?? it.localId ?? it.id;
91
97
  navigate({ to: "/voorraad/$id", params: { id: target } });
@@ -1 +1 @@
1
- {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,gCAiC7B"}
1
+ {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,gCAkC7B"}
@@ -5,8 +5,9 @@ import { ArrowLeft } from "lucide-react";
5
5
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
6
6
  import { useVoorraadData } from "../../hooks/useVoorraadData.js";
7
7
  import { ValidationOverviewTable } from "./ValidationOverviewTable.js";
8
+ import { KoppelingStatusPanel } from "./KoppelingStatusPanel.js";
8
9
  export function ValidationPage() {
9
10
  const { activeId } = useFloridayConnection();
10
11
  const { items, loading, reload } = useVoorraadData(activeId);
11
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
12
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(KoppelingStatusPanel, { connectionId: activeId, onChanged: reload }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
12
13
  }
@@ -1,15 +1,3 @@
1
- import type { TradeItem } from "../../lib/floriday-client.js";
2
- /**
3
- * "Op voorraad" = artikelen met liggende voorraad (fysiek > 0).
4
- * "Vrij beschikbaar" = artikelen waarvan liggend min gereserveerd > 0.
5
- */
6
- export declare function countStock(items: Array<Pick<TradeItem, "quantity" | "availableQuantity" | "minQuantity">>): {
7
- total: number;
8
- inStock: number;
9
- outOfStock: number;
10
- belowMin: number;
11
- fullyReserved: number;
12
- freeAvailable: number;
13
- };
1
+ export { countStock } from "../../lib/count-stock.js";
14
2
  export declare function VoorraadPage(): import("react").JSX.Element;
15
3
  //# sourceMappingURL=VoorraadPage.d.ts.map
@@ -1 +1 @@
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;AAmF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCAsW3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AA2CnD,wBAAgB,YAAY,gCA6M3B"}