@flowselections/floriday-voorraad 1.0.149 → 1.0.152

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 (100) hide show
  1. package/dist-lib/components/fs-ui/DataTable.d.ts +42 -0
  2. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
  3. package/dist-lib/components/fs-ui/DataTable.js +68 -0
  4. package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
  5. package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
  6. package/dist-lib/components/fs-ui/EmptyState.js +5 -0
  7. package/dist-lib/components/fs-ui/FilterChips.d.ts +13 -0
  8. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
  9. package/dist-lib/components/fs-ui/FilterChips.js +8 -0
  10. package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
  11. package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
  12. package/dist-lib/components/fs-ui/HoverTip.js +24 -0
  13. package/dist-lib/components/fs-ui/InlineAlert.d.ts +12 -0
  14. package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
  15. package/dist-lib/components/fs-ui/InlineAlert.js +15 -0
  16. package/dist-lib/components/fs-ui/KpiRow.d.ts +10 -0
  17. package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
  18. package/dist-lib/components/fs-ui/KpiRow.js +8 -0
  19. package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
  20. package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
  21. package/dist-lib/components/fs-ui/KpiTile.js +40 -0
  22. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +13 -0
  23. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
  24. package/dist-lib/components/fs-ui/MobilePrimaryAction.js +10 -0
  25. package/dist-lib/components/fs-ui/PageHeader.d.ts +18 -0
  26. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
  27. package/dist-lib/components/fs-ui/PageHeader.js +8 -0
  28. package/dist-lib/components/fs-ui/PillTabs.d.ts +19 -0
  29. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
  30. package/dist-lib/components/fs-ui/PillTabs.js +18 -0
  31. package/dist-lib/components/fs-ui/SearchInput.d.ts +14 -0
  32. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
  33. package/dist-lib/components/fs-ui/SearchInput.js +51 -0
  34. package/dist-lib/components/fs-ui/StatusIcon.d.ts +13 -0
  35. package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
  36. package/dist-lib/components/fs-ui/StatusIcon.js +15 -0
  37. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +7 -0
  38. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
  39. package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
  40. package/dist-lib/components/fs-ui/Toolbar.d.ts +8 -0
  41. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
  42. package/dist-lib/components/fs-ui/Toolbar.js +5 -0
  43. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +6 -0
  44. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
  45. package/dist-lib/components/fs-ui/UpdatedAt.js +10 -0
  46. package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -1
  47. package/dist-lib/components/voorraad/ArtikelCompositionCard.js +46 -15
  48. package/dist-lib/components/voorraad/ArtikelWizard.js +4 -4
  49. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts +6 -6
  50. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts.map +1 -1
  51. package/dist-lib/components/voorraad/FloridayConnectionNotice.js +10 -12
  52. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  53. package/dist-lib/components/voorraad/FustenPage.js +4 -2
  54. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
  55. package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -1
  56. package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -1
  57. package/dist-lib/components/voorraad/PublicatiesPage.js +2 -1
  58. package/dist-lib/components/voorraad/Sneltoetsen.d.ts +14 -0
  59. package/dist-lib/components/voorraad/Sneltoetsen.d.ts.map +1 -0
  60. package/dist-lib/components/voorraad/Sneltoetsen.js +45 -0
  61. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
  62. package/dist-lib/components/voorraad/StockIntegrityAlert.js +9 -2
  63. package/dist-lib/components/voorraad/StockPushMonitor.js +2 -2
  64. package/dist-lib/components/voorraad/TelmomentButton.d.ts +5 -1
  65. package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -1
  66. package/dist-lib/components/voorraad/TelmomentButton.js +13 -6
  67. package/dist-lib/components/voorraad/ValidationOverviewTable.js +9 -7
  68. package/dist-lib/components/voorraad/ValidationPage.d.ts +1 -0
  69. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
  70. package/dist-lib/components/voorraad/ValidationPage.js +33 -5
  71. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts +4 -1
  72. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts.map +1 -1
  73. package/dist-lib/components/voorraad/VoorraadBeheerCard.js +26 -5
  74. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  75. package/dist-lib/components/voorraad/VoorraadPage.js +48 -34
  76. package/dist-lib/components/voorraad/VoorraadSturingCard.js +1 -1
  77. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
  78. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +28 -18
  79. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +8 -2
  80. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -1
  81. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +62 -27
  82. package/dist-lib/hooks/useLocalTradeItems.d.ts +14 -0
  83. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  84. package/dist-lib/hooks/useLocalTradeItems.js +5 -3
  85. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  86. package/dist-lib/hooks/useTradeItemStock.js +6 -3
  87. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  88. package/dist-lib/hooks/useVoorraadData.js +16 -8
  89. package/dist-lib/hooks/useVoorraadLijst.d.ts +10 -0
  90. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -1
  91. package/dist-lib/hooks/useVoorraadLijst.js +22 -4
  92. package/dist-lib/integrations/supabase/types.d.ts +59 -0
  93. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  94. package/dist-lib/lib/alle-rijen.d.ts +15 -0
  95. package/dist-lib/lib/alle-rijen.d.ts.map +1 -0
  96. package/dist-lib/lib/alle-rijen.js +17 -0
  97. package/dist-lib/lib/db-error-message.d.ts.map +1 -1
  98. package/dist-lib/lib/db-error-message.js +3 -1
  99. package/package.json +2 -1
  100. package/sql/floriday-voorraad-schema.sql +315 -39
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useEffect, useState } from "react";
3
3
  import { ClipboardCheck } from "lucide-react";
4
4
  import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast, } from "@flowselections/core";
5
5
  import { supabase } from "../../integrations/supabase/client.js";
@@ -12,8 +12,10 @@ function lokaleInvoer(d) {
12
12
  * Telmoment vastleggen: orders vóór de teldag en orders die vóór het telmoment vertrokken
13
13
  * zijn tellen niet mee; orders van de teldag die al weg zijn vink je aan.
14
14
  */
15
- export function TelmomentButton({ onSaved }) {
16
- const [open, setOpen] = useState(false);
15
+ export function TelmomentButton({ onSaved, open: openVan, onOpenChange, verbergKnop }) {
16
+ const [openEigen, setOpenEigen] = useState(false);
17
+ const open = openVan ?? openEigen;
18
+ const setOpen = (o) => { setOpenEigen(o); onOpenChange?.(o); };
17
19
  const [moment, setMoment] = useState(() => lokaleInvoer(new Date()));
18
20
  const [regels, setRegels] = useState(null);
19
21
  const [alWeg, setAlWeg] = useState(new Set());
@@ -22,7 +24,8 @@ export function TelmomentButton({ onSaved }) {
22
24
  setRegels(null);
23
25
  const { data, error } = await supabase.rpc("voorraad_telmoment_lijst", { p_telmoment: new Date(m).toISOString(), p_keys: null });
24
26
  if (error) {
25
- toast.error(error.message);
27
+ console.warn(error);
28
+ toast.error("De lijst kon niet geladen worden. Probeer opnieuw.");
26
29
  setRegels([]);
27
30
  return;
28
31
  }
@@ -42,14 +45,18 @@ export function TelmomentButton({ onSaved }) {
42
45
  onSaved?.();
43
46
  }
44
47
  catch (e) {
45
- toast.error(e.message);
48
+ console.warn(e);
49
+ toast.error("Telmoment opslaan lukt niet. Probeer opnieuw.");
46
50
  }
47
51
  finally {
48
52
  setBusy(false);
49
53
  }
50
54
  };
55
+ // Van buitenaf geopend: lijst laden.
56
+ useEffect(() => { if (openVan)
57
+ void laden(); }, [openVan]); // eslint-disable-line react-hooks/exhaustive-deps
51
58
  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) => {
59
+ return (_jsxs(_Fragment, { children: [!verbergKnop && (_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
60
  const lijst = regels.filter((r) => r.categorie === g);
54
61
  if (lijst.length === 0)
55
62
  return null;
@@ -33,13 +33,14 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
33
33
  else {
34
34
  const total = (res.linked ?? 0) + (res.repaired ?? 0);
35
35
  toast.success(total > 0
36
- ? `${total} product(en) opnieuw gekoppeld aan Floriday`
36
+ ? `${total} artikel(en) opnieuw gekoppeld aan Floriday`
37
37
  : "Geen extra koppelingen gevonden");
38
38
  }
39
39
  await onChanged();
40
40
  }
41
41
  catch (e) {
42
- toast.error(e?.message ?? "Automatisch koppelen mislukt");
42
+ console.warn(e);
43
+ toast.error("Automatisch koppelen is niet gelukt. Probeer het later opnieuw.");
43
44
  }
44
45
  finally {
45
46
  setRelinking(false);
@@ -74,25 +75,26 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
74
75
  floridayLinkError: null,
75
76
  lastFloridaySyncAt: new Date().toISOString(),
76
77
  });
77
- toast.success("Product staat nu actief op Floriday");
78
+ toast.success("Artikel staat nu op Floriday");
78
79
  await onChanged();
79
80
  }
80
81
  catch (e) {
81
- toast.error(e?.message ?? "Activeren op Floriday mislukt");
82
+ console.warn(e);
83
+ toast.error("Op Floriday zetten is niet gelukt. Probeer het later opnieuw.");
82
84
  await onChanged();
83
85
  }
84
86
  };
85
87
  const relinkButton = (_jsxs(Button, { size: "sm", variant: "outline", onClick: runRelink, disabled: relinking, children: [relinking ? (_jsx(Loader2, { className: "h-3 w-3 mr-1 animate-spin" })) : (_jsx(Link2, { className: "h-3 w-3 mr-1" })), "Nu opnieuw koppelen"] }));
86
88
  if (rows.length === 0) {
87
- return (_jsxs("div", { className: "p-8 text-center text-sm text-muted-foreground space-y-3", children: [_jsx("div", { children: "Alle producten staan actief op Floriday." }), _jsx("div", { className: "flex justify-center", children: relinkButton })] }));
89
+ return (_jsxs("div", { className: "p-8 text-center text-sm text-muted-foreground space-y-3", children: [_jsx("div", { children: "Alle artikelen staan op Floriday." }), _jsx("div", { className: "flex justify-center", children: relinkButton })] }));
88
90
  }
89
- return (_jsxs("div", { className: "overflow-x-auto", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 p-3 border-b", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Koppelen gebeurt automatisch zodra Floriday-artikelen binnenkomen en elk uur op de achtergrond." }), relinkButton] }), _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: "Artikelcode" }), _jsx("th", { className: "p-3", children: "Artikelnaam" }), _jsx("th", { className: "p-3", children: "Floriday-status" }), _jsx("th", { className: "p-3", children: "Reden" }), _jsx("th", { className: "p-3", children: "Ontbrekende Floriday-velden" }), _jsx("th", { className: "p-3 text-right", children: "Acties" })] }) }), _jsx("tbody", { children: rows.map((it) => {
91
+ return (_jsxs("div", { className: "overflow-x-auto", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 p-3 border-b", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Koppelen gebeurt automatisch zodra Floriday-artikelen binnenkomen en elk uur op de achtergrond." }), relinkButton] }), _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs font-medium text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Artikelcode" }), _jsx("th", { className: "p-3", children: "Artikelnaam" }), _jsx("th", { className: "p-3", children: "Floriday-status" }), _jsx("th", { className: "p-3", children: "Reden" }), _jsx("th", { className: "p-3", children: "Ontbrekende Floriday-velden" }), _jsx("th", { className: "p-3 text-right", children: "Acties" })] }) }), _jsx("tbody", { children: rows.map((it) => {
90
92
  const payload = it.raw?.custom_fields?.floriday_payload ??
91
93
  it.raw?.floriday_payload ??
92
94
  it.raw?.data ??
93
95
  {};
94
96
  const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(it)));
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: () => {
97
+ return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-3 text-xs tabular-nums", children: it.articleNumber || "—" }), _jsx("td", { className: "p-3", children: it.name }), _jsx("td", { className: "p-3", children: _jsx(Badge, { variant: "outline", className: "rounded-full border-border bg-muted text-muted-foreground", 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-success-text", 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: () => {
96
98
  const target = it.productId ?? it.localId ?? it.id;
97
99
  navigate({ to: "/voorraad/$id", params: { id: target } });
98
100
  }, children: [_jsx(Pencil, { className: "h-3 w-3 mr-1" }), "Bewerken"] }), _jsxs(Button, { size: "sm", onClick: () => publish(it), disabled: !v.ok, children: [_jsx(Send, { className: "h-3 w-3 mr-1" }), "Activeer op Floriday"] })] }) })] }, it.id));
@@ -1,2 +1,3 @@
1
+ /** Floriday-controle: artikelen die nog niet op Floriday staan, per pagina van de server (geen limiet van 1000). */
1
2
  export declare function ValidationPage(): import("react").JSX.Element;
2
3
  //# sourceMappingURL=ValidationPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,gCAkC7B"}
1
+ {"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAiBA,oHAAoH;AACpH,wBAAgB,cAAc,gCA0C7B"}
@@ -1,13 +1,41 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
2
3
  import { Link } from "@tanstack/react-router";
3
- import { Card } from "@flowselections/core";
4
+ import { Card, Skeleton } from "@flowselections/core";
4
5
  import { ArrowLeft } from "lucide-react";
5
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
6
- import { useVoorraadData } from "../../hooks/useVoorraadData.js";
7
+ import { supabase } from "../../integrations/supabase/client.js";
8
+ import { productToTradeItem } from "../../hooks/useLocalTradeItems.js";
9
+ import { dbFoutTekst } from "../../lib/db-error-message.js";
7
10
  import { ValidationOverviewTable } from "./ValidationOverviewTable.js";
8
- import { KoppelingStatusPanel } from "./KoppelingStatusPanel.js";
11
+ import { VoorraadPagination } from "./VoorraadPagination.js";
12
+ import { PageHeader } from "../fs-ui/PageHeader.js";
13
+ import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
14
+ import { InlineAlert } from "../fs-ui/InlineAlert.js";
15
+ const GROOTTE = 50;
16
+ /** Floriday-controle: artikelen die nog niet op Floriday staan, per pagina van de server (geen limiet van 1000). */
9
17
  export function ValidationPage() {
10
18
  const { activeId } = useFloridayConnection();
11
- const { items, loading, reload } = useVoorraadData(activeId);
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 })) })] }));
19
+ const [pagina, setPagina] = useState(1);
20
+ const [items, setItems] = useState([]);
21
+ const [totaal, setTotaal] = useState(0);
22
+ const [loading, setLoading] = useState(true);
23
+ const [fout, setFout] = useState(null);
24
+ const [bijgewerkt, setBijgewerkt] = useState(null);
25
+ const reload = useCallback(async () => {
26
+ const { data, error } = await supabase.rpc("voorraad_validatie_pagina", { p_pagina: pagina, p_grootte: GROOTTE });
27
+ if (error) {
28
+ setFout(dbFoutTekst(error));
29
+ setLoading(false);
30
+ return;
31
+ }
32
+ const r = data;
33
+ setItems((r.rijen ?? []).map(productToTradeItem));
34
+ setTotaal(Number(r.totaal ?? 0));
35
+ setFout(null);
36
+ setLoading(false);
37
+ setBijgewerkt(new Date());
38
+ }, [pagina]);
39
+ useEffect(() => { void reload(); }, [reload]);
40
+ return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { crumb: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " Voorraad"] }), title: "Floriday-controle", updated: _jsx(UpdatedAt, { at: bijgewerkt }), meta: _jsxs("span", { children: ["Artikelen die nog niet op Floriday staan", loading ? "" : ` · ${totaal.toLocaleString("nl-NL")}`] }) }), fout && _jsx(InlineAlert, { tone: "fout", children: fout }), _jsx(Card, { className: "overflow-hidden rounded-xl shadow-sm", children: loading ? (_jsx("div", { className: "space-y-2 p-4", "aria-busy": "true", children: Array.from({ length: 8 }).map((_, i) => _jsx(Skeleton, { className: "h-10 w-full rounded-lg" }, i)) })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) }), totaal > GROOTTE && (_jsx(VoorraadPagination, { page: pagina, pageSize: GROOTTE, pageSizeOptions: [GROOTTE], total: totaal, onPageChange: setPagina, onPageSizeChange: () => undefined }))] }));
13
41
  }
@@ -1,3 +1,6 @@
1
- /** Inklapbaar blok "Beheer": alleen zichtbaar voor beheerders, alleen informatie plus één knop. */
1
+ /**
2
+ * "Voorraad — Controle Floriday-artikelen": alleen voor admin en superadmin
3
+ * (`voorraad_is_beheerder` in de database). Inklapbaar; ververst na de knop en zolang een controle loopt.
4
+ */
2
5
  export declare function VoorraadBeheerCard(): import("react").JSX.Element | null;
3
6
  //# sourceMappingURL=VoorraadBeheerCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadBeheerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadBeheerCard.tsx"],"names":[],"mappings":"AAuBA,mGAAmG;AACnG,wBAAgB,kBAAkB,uCAwDjC"}
1
+ {"version":3,"file":"VoorraadBeheerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadBeheerCard.tsx"],"names":[],"mappings":"AA8BA;;;GAGG;AACH,wBAAgB,kBAAkB,uCAoFjC"}
@@ -1,28 +1,48 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useState } from "react";
3
- import { Button, Card, CardContent, CardHeader, CardTitle, toast } from "@flowselections/core";
2
+ import { lazy, Suspense, useCallback, useEffect, useState } from "react";
3
+ import { Button, Card, Skeleton, toast } from "@flowselections/core";
4
+ import { ChevronDown, RefreshCw } from "lucide-react";
4
5
  import { supabase } from "../../integrations/supabase/client.js";
5
6
  import { dbFoutTekst } from "../../lib/db-error-message.js";
6
7
  import { toonVersieBeheer } from "../../lib/floriday-versie-ronde.js";
7
8
  import { floridayVersiesControleren } from "../../lib/floriday-versie-ronde.functions.js";
9
+ import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
10
+ // Zware beheeronderdelen laden pas als je het blok openklapt.
11
+ const KoppelingStatusPanel = lazy(() => import("./KoppelingStatusPanel.js").then((m) => ({ default: m.KoppelingStatusPanel })));
12
+ const StockPushMonitor = lazy(() => import("./StockPushMonitor.js").then((m) => ({ default: m.StockPushMonitor })));
13
+ const FloridayTakeoverPanel = lazy(() => import("./FloridayTakeoverPanel.js").then((m) => ({ default: m.FloridayTakeoverPanel })));
8
14
  const STATUS_TEKST = {
9
15
  geslaagd: "geslaagd",
10
16
  onvolledig: "nog niet compleet (wordt automatisch opnieuw geprobeerd)",
11
17
  mislukt: "mislukt (wordt automatisch opnieuw geprobeerd)",
12
18
  };
13
- /** Inklapbaar blok "Beheer": alleen zichtbaar voor beheerders, alleen informatie plus één knop. */
19
+ /**
20
+ * "Voorraad — Controle Floriday-artikelen": alleen voor admin en superadmin
21
+ * (`voorraad_is_beheerder` in de database). Inklapbaar; ververst na de knop en zolang een controle loopt.
22
+ */
14
23
  export function VoorraadBeheerCard() {
15
24
  const [status, setStatus] = useState(null);
16
25
  const [bezig, setBezig] = useState(false);
26
+ const [open, setOpen] = useState(false);
27
+ const [versie, setVersie] = useState(0);
28
+ const conn = useFloridayConnection();
17
29
  const laad = useCallback(async () => {
18
30
  const { data, error } = await supabase.rpc("voorraad_floriday_versieronde_status");
19
31
  if (error) {
32
+ console.warn("Beheerstatus voorraad niet geladen", error);
20
33
  setStatus(null);
21
34
  return;
22
35
  }
23
36
  setStatus(data);
24
37
  }, []);
25
38
  useEffect(() => { void laad(); }, [laad]);
39
+ // Stil verversen zolang een controle loopt.
40
+ useEffect(() => {
41
+ if (!status?.bezig)
42
+ return;
43
+ const t = setInterval(() => void laad(), 5000);
44
+ return () => clearInterval(t);
45
+ }, [status?.bezig, laad]);
26
46
  if (!toonVersieBeheer(status) || !status?.heeft_koppeling)
27
47
  return null;
28
48
  const start = async () => {
@@ -30,15 +50,16 @@ export function VoorraadBeheerCard() {
30
50
  try {
31
51
  await floridayVersiesControleren();
32
52
  toast.success("Controle gestart. Dit loopt op de achtergrond.");
33
- await laad();
34
53
  }
35
54
  catch (e) {
36
55
  toast.error(dbFoutTekst(e));
37
56
  }
38
57
  finally {
39
58
  setBezig(false);
59
+ setVersie((v) => v + 1);
60
+ await laad();
40
61
  }
41
62
  };
42
63
  const l = status.laatste;
43
- return (_jsx(Card, { children: _jsxs("details", { children: [_jsx("summary", { className: "cursor-pointer list-none", children: _jsx(CardHeader, { children: _jsx(CardTitle, { children: "Beheer" }) }) }), _jsxs(CardContent, { className: "space-y-2 text-sm", children: [_jsx("p", { className: "font-medium", children: "Controle van Floriday-artikelen" }), _jsx("p", { className: "text-muted-foreground", children: "Loopt vanzelf elke nacht. Oude versies van Floriday-artikelen worden geen zichtbaar artikel." }), _jsxs("p", { children: ["Laatste controle:", " ", l ? `${new Date(l.uitgevoerd_at).toLocaleString("nl-NL")} — ${STATUS_TEKST[l.status] ?? l.status}` : "nog niet uitgevoerd", status.bezig && " · nu bezig"] }), l?.melding && l.status !== "geslaagd" && _jsx("p", { className: "text-muted-foreground", children: l.melding }), _jsxs("p", { children: ["Vervangen in Floriday: ", status.vervangen ?? 0, " \u00B7 Vervallen: ", status.vervallen ?? 0, " \u00B7 Apart gezet (geen antwoord van Floriday): ", status.onbekend ?? 0] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void start(), disabled: bezig || status.bezig, children: bezig ? "Bezig…" : "Nu opnieuw controleren" })] })] }) }));
64
+ return (_jsxs(Card, { className: "overflow-hidden rounded-xl shadow-sm", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, className: "flex w-full items-center justify-between gap-3 px-5 py-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsxs("div", { children: [_jsx("div", { className: "text-base font-semibold", children: "Voorraad \u2014 Controle Floriday-artikelen" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Alleen voor beheerders \u00B7 Laatste controle ", l ? new Date(l.uitgevoerd_at).toLocaleString("nl-NL") : "nog niet uitgevoerd"] })] }), _jsx(ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform motion-reduce:transition-none " + (open ? "rotate-180" : ""), "aria-hidden": true })] }), open && (_jsxs("div", { className: "space-y-4 border-t border-border px-5 py-4 text-sm", children: [_jsxs("section", { className: "space-y-2", children: [_jsx("p", { className: "text-muted-foreground", children: "Loopt vanzelf elke nacht. Oude versies van Floriday-artikelen worden geen zichtbaar artikel." }), _jsxs("p", { children: ["Laatste controle:", " ", l ? `${new Date(l.uitgevoerd_at).toLocaleString("nl-NL")} — ${STATUS_TEKST[l.status] ?? "onbekend"}` : "nog niet uitgevoerd", status.bezig && " · nu bezig"] }), l?.melding && l.status !== "geslaagd" && _jsx("p", { className: "text-muted-foreground", children: l.melding }), _jsxs("p", { className: "tabular-nums", children: ["Vervangen in Floriday: ", status.vervangen ?? 0, " \u00B7 Vervallen: ", status.vervallen ?? 0, " \u00B7 Apart gezet (geen antwoord van Floriday): ", status.onbekend ?? 0] }), _jsxs(Button, { size: "sm", variant: "outline", className: "h-8", onClick: () => void start(), disabled: bezig || status.bezig, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4 " + (bezig || status.bezig ? "animate-spin motion-reduce:animate-none" : "") }), bezig ? "Bezig…" : "Nu opnieuw controleren"] })] }), _jsx(Suspense, { fallback: _jsx(Skeleton, { className: "h-32 w-full rounded-xl" }), children: _jsxs("div", { className: "space-y-4", children: [_jsx(KoppelingStatusPanel, { connectionId: conn.activeId ?? null, onChanged: () => void laad() }), _jsx(StockPushMonitor, { connectionId: conn.activeId ?? null }), _jsx(FloridayTakeoverPanel, { connectionId: conn.activeId ?? null })] }, versie) })] }))] }));
44
65
  }
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAuEA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAsDnD,wBAAgB,YAAY,gCA2N3B"}
@@ -1,9 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { TwijfelKiezen } from "./artikel/TwijfelKiezen.js";
3
- import { Fragment, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
- import { Link, useNavigate } from "@tanstack/react-router";
5
- import { Button, Card, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger, Input, } from "@flowselections/core";
6
- import { AlertTriangle, Columns3, Plus, Search } from "lucide-react";
3
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
+ import { useNavigate } from "@tanstack/react-router";
5
+ import { Button, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@flowselections/core";
6
+ import { AlertTriangle, Archive, ArrowDownToLine, ClipboardCheck, Columns3, Euro, HelpCircle, Layers, Link2Off, ListChecks, MoreHorizontal, Package, PackageSearch, PackageX, Plus, Send, TrendingDown, } from "lucide-react";
7
7
  import { supabase } from "../../integrations/supabase/client.js";
8
8
  import { pageRequestGuard } from "../../lib/request-guard.js";
9
9
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
@@ -19,11 +19,33 @@ import { OPTIONELE_KOLOMMEN, VoorraadLijstTabel } from "./lijst/VoorraadLijstTab
19
19
  import { AanpassenDialog, AfboekenDialog } from "./lijst/SnelActieDialogen.js";
20
20
  import { LijstExportKnop } from "./lijst/LijstExportKnop.js";
21
21
  import { ImporteerKnop } from "./lijst/ImporteerKnop.js";
22
+ import { Kbd, Sneltoetsen } from "./Sneltoetsen.js";
23
+ import { PageHeader } from "../fs-ui/PageHeader.js";
24
+ import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
25
+ import { KpiRow } from "../fs-ui/KpiRow.js";
26
+ import { KpiTile } from "../fs-ui/KpiTile.js";
27
+ import { PillTabs } from "../fs-ui/PillTabs.js";
28
+ import { Toolbar } from "../fs-ui/Toolbar.js";
29
+ import { SearchInput } from "../fs-ui/SearchInput.js";
30
+ import { InlineAlert } from "../fs-ui/InlineAlert.js";
31
+ import { EmptyState } from "../fs-ui/EmptyState.js";
32
+ import { MobilePrimaryAction } from "../fs-ui/MobilePrimaryAction.js";
33
+ import { DataTable } from "../fs-ui/DataTable.js";
22
34
  // Bestaande export blijft (tests en andere modules).
23
35
  export { countStock } from "../../lib/count-stock.js";
24
36
  const EMPTY_TRIPS = [];
25
37
  const KOLOMMEN_KEY = "voorraad.lijst.kolommen.v2";
26
38
  const STANDAARD_KOLOMMEN = ["inkomend", "prijs"];
39
+ const FILTER_ICON = {
40
+ alles: _jsx(Layers, {}),
41
+ onder_minimum: _jsx(TrendingDown, {}),
42
+ verpakking: _jsx(PackageX, {}),
43
+ niet_gekoppeld: _jsx(Link2Off, {}),
44
+ verwijderd_floriday: _jsx(Link2Off, {}),
45
+ prijs: _jsx(Euro, {}),
46
+ twijfel: _jsx(HelpCircle, {}),
47
+ gearchiveerd: _jsx(Archive, {}),
48
+ };
27
49
  function useKolommen() {
28
50
  const [set, setSet] = useState(new Set(STANDAARD_KOLOMMEN));
29
51
  useEffect(() => {
@@ -68,7 +90,6 @@ export function VoorraadPage() {
68
90
  const navigate = useNavigate();
69
91
  const conn = useFloridayConnection();
70
92
  const stockConnId = useStockContextId();
71
- const [zoekInvoer, setZoekInvoer] = useState("");
72
93
  const [zoek, setZoek] = useState("");
73
94
  const [filter, setFilter] = useState("alles");
74
95
  const [sorteer, setSorteer] = useState({ sort: "naam", oplopend: true });
@@ -76,34 +97,23 @@ export function VoorraadPage() {
76
97
  const [grootte, setGrootte] = useState(25);
77
98
  const [selected, setSelected] = useState(new Set());
78
99
  const [drawerOpen, setDrawerOpen] = useState(false);
100
+ const [telmomentOpen, setTelmomentOpen] = useState(false);
79
101
  const [aanpassen, setAanpassen] = useState(null);
80
102
  const [afboeken, setAfboeken] = useState(null);
81
103
  const { kolommen, toggle: toggleKolom } = useKolommen();
82
104
  const guardTrips = useSyncExternalStore(pageRequestGuard.subscribe, () => pageRequestGuard.trips(), () => EMPTY_TRIPS);
83
- // Zoeken met korte vertraging; nieuwe zoekopdracht of filter begint op pagina 1.
84
- useEffect(() => {
85
- const t = setTimeout(() => setZoek(zoekInvoer), 300);
86
- return () => clearTimeout(t);
87
- }, [zoekInvoer]);
105
+ // Nieuwe zoekopdracht, filter of sortering begint op pagina 1.
88
106
  useEffect(() => setPagina(1), [zoek, filter, sorteer, grootte]);
89
- // Sneltoets N = nieuw artikel (niet tijdens typen).
90
- useEffect(() => {
91
- const h = (e) => {
92
- const t = e.target;
93
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable))
94
- return;
95
- if (e.key === "n" && !e.metaKey && !e.ctrlKey && !e.altKey)
96
- navigate({ to: "/voorraad/nieuw" });
97
- };
98
- window.addEventListener("keydown", h);
99
- return () => window.removeEventListener("keydown", h);
100
- }, [navigate]);
107
+ const nieuw = useCallback(() => navigate({ to: "/voorraad/nieuw" }), [navigate]);
101
108
  const vraag = { zoek, filter, sort: sorteer.sort, oplopend: sorteer.oplopend, pagina, grootte };
102
- const { data, loading, error, reload } = useVoorraadLijst(vraag);
109
+ const { data, kpi, loading, error, reload } = useVoorraadLijst(vraag);
103
110
  const heeftKoppeling = data?.heeft_koppeling ?? false;
104
111
  const filters = useMemo(() => zichtbareFilters(heeftKoppeling), [heeftKoppeling]);
105
112
  const melding = data ? meldingen(data) : [];
106
- const bijgewerkt = data?.bijgewerkt ? new Date(data.bijgewerkt).toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" }) : null;
113
+ // Twijfelgevallen bestaan alleen bij een koppeling; tonen als er iets te kiezen is of als je er al staat.
114
+ const tabs = filters
115
+ .filter((f) => f !== "twijfel" || heeftKoppeling || (data?.tellers?.twijfel ?? 0) > 0 || filter === "twijfel")
116
+ .map((f) => ({ value: f, label: FILTER_LABEL[f], icon: FILTER_ICON[f], count: data?.tellers?.[f] ?? null }));
107
117
  const toggleSelect = (id) => setSelected((cur) => {
108
118
  const next = new Set(cur);
109
119
  if (next.has(id))
@@ -113,16 +123,20 @@ export function VoorraadPage() {
113
123
  return next;
114
124
  });
115
125
  const toggleAll = (ids) => setSelected((cur) => (ids.length > 0 && ids.every((id) => cur.has(id)) ? new Set() : new Set(ids)));
116
- return (_jsxs("main", { className: "p-6 space-y-4", 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: bijgewerkt ? `Bijgewerkt ${bijgewerkt}` : "Laden…" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(ImporteerKnop, { connectionId: conn.activeId ?? null, onImported: reload }), _jsx(LijstExportKnop, { vraag: { zoek, filter, sort: sorteer.sort, oplopend: sorteer.oplopend } }), _jsx(TelmomentButton, { onSaved: reload }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, size: "sm", title: "Sneltoets N", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Nieuw artikel"] }) })] })] }), guardTrips.length > 0 && (_jsx("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." })), _jsx(StockIntegrityAlert, {}), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), error && (_jsxs("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), melding.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 rounded-md border border-border bg-muted/40 px-3 py-2 text-sm", children: [_jsx(AlertTriangle, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), melding.map((m) => m.filter ? (_jsx("button", { type: "button", className: "underline-offset-2 hover:underline", onClick: () => setFilter(m.filter), children: m.tekst }, m.tekst)) : (_jsx("span", { children: m.tekst }, m.tekst)))] })), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { role: "tablist", "aria-label": "Filter", className: "flex flex-wrap items-center gap-1 border-b px-2", children: filters.map((f) => {
117
- const actief = filter === f;
118
- const n = data?.tellers?.[f];
119
- return (_jsxs("button", { role: "tab", "aria-selected": actief, type: "button", onClick: () => setFilter(f), className: `relative px-3 py-3 text-sm font-medium whitespace-nowrap transition-colors ${actief ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`, children: [FILTER_LABEL[f], _jsx("span", { className: `ml-2 text-xs tabular-nums ${actief ? "text-primary" : "text-muted-foreground/70"}`, children: n == null ? "…" : aantal(n) }), actief && _jsx("span", { className: "absolute left-2 right-2 -bottom-px h-0.5 rounded-full bg-primary" })] }, f));
120
- }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b p-3", children: [_jsxs("div", { className: "relative flex-1 min-w-[220px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op naam, artikelnummer of barcode\u2026", className: "pl-9", value: zoekInvoer, onChange: (e) => setZoekInvoer(e.target.value), "aria-label": "Zoeken" })] }), _jsx("div", { className: "ml-auto", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Columns3, { className: "mr-2 h-4 w-4" }), "Kolommen"] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: "Kolommen kiezen" }), OPTIONELE_KOLOMMEN.map((c) => (_jsx(DropdownMenuCheckboxItem, { checked: kolommen.has(c.key), onCheckedChange: () => toggleKolom(c.key), children: c.label }, c.key)))] })] }) })] }), selected.size > 0 && filter !== "twijfel" && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b bg-primary/5 px-4 py-2 text-sm", children: [_jsxs("span", { className: "font-medium", children: [selected.size, " gekozen"] }), _jsx(Button, { size: "sm", onClick: () => setDrawerOpen(true), children: "Voorraadsturing instellen" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setSelected(new Set()), children: "Keuze wissen" })] })), _jsx("div", { className: "overflow-x-auto", children: filter === "twijfel" ? (_jsx(TwijfelTabel, { rijen: data?.twijfel_rijen ?? [], loading: loading, onGekozen: () => void reload() })) : (_jsx(VoorraadLijstTabel, { rijen: data?.rijen ?? [], loading: loading, sort: sorteer.sort, oplopend: sorteer.oplopend, onSort: (k) => setSorteer((cur) => volgendeSortering(cur, k)), kolommen: kolommen, selected: selected, onToggleSelect: toggleSelect, onToggleAll: toggleAll, onAanpassen: setAanpassen, onAfboeken: setAfboeken, heeftKoppeling: heeftKoppeling })) }), _jsx(VoorraadPagination, { total: data?.totaal ?? 0, page: pagina, pageSize: grootte, onPageChange: setPagina, onPageSizeChange: setGrootte })] }), _jsx(AanpassenDialog, { rij: aanpassen, onClose: () => setAanpassen(null), onDone: reload }), _jsx(AfboekenDialog, { rij: afboeken, connectionId: stockConnId, onClose: () => setAfboeken(null), onDone: reload }), _jsx(InventorySettingsDrawer, { open: drawerOpen, onOpenChange: setDrawerOpen, connectionId: conn.activeId ?? null, productIds: [...selected], onSaved: reload })] }));
126
+ const selectieBalk = selected.size > 0 && filter !== "twijfel";
127
+ const kpiLaden = kpi === null;
128
+ const leeg = zoek || filter !== "alles" ? (_jsx(EmptyState, { icon: _jsx(PackageSearch, { className: "h-6 w-6" }), title: "Niets gevonden", text: zoek ? `Geen artikelen voor “${zoek}”${filter !== "alles" ? ` in ${FILTER_LABEL[filter].toLowerCase()}` : ""}.` : "Geen artikelen in deze weergave.", action: _jsx(Button, { variant: "outline", size: "sm", onClick: () => { setZoek(""); setFilter("alles"); }, children: "Filters wissen" }) })) : (_jsx(EmptyState, { icon: _jsx(Package, { className: "h-6 w-6" }), title: "Nog geen artikelen", text: "Maak je eerste artikel aan of importeer een lijst.", action: _jsxs(Button, { size: "sm", onClick: nieuw, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Nieuw artikel"] }) }));
129
+ return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(Sneltoetsen, { onNieuw: nieuw }), _jsx(PageHeader, { title: "Voorraad", updated: _jsx(UpdatedAt, { at: data?.bijgewerkt ?? null }), actions: _jsxs(_Fragment, { children: [_jsx(ImporteerKnop, { connectionId: conn.activeId ?? null, onImported: reload }), _jsx(LijstExportKnop, { vraag: { zoek, filter, sort: sorteer.sort, oplopend: sorteer.oplopend } }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9", "aria-label": "Meer acties", children: [_jsx(MoreHorizontal, { className: "h-4 w-4 sm:mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Meer" })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [_jsxs(DropdownMenuItem, { onSelect: () => setTelmomentOpen(true), children: [_jsx(ClipboardCheck, { className: "mr-2 h-4 w-4" }), "Telmoment vastleggen"] }), heeftKoppeling && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: () => navigate({ to: "/voorraad/validatie" }), children: [_jsx(ListChecks, { className: "mr-2 h-4 w-4" }), "Controle koppeling"] }), _jsxs(DropdownMenuItem, { onSelect: () => navigate({ to: "/voorraad/publicaties" }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Verzonden naar Floriday"] })] }))] })] }), _jsxs(Button, { size: "sm", className: "hidden h-9 md:inline-flex", onClick: nieuw, title: "Sneltoets N", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Nieuw artikel", _jsx(Kbd, { children: "N" })] })] }) }), _jsx(TelmomentButton, { verbergKnop: true, open: telmomentOpen, onOpenChange: setTelmomentOpen, onSaved: reload }), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Artikelen", icon: _jsx(Package, {}), loading: kpiLaden, value: aantal(kpi?.artikelen ?? 0), sub: "Actief, zonder gearchiveerd", onClick: () => setFilter("alles"), active: filter === "alles" }), _jsx(KpiTile, { title: "Onder minimum", icon: _jsx(TrendingDown, {}), tone: (kpi?.onder_minimum ?? 0) > 0 ? "warning" : "default", loading: kpiLaden, value: aantal(kpi?.onder_minimum ?? 0), sub: (kpi?.onder_minimum ?? 0) > 0 ? _jsx("span", { className: "text-destructive", children: "Bijbestellen of bijmaken" }) : "Alles boven het minimum", onClick: () => setFilter("onder_minimum"), active: filter === "onder_minimum" }), _jsx(KpiTile, { title: "Vrij beschikbaar", icon: _jsx(Layers, {}), loading: kpiLaden, value: aantal(kpi?.vrij ?? 0), sub: "Stuks, losse artikelen" }), _jsx(KpiTile, { title: "Inkomend", icon: _jsx(ArrowDownToLine, {}), loading: kpiLaden, value: aantal(kpi?.inkomend ?? 0), sub: "Stuks verwacht" })] }), guardTrips.length > 0 && (_jsx(InlineAlert, { tone: "fout", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => window.location.reload(), children: "Opnieuw laden" }), children: "De pagina is gestopt met laden. Laad de pagina opnieuw." })), _jsx(StockIntegrityAlert, {}), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), error && (_jsx(InlineAlert, { tone: "fout", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void reload(), children: "Opnieuw proberen" }), children: "De voorraad laden lukt niet. Probeer opnieuw." })), melding.length > 0 && (_jsx(InlineAlert, { tone: "let_op", icon: _jsx(AlertTriangle, {}), children: _jsx("span", { className: "flex flex-wrap gap-x-4 gap-y-1", children: melding.map((m) => m.filter ? (_jsx("button", { type: "button", className: "underline-offset-2 hover:underline", onClick: () => setFilter(m.filter), children: m.tekst }, m.tekst)) : (_jsx("span", { children: m.tekst }, m.tekst))) }) })), _jsx(PillTabs, { tabs: tabs, value: filter, onChange: setFilter, label: "Filter" }), _jsx(Toolbar, { right: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9", "aria-label": "Kolommen kiezen", children: [_jsx(Columns3, { className: "h-4 w-4 sm:mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Kolommen" })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: "Kolommen kiezen" }), OPTIONELE_KOLOMMEN.map((c) => (_jsx(DropdownMenuCheckboxItem, { checked: kolommen.has(c.key), onCheckedChange: () => toggleKolom(c.key), children: c.label }, c.key)))] })] }), children: _jsx(SearchInput, { value: zoek, onChange: setZoek, placeholder: "Zoek op naam, artikelnummer of barcode\u2026" }) }), selectieBalk && (_jsxs("div", { className: "sticky top-2 z-20 flex flex-wrap items-center gap-2 rounded-lg border border-border bg-card px-4 py-2 text-sm shadow-lg", children: [_jsxs("span", { className: "font-medium tabular-nums", children: [selected.size, " gekozen"] }), _jsx(Button, { size: "sm", className: "h-8", onClick: () => setDrawerOpen(true), children: "Voorraadsturing instellen" }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-8", onClick: () => setSelected(new Set()), children: "Keuze wissen" })] })), filter === "twijfel" ? (_jsx(TwijfelTabel, { rijen: data?.twijfel_rijen ?? [], loading: loading, onGekozen: () => void reload() })) : (_jsx(VoorraadLijstTabel, { rijen: data?.rijen ?? [], loading: loading, sort: sorteer.sort, oplopend: sorteer.oplopend, onSort: (k) => setSorteer((cur) => volgendeSortering(cur, k)), kolommen: kolommen, selected: selected, onToggleSelect: toggleSelect, onToggleAll: toggleAll, onAanpassen: setAanpassen, onAfboeken: setAfboeken, heeftKoppeling: heeftKoppeling, leeg: leeg })), _jsx(VoorraadPagination, { total: data?.totaal ?? 0, page: pagina, pageSize: grootte, onPageChange: setPagina, onPageSizeChange: setGrootte }), _jsx(AanpassenDialog, { rij: aanpassen, onClose: () => setAanpassen(null), onDone: reload }), _jsx(AfboekenDialog, { rij: afboeken, connectionId: stockConnId, onClose: () => setAfboeken(null), onDone: reload }), _jsx(InventorySettingsDrawer, { open: drawerOpen, onOpenChange: setDrawerOpen, connectionId: conn.activeId ?? null, productIds: [...selected], onSaved: reload }), _jsx(MobilePrimaryAction, { onClick: nieuw, hidden: selectieBalk, icon: _jsx(Plus, {}), children: "Nieuw artikel" })] }));
121
130
  }
122
131
  function TwijfelTabel({ rijen, loading, onGekozen }) {
123
132
  const [open, setOpen] = useState(null);
124
- if (!loading && rijen.length === 0) {
125
- return _jsx("p", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: "Geen twijfelgevallen." });
126
- }
127
- return (_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "border-b bg-muted/40 text-xs text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Floriday-artikel" }), _jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Reden" }), _jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Mogelijke artikelen" }), _jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Actie" })] }) }), _jsx("tbody", { children: rijen.map((t) => (_jsxs(Fragment, { children: [_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: "px-3 py-2 font-medium", children: t.naam ?? "(naamloos)" }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: t.reden ?? "Meerdere mogelijke artikelen; niets aangemaakt" }), _jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: t.kandidaten }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsx(Button, { size: "sm", variant: "outline", onClick: () => setOpen(open === t.floriday_id ? null : t.floriday_id), children: "Zelf kiezen" }) })] }), open === t.floriday_id && (_jsx("tr", { className: "border-b", children: _jsx("td", { colSpan: 4, className: "px-3 py-2", children: _jsx(TwijfelKiezen, { floridayId: t.floriday_id, onGekozen: () => { setOpen(null); onGekozen?.(); } }) }) }))] }, t.floriday_id))) })] }));
133
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(DataTable, { rows: rijen, getId: (t) => t.floriday_id, loading: loading, keyboard: false, empty: _jsx(EmptyState, { icon: _jsx(HelpCircle, { className: "h-6 w-6" }), title: "Geen twijfelgevallen", text: "Alle artikelen uit Floriday zijn aan een eigen artikel gekoppeld." }), columns: [
134
+ { key: "naam", header: "Artikel in Floriday", width: "36%", render: (t) => _jsx("span", { className: "font-medium", children: t.naam ?? "(naamloos)" }) },
135
+ { key: "reden", header: "Reden", width: "36%", render: (t) => _jsx("span", { className: "text-muted-foreground", children: t.reden ?? "Meerdere mogelijke artikelen; niets aangemaakt" }) },
136
+ { key: "kandidaten", header: "Mogelijke artikelen", width: "14%", render: (t) => _jsx("span", { className: "tabular-nums", children: t.kandidaten }) },
137
+ {
138
+ key: "actie", header: "Actie", width: "14%",
139
+ render: (t) => (_jsx(Button, { size: "sm", variant: open === t.floriday_id ? "default" : "outline", className: "h-8", onClick: () => setOpen(open === t.floriday_id ? null : t.floriday_id), children: "Zelf kiezen" })),
140
+ },
141
+ ], mobileCard: (t) => (_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: t.naam ?? "(naamloos)" }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [t.kandidaten, " mogelijke artikelen"] })] }), _jsx(Button, { size: "sm", variant: "outline", className: "h-10", onClick: (e) => { e.stopPropagation(); setOpen(open === t.floriday_id ? null : t.floriday_id); }, children: "Zelf kiezen" })] })) }), open && (_jsx("div", { className: "rounded-xl border border-border bg-card p-4 shadow-sm", children: _jsx(TwijfelKiezen, { floridayId: open, onGekozen: () => { setOpen(null); onGekozen?.(); } }) }))] }));
128
142
  }
@@ -41,7 +41,7 @@ export function VoorraadSturingCard() {
41
41
  };
42
42
  const opties = [
43
43
  { waarde: "lokaal", titel: "Lokaal", uitleg: "Je eigen voorraad leidt; Floriday wordt niet bijgewerkt tot je dat per artikel aanzet." },
44
- { waarde: "floriday", titel: "Floriday volgt", uitleg: "Je eigen voorraad gaat via de gewone wachtrij naar Floriday." },
44
+ { waarde: "floriday", titel: "Floriday volgt", uitleg: "Je eigen voorraad gaat vanzelf naar Floriday." },
45
45
  ];
46
46
  return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Standaard voorraadsturing" }), _jsx(CardDescription, { children: "Geldt voor artikelen die automatisch uit Floriday worden aangemaakt. Bestaande artikelen veranderen niet." })] }), _jsx(CardContent, { className: "space-y-3", children: opgeslagen === null ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx("div", { role: "radiogroup", className: "space-y-2", children: opties.map((o) => (_jsxs(Label, { className: "flex cursor-pointer items-start gap-2 rounded-md border p-3 font-normal", children: [_jsx("input", { type: "radio", name: "standaard-sturing", className: "mt-1", checked: keuze === o.waarde, onChange: () => setKeuze(o.waarde) }), _jsxs("span", { children: [_jsx("span", { className: "font-medium", children: o.titel }), _jsx("span", { className: "block text-sm text-muted-foreground", children: o.uitleg })] })] }, o.waarde))) }), _jsxs(Button, { onClick: opslaan, disabled: bezig || keuze === opgeslagen, children: [bezig && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] })] })) })] }));
47
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelPagina.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/artikel/ArtikelPagina.tsx"],"names":[],"mappings":"AAwFA,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BA+bnI"}
1
+ {"version":3,"file":"ArtikelPagina.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/artikel/ArtikelPagina.tsx"],"names":[],"mappings":"AAkGA,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BAoenI"}