@flowselections/floriday-voorraad 1.0.150 → 1.0.154

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 (141) 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/FilterPanel.d.ts +19 -0
  11. package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
  12. package/dist-lib/components/fs-ui/FilterPanel.js +10 -0
  13. package/dist-lib/components/fs-ui/FiltersButton.d.ts +6 -0
  14. package/dist-lib/components/fs-ui/FiltersButton.d.ts.map +1 -0
  15. package/dist-lib/components/fs-ui/FiltersButton.js +7 -0
  16. package/dist-lib/components/fs-ui/HeaderButton.d.ts +16 -0
  17. package/dist-lib/components/fs-ui/HeaderButton.d.ts.map +1 -0
  18. package/dist-lib/components/fs-ui/HeaderButton.js +10 -0
  19. package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
  20. package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
  21. package/dist-lib/components/fs-ui/HoverTip.js +24 -0
  22. package/dist-lib/components/fs-ui/InlineAlert.d.ts +12 -0
  23. package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
  24. package/dist-lib/components/fs-ui/InlineAlert.js +15 -0
  25. package/dist-lib/components/fs-ui/KpiRow.d.ts +10 -0
  26. package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
  27. package/dist-lib/components/fs-ui/KpiRow.js +8 -0
  28. package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
  29. package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
  30. package/dist-lib/components/fs-ui/KpiTile.js +40 -0
  31. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +13 -0
  32. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
  33. package/dist-lib/components/fs-ui/MobilePrimaryAction.js +10 -0
  34. package/dist-lib/components/fs-ui/NoticeBar.d.ts +10 -0
  35. package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
  36. package/dist-lib/components/fs-ui/NoticeBar.js +16 -0
  37. package/dist-lib/components/fs-ui/PageHeader.d.ts +19 -0
  38. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
  39. package/dist-lib/components/fs-ui/PageHeader.js +9 -0
  40. package/dist-lib/components/fs-ui/Pagination.d.ts +10 -0
  41. package/dist-lib/components/fs-ui/Pagination.d.ts.map +1 -0
  42. package/dist-lib/components/fs-ui/Pagination.js +10 -0
  43. package/dist-lib/components/fs-ui/PillTabs.d.ts +17 -0
  44. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
  45. package/dist-lib/components/fs-ui/PillTabs.js +28 -0
  46. package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
  47. package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
  48. package/dist-lib/components/fs-ui/RowMenu.js +7 -0
  49. package/dist-lib/components/fs-ui/SearchInput.d.ts +11 -0
  50. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
  51. package/dist-lib/components/fs-ui/SearchInput.js +55 -0
  52. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +8 -0
  53. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
  54. package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
  55. package/dist-lib/components/fs-ui/StatusIcon.d.ts +13 -0
  56. package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
  57. package/dist-lib/components/fs-ui/StatusIcon.js +15 -0
  58. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +7 -0
  59. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
  60. package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
  61. package/dist-lib/components/fs-ui/Toolbar.d.ts +12 -0
  62. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
  63. package/dist-lib/components/fs-ui/Toolbar.js +8 -0
  64. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +10 -0
  65. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
  66. package/dist-lib/components/fs-ui/UpdatedAt.js +18 -0
  67. package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -1
  68. package/dist-lib/components/voorraad/ArtikelCompositionCard.js +46 -15
  69. package/dist-lib/components/voorraad/ArtikelWizard.js +4 -4
  70. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts +7 -6
  71. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts.map +1 -1
  72. package/dist-lib/components/voorraad/FloridayConnectionNotice.js +15 -12
  73. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  74. package/dist-lib/components/voorraad/FustenPage.js +92 -26
  75. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
  76. package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -1
  77. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +3 -1
  78. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -1
  79. package/dist-lib/components/voorraad/OrderHistoryCard.js +26 -50
  80. package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -1
  81. package/dist-lib/components/voorraad/PublicatiesPage.js +42 -36
  82. package/dist-lib/components/voorraad/Sneltoetsen.d.ts +14 -0
  83. package/dist-lib/components/voorraad/Sneltoetsen.d.ts.map +1 -0
  84. package/dist-lib/components/voorraad/Sneltoetsen.js +44 -0
  85. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts +42 -1
  86. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
  87. package/dist-lib/components/voorraad/StockIntegrityAlert.js +38 -7
  88. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
  89. package/dist-lib/components/voorraad/StockMutationHistoryCard.js +10 -46
  90. package/dist-lib/components/voorraad/StockPushMonitor.js +2 -2
  91. package/dist-lib/components/voorraad/TelmomentButton.d.ts +5 -1
  92. package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -1
  93. package/dist-lib/components/voorraad/TelmomentButton.js +13 -6
  94. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  95. package/dist-lib/components/voorraad/ValidationOverviewTable.js +11 -40
  96. package/dist-lib/components/voorraad/ValidationPage.d.ts +1 -0
  97. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
  98. package/dist-lib/components/voorraad/ValidationPage.js +33 -5
  99. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts +4 -1
  100. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts.map +1 -1
  101. package/dist-lib/components/voorraad/VoorraadBeheerCard.js +26 -5
  102. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  103. package/dist-lib/components/voorraad/VoorraadPage.js +67 -38
  104. package/dist-lib/components/voorraad/VoorraadSturingCard.js +1 -1
  105. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
  106. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +42 -19
  107. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -1
  108. package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +1 -1
  109. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -1
  110. package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +1 -1
  111. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +8 -2
  112. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -1
  113. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +62 -27
  114. package/dist-lib/hooks/useLocalTradeItems.d.ts +14 -0
  115. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  116. package/dist-lib/hooks/useLocalTradeItems.js +5 -3
  117. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  118. package/dist-lib/hooks/useTradeItemStock.js +6 -3
  119. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  120. package/dist-lib/hooks/useVoorraadData.js +16 -8
  121. package/dist-lib/hooks/useVoorraadLijst.d.ts +10 -0
  122. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -1
  123. package/dist-lib/hooks/useVoorraadLijst.js +22 -4
  124. package/dist-lib/integrations/supabase/types.d.ts +85 -0
  125. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  126. package/dist-lib/lib/alle-rijen.d.ts +15 -0
  127. package/dist-lib/lib/alle-rijen.d.ts.map +1 -0
  128. package/dist-lib/lib/alle-rijen.js +17 -0
  129. package/dist-lib/lib/db-error-message.d.ts.map +1 -1
  130. package/dist-lib/lib/db-error-message.js +3 -1
  131. package/dist-lib/lib/labels.d.ts +42 -0
  132. package/dist-lib/lib/labels.d.ts.map +1 -0
  133. package/dist-lib/lib/labels.js +152 -0
  134. package/dist-lib/lib/notatie.d.ts +4 -0
  135. package/dist-lib/lib/notatie.d.ts.map +1 -0
  136. package/dist-lib/lib/notatie.js +18 -0
  137. package/dist-lib/lib/voorraad-lijst.d.ts +2 -0
  138. package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -1
  139. package/dist-lib/lib/voorraad-lijst.js +14 -3
  140. package/package.json +2 -1
  141. package/sql/floriday-voorraad-schema.sql +315 -39
@@ -1,29 +1,55 @@
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 { 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";
10
10
  import { useEnsureVoorraadDefaults } from "../../hooks/useEnsureVoorraadDefaults.js";
11
11
  import { useVoorraadLijst } from "../../hooks/useVoorraadLijst.js";
12
- import { aantal, FILTER_LABEL, meldingen, volgendeSortering, zichtbareFilters, } from "../../lib/voorraad-lijst.js";
13
- import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
14
- import { StockIntegrityAlert } from "./StockIntegrityAlert.js";
12
+ import { aantal, FILTER_LABEL, FILTER_HINT, meldingen, volgendeSortering, zichtbareFilters, } from "../../lib/voorraad-lijst.js";
13
+ import { floridayConnectionNotice } from "./FloridayConnectionNotice.js";
14
+ import { useStockIntegrityNotices } from "./StockIntegrityAlert.js";
15
15
  import { TelmomentButton } from "./TelmomentButton.js";
16
- import { VoorraadPagination } from "./VoorraadPagination.js";
17
16
  import { InventorySettingsDrawer } from "./InventorySettingsDrawer.js";
18
17
  import { OPTIONELE_KOLOMMEN, VoorraadLijstTabel } from "./lijst/VoorraadLijstTabel.js";
19
18
  import { AanpassenDialog, AfboekenDialog } from "./lijst/SnelActieDialogen.js";
20
19
  import { LijstExportKnop } from "./lijst/LijstExportKnop.js";
21
20
  import { ImporteerKnop } from "./lijst/ImporteerKnop.js";
21
+ import { Sneltoetsen } from "./Sneltoetsen.js";
22
+ import { PageHeader } from "../fs-ui/PageHeader.js";
23
+ import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
24
+ import { KpiRow } from "../fs-ui/KpiRow.js";
25
+ import { KpiTile } from "../fs-ui/KpiTile.js";
26
+ import { PillTabs } from "../fs-ui/PillTabs.js";
27
+ import { Toolbar } from "../fs-ui/Toolbar.js";
28
+ import { SearchInput } from "../fs-ui/SearchInput.js";
29
+ import { NoticeBar } from "../fs-ui/NoticeBar.js";
30
+ import { FiltersButton } from "../fs-ui/FiltersButton.js";
31
+ import { FilterChips } from "../fs-ui/FilterChips.js";
32
+ import { FilterPanel, FilterGroup } from "../fs-ui/FilterPanel.js";
33
+ import { Pagination } from "../fs-ui/Pagination.js";
34
+ import { HeaderPrimary } from "../fs-ui/HeaderButton.js";
35
+ import { EmptyState } from "../fs-ui/EmptyState.js";
36
+ import { MobilePrimaryAction } from "../fs-ui/MobilePrimaryAction.js";
37
+ import { DataTable } from "../fs-ui/DataTable.js";
22
38
  // Bestaande export blijft (tests en andere modules).
23
39
  export { countStock } from "../../lib/count-stock.js";
24
40
  const EMPTY_TRIPS = [];
25
41
  const KOLOMMEN_KEY = "voorraad.lijst.kolommen.v2";
26
42
  const STANDAARD_KOLOMMEN = ["inkomend", "prijs"];
43
+ const FILTER_ICON = {
44
+ alles: _jsx(Layers, {}),
45
+ onder_minimum: _jsx(TrendingDown, {}),
46
+ verpakking: _jsx(PackageX, {}),
47
+ niet_gekoppeld: _jsx(Link2Off, {}),
48
+ verwijderd_floriday: _jsx(Link2Off, {}),
49
+ prijs: _jsx(Euro, {}),
50
+ twijfel: _jsx(HelpCircle, {}),
51
+ gearchiveerd: _jsx(Archive, {}),
52
+ };
27
53
  function useKolommen() {
28
54
  const [set, setSet] = useState(new Set(STANDAARD_KOLOMMEN));
29
55
  useEffect(() => {
@@ -68,7 +94,6 @@ export function VoorraadPage() {
68
94
  const navigate = useNavigate();
69
95
  const conn = useFloridayConnection();
70
96
  const stockConnId = useStockContextId();
71
- const [zoekInvoer, setZoekInvoer] = useState("");
72
97
  const [zoek, setZoek] = useState("");
73
98
  const [filter, setFilter] = useState("alles");
74
99
  const [sorteer, setSorteer] = useState({ sort: "naam", oplopend: true });
@@ -76,34 +101,25 @@ export function VoorraadPage() {
76
101
  const [grootte, setGrootte] = useState(25);
77
102
  const [selected, setSelected] = useState(new Set());
78
103
  const [drawerOpen, setDrawerOpen] = useState(false);
104
+ const [telmomentOpen, setTelmomentOpen] = useState(false);
105
+ const [filterOpen, setFilterOpen] = useState(false);
106
+ const integriteit = useStockIntegrityNotices();
79
107
  const [aanpassen, setAanpassen] = useState(null);
80
108
  const [afboeken, setAfboeken] = useState(null);
81
109
  const { kolommen, toggle: toggleKolom } = useKolommen();
82
110
  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]);
111
+ // Nieuwe zoekopdracht, filter of sortering begint op pagina 1.
88
112
  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]);
113
+ const nieuw = useCallback(() => navigate({ to: "/voorraad/nieuw" }), [navigate]);
101
114
  const vraag = { zoek, filter, sort: sorteer.sort, oplopend: sorteer.oplopend, pagina, grootte };
102
- const { data, loading, error, reload } = useVoorraadLijst(vraag);
115
+ const { data, kpi, loading, error, reload } = useVoorraadLijst(vraag);
103
116
  const heeftKoppeling = data?.heeft_koppeling ?? false;
104
117
  const filters = useMemo(() => zichtbareFilters(heeftKoppeling), [heeftKoppeling]);
105
118
  const melding = data ? meldingen(data) : [];
106
- const bijgewerkt = data?.bijgewerkt ? new Date(data.bijgewerkt).toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" }) : null;
119
+ // Twijfelgevallen bestaan alleen bij een koppeling; tonen als er iets te kiezen is of als je er al staat.
120
+ const tabs = filters
121
+ .filter((f) => f !== "twijfel" || heeftKoppeling || (data?.tellers?.twijfel ?? 0) > 0 || filter === "twijfel")
122
+ .map((f) => ({ value: f, label: FILTER_LABEL[f], icon: FILTER_ICON[f], hint: FILTER_HINT[f], count: data?.tellers?.[f] ?? null }));
107
123
  const toggleSelect = (id) => setSelected((cur) => {
108
124
  const next = new Set(cur);
109
125
  if (next.has(id))
@@ -113,16 +129,29 @@ export function VoorraadPage() {
113
129
  return next;
114
130
  });
115
131
  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 })] }));
132
+ const selectieBalk = selected.size > 0 && filter !== "twijfel";
133
+ const kpiLaden = kpi === null;
134
+ // Meldingen: één regel per melding, direct onder de kop.
135
+ const verbindingTekst = floridayConnectionNotice(conn.status, conn.error);
136
+ const noticeItems = [
137
+ ...(guardTrips.length > 0 ? [_jsxs("span", { children: ["De pagina is gestopt met laden. ", _jsx("span", { role: "button", tabIndex: 0, className: "font-medium underline underline-offset-2", onClick: () => window.location.reload(), children: "Opnieuw laden" })] }, "guard")] : []),
138
+ ...(error ? [_jsxs("span", { children: ["De voorraad laden lukt niet. ", _jsx("span", { role: "button", tabIndex: 0, className: "font-medium underline underline-offset-2", onClick: (e) => { e.stopPropagation(); void reload(); }, children: "Opnieuw proberen" })] }, "err")] : []),
139
+ ...integriteit.items,
140
+ ...(verbindingTekst ? [verbindingTekst] : []),
141
+ ...melding.map((m) => m.filter ? (_jsx("span", { role: "button", tabIndex: 0, className: "underline-offset-2 hover:underline", onClick: (e) => { e.stopPropagation(); setFilter(m.filter); }, children: m.tekst }, m.tekst)) : m.tekst),
142
+ ];
143
+ 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_HINT[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"] }) }));
144
+ return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-[calc(7rem+env(safe-area-inset-bottom))] 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-10 w-10 p-0 sm:h-9 sm:w-auto sm:px-3", "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"] })] }))] })] }), _jsx(HeaderPrimary, { icon: _jsx(Plus, {}), label: "Nieuw artikel", kbd: "N", onClick: nieuw })] }) }), _jsx(TelmomentButton, { verbergKnop: true, open: telmomentOpen, onOpenChange: setTelmomentOpen, onSaved: reload }), _jsx(NoticeBar, { items: noticeItems }), integriteit.dialog, _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: false }), _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" })] }), _jsx(PillTabs, { tabs: tabs, value: filter, onChange: setFilter, label: "Filter" }), _jsx(Toolbar, { filters: _jsx(FiltersButton, { count: filter !== "alles" ? 1 : 0, onClick: () => setFilterOpen(true) }), chips: _jsx(FilterChips, { chips: filter !== "alles" ? [{ key: "weergave", label: FILTER_HINT[filter], icon: FILTER_ICON[filter], onRemove: () => setFilter("alles") }] : [] }), right: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9 px-3", "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" }) }), 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(Pagination, { totalCount: data?.totaal ?? 0, page: pagina, pageSize: grootte, onPage: setPagina, onPageSize: setGrootte }), _jsx(FilterPanel, { open: filterOpen, onOpenChange: setFilterOpen, onClear: () => setFilter("alles"), children: _jsx(FilterGroup, { title: "Weergave", value: filter, onChange: setFilter, options: tabs.map((t) => ({ value: t.value, label: FILTER_HINT[t.value] })) }) }), _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
145
  }
122
146
  function TwijfelTabel({ rijen, loading, onGekozen }) {
123
147
  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))) })] }));
148
+ 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: [
149
+ { key: "naam", header: "Artikel in Floriday", width: "36%", render: (t) => _jsx("span", { className: "font-medium", children: t.naam ?? "(naamloos)" }) },
150
+ { key: "reden", header: "Reden", width: "36%", render: (t) => _jsx("span", { className: "text-muted-foreground", children: t.reden ?? "Meerdere mogelijke artikelen; niets aangemaakt" }) },
151
+ { key: "kandidaten", header: "Mogelijke artikelen", width: "14%", render: (t) => _jsx("span", { className: "tabular-nums", children: t.kandidaten }) },
152
+ {
153
+ key: "actie", header: "Actie", width: "14%",
154
+ 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" })),
155
+ },
156
+ ], 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
157
  }
@@ -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":"AAmGA,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,+BA6enI"}
@@ -1,8 +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 { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { Link, useBlocker } from "@tanstack/react-router";
4
- import { Badge, Button, Card, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Label, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
5
- import { AlertTriangle, ArrowLeft, Loader2, MoreHorizontal, Package, Send } from "lucide-react";
3
+ import { Link, useBlocker, useRouterState } from "@tanstack/react-router";
4
+ import { datum } from "../../../lib/notatie.js";
5
+ import { Button, Card, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Input, Label, Skeleton, Tabs, TabsContent, toast, } from "@flowselections/core";
6
+ import { AlertTriangle, Archive, ArchiveRestore, ArrowDownToLine, ArrowLeft, Boxes, Layers, Link2, Loader2, Lock, MoreHorizontal, Minus, Package, Send, SlidersHorizontal } from "lucide-react";
6
7
  import { supabase } from "../../../integrations/supabase/client.js";
7
8
  import { dbFoutTekst } from "../../../lib/db-error-message.js";
8
9
  import { ARTIKEL_GEWIJZIGD } from "../../../lib/artikel-gewijzigd.js";
@@ -11,6 +12,14 @@ import { STATUS_INFO, aantal, geld } from "../../../lib/voorraad-lijst.js";
11
12
  import { AanpassenDialog, AfboekenDialog } from "../lijst/SnelActieDialogen.js";
12
13
  import { TwijfelKiezen } from "./TwijfelKiezen.js";
13
14
  import { OpslaanContext } from "./opslaan-context.js";
15
+ import { ArtikelStatusIcoon } from "../lijst/VoorraadLijstTabel.js";
16
+ import { PageHeader } from "../../fs-ui/PageHeader.js";
17
+ import { KpiRow } from "../../fs-ui/KpiRow.js";
18
+ import { KpiTile } from "../../fs-ui/KpiTile.js";
19
+ import { PillTabs } from "../../fs-ui/PillTabs.js";
20
+ import { InlineAlert } from "../../fs-ui/InlineAlert.js";
21
+ import { HoverTip } from "../../fs-ui/HoverTip.js";
22
+ import { MobilePrimaryAction } from "../../fs-ui/MobilePrimaryAction.js";
14
23
  // Tabbladinhoud laadt pas als je het tabblad opent.
15
24
  const StockMutationHistoryCard = lazy(() => import("../StockMutationHistoryCard.js").then((m) => ({ default: m.StockMutationHistoryCard })));
16
25
  const OrderHistoryCard = lazy(() => import("../OrderHistoryCard.js").then((m) => ({ default: m.OrderHistoryCard })));
@@ -21,6 +30,12 @@ const ProductCustomFieldsCard = lazy(() => import("../ProductCustomFieldsCard.js
21
30
  const ProductLinkCard = lazy(() => import("../ProductLinkCard.js").then((m) => ({ default: m.ProductLinkCard })));
22
31
  const FloridayInfoCard = lazy(() => import("../FloridayInfoCard.js").then((m) => ({ default: m.FloridayInfoCard })));
23
32
  const ArtikelWizard = lazy(() => import("../ArtikelWizard.js").then((m) => ({ default: m.ArtikelWizard })));
33
+ const TAB_ICON = {
34
+ voorraad: _jsx(Package, {}),
35
+ verpakking: _jsx(Boxes, {}),
36
+ koppelingen: _jsx(Link2, {}),
37
+ floriday: _jsx(Send, {}),
38
+ };
24
39
  const TAB_LABEL = {
25
40
  voorraad: "Voorraad",
26
41
  verpakking: "Verpakking en samenstelling",
@@ -28,7 +43,7 @@ const TAB_LABEL = {
28
43
  floriday: "Floriday",
29
44
  };
30
45
  function Laden() {
31
- return (_jsxs("div", { className: "flex items-center gap-2 p-4 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden\u2026"] }));
46
+ return _jsx(Skeleton, { className: "h-40 w-full rounded-xl", "aria-label": "Laden\u2026" });
32
47
  }
33
48
  function toon(v) {
34
49
  if (v === null || v === undefined || v === "")
@@ -239,7 +254,7 @@ export function ArtikelPagina({ id, resumeJobId, onNietGevonden }) {
239
254
  if (alleConflicten.length)
240
255
  setConflicten(alleConflicten);
241
256
  if (opgeslagen)
242
- toast.success(alleConflicten.length ? "Deels opgeslagen; kies bij de conflicten." : "Opgeslagen (alleen lokaal).");
257
+ toast.success(alleConflicten.length ? "Deels opgeslagen; kies bij de conflicten." : detail?.heeft_koppeling ? "Opgeslagen. Niet naar Floriday gestuurd." : "Opgeslagen.");
243
258
  await laad();
244
259
  }
245
260
  /** Conflict: neem hun waarde over voor dat veld en leg de nieuwe stand vast. */
@@ -272,7 +287,7 @@ export function ArtikelPagina({ id, resumeJobId, onNietGevonden }) {
272
287
  return;
273
288
  }
274
289
  setArchief(null);
275
- toast.success(archiefKeuze === "floriday" ? "Artikel gearchiveerd. De voorraad op Floriday gaat via de gewone wachtrij naar 0." : "Artikel gearchiveerd. Op Floriday is niets veranderd.");
290
+ toast.success(archiefKeuze === "floriday" ? "Artikel gearchiveerd. De voorraad op Floriday gaat naar 0 zodra Floriday het verwerkt." : "Artikel gearchiveerd. Op Floriday is niets veranderd.");
276
291
  await laad();
277
292
  }
278
293
  async function terugzetten() {
@@ -284,11 +299,23 @@ export function ArtikelPagina({ id, resumeJobId, onNietGevonden }) {
284
299
  toast.success("Artikel staat weer in de voorraad.");
285
300
  await laad();
286
301
  }
302
+ // Vanaf de controlepagina: open het overzicht vooraf (er wordt niets verstuurd zonder bevestiging).
303
+ const vraagOverzicht = useRouterState({ select: (st) => Boolean(st.location.state?.stuurOverzicht) });
304
+ const overzichtGeopend = useRef(false);
305
+ useEffect(() => {
306
+ if (!vraagOverzicht || overzichtGeopend.current || !detail)
307
+ return;
308
+ overzichtGeopend.current = true;
309
+ if (floridayActie(detail)) {
310
+ setFloridayOpen(true);
311
+ setTab("floriday");
312
+ }
313
+ }, [vraagOverzicht, detail]); // eslint-disable-line react-hooks/exhaustive-deps
287
314
  if (fout) {
288
- return _jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6 text-sm", children: fout }) });
315
+ return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", 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" }), " Voorraad"] }), _jsx(InlineAlert, { tone: "fout", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => { setFout(null); geladen.current = false; void laad(); }, children: "Opnieuw proberen" }), children: fout })] }));
289
316
  }
290
317
  if (!detail || !nieuw) {
291
- return _jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: _jsx(Laden, {}) }) });
318
+ return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", "aria-busy": "true", children: [_jsx(Skeleton, { className: "h-4 w-24" }), _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Skeleton, { className: "h-16 w-16 rounded-xl" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-64" }), _jsx(Skeleton, { className: "h-3 w-32" })] })] }), _jsx(KpiRow, { children: [0, 1, 2, 3].map((i) => _jsx(KpiTile, { title: "", value: "", loading: true }, i)) }), _jsx(Skeleton, { className: "h-9 w-80 rounded-lg" }), _jsx(Skeleton, { className: "h-48 w-full rounded-xl" })] }));
292
319
  }
293
320
  const status = artikelStatus({ ...detail, velden: nieuw });
294
321
  const info = STATUS_INFO[status];
@@ -297,17 +324,13 @@ export function ArtikelPagina({ id, resumeJobId, onNietGevonden }) {
297
324
  if (floridayOpen && !tabs.includes("floriday") && actie === "op_floriday")
298
325
  tabs.push("floriday");
299
326
  const rij = alsLijstRij(detail);
300
- return (_jsxs("main", { className: "p-6 space-y-4", 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"] }), _jsx(Card, { className: "p-4", children: _jsxs("div", { className: "flex flex-wrap items-start gap-4", children: [_jsx("div", { className: "h-20 w-20 shrink-0 overflow-hidden rounded-md border bg-muted flex items-center justify-center", children: detail.foto ? _jsx("img", { src: detail.foto, alt: detail.naam, className: "h-full w-full object-cover" }) : _jsx(Package, { className: "h-8 w-8 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0 flex-1 basis-64", children: [_jsx("h1", { className: "text-xl font-semibold leading-tight break-words", children: detail.naam }), _jsx("div", { className: "mt-1 text-sm text-muted-foreground break-words", children: detail.artikelnummer ?? "Geen artikelnummer" }), _jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2 text-sm", children: [_jsx(Badge, { variant: status === "ok" ? "secondary" : status === "gearchiveerd" ? "outline" : "destructive", children: info.label }), info.actie && info.tab && tabs.includes(info.tab) && (_jsx("button", { type: "button", className: "underline text-muted-foreground", onClick: () => setTab(info.tab), children: info.actie })), vuil && _jsx("span", { className: "text-muted-foreground", children: "\u00B7 Niet-opgeslagen wijzigingen" })] })] }), _jsx("dl", { className: "grid grid-cols-3 gap-4 text-center sm:grid-cols-5", children: [
301
- ["Liggend", aantal(detail.liggend)],
302
- ["Gereserveerd", aantal(detail.gereserveerd)],
303
- ["Inkomend", aantal(detail.inkomend)],
304
- ["Vrij", aantal(detail.vrij)],
305
- ["Prijs per stuk", geld(detail.velden.sale_price === null ? null : Number(detail.velden.sale_price))],
306
- ].map(([l, v], i) => (_jsxs("div", { children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: l }), _jsx("dd", { className: `text-lg font-semibold tabular-nums ${i === 3 && rij.onder_minimum ? "text-destructive" : ""}`, children: v })] }, l))) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { onClick: doeOpslaan, disabled: !vuil || opslaan || Boolean(prijsFout) || Boolean(aantalFout), children: [opslaan && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] }), actie && (_jsxs(Button, { variant: "outline", onClick: () => { setFloridayOpen(true); setTab("floriday"); }, children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), actie === "sturen" ? "Naar Floriday sturen" : "Op Floriday zetten"] })), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Meer acties", children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: detail.gearchiveerd_at
307
- ? _jsx(DropdownMenuItem, { onClick: terugzetten, children: "Terugzetten in voorraad" })
308
- : _jsx(DropdownMenuItem, { onClick: () => { setArchiefKeuze("hier"); setArchief({}); }, children: "Archiveren\u2026" }) })] })] })] }) }), detail.gearchiveerd_at && (detail.orders_gearchiveerd ?? 0) > 0 && (_jsxs("div", { role: "alert", className: "flex flex-wrap items-center gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: ["Dit artikel is gearchiveerd, maar er ", detail.orders_gearchiveerd === 1 ? "is 1 open orderregel" : `zijn ${detail.orders_gearchiveerd} open orderregels`, " binnengekomen. De orders zijn gewoon gekoppeld."] }), _jsx(Button, { size: "sm", variant: "outline", onClick: terugzetten, children: "Terugzetten in voorraad" })] })), _jsx(OpslaanContext.Provider, { value: register, children: _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsx(TabsList, { children: tabs.map((t) => _jsx(TabsTrigger, { value: t, children: TAB_LABEL[t] }, t)) }), _jsxs(TabsContent, { value: "voorraad", forceMount: bezocht.has("voorraad") ? true : undefined, className: "space-y-4 data-[state=inactive]:hidden", children: [_jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("h2", { className: "font-medium", children: "Artikelgegevens" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Opslaan is alleen lokaal; er gaat niets automatisch naar Floriday." })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-5", children: ARTIKEL_VELDEN.map((k) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `veld-${k}`, children: VELD_LABEL[k] }), _jsx(Input, { id: `veld-${k}`, value: invoer[k] ?? "", inputMode: k === "sale_price" ? "decimal" : undefined, onChange: (e) => setInvoer((s) => ({ ...s, [k]: e.target.value })) }), k === "sale_price" && prijsFout && _jsx("p", { className: "text-xs text-destructive", children: prijsFout })] }, k))) })] }), _jsxs(Card, { className: "p-4 flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "grid w-full gap-3 sm:grid-cols-3", children: [VOORRAAD_VELDEN.map((k) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `voorraad-${k}`, children: VOORRAAD_LABEL[k] }), _jsx(Input, { id: `voorraad-${k}`, value: invoer[k] ?? "", inputMode: k === "notes" ? undefined : "numeric", onChange: (e) => setInvoer((s) => ({ ...s, [k]: e.target.value })) })] }, k))), aantalFout && _jsx("p", { className: "text-xs text-destructive sm:col-span-3", children: aantalFout })] }), _jsxs("div", { className: "text-sm", children: [_jsx("div", { className: "font-medium", children: "Voorraad" }), _jsxs("div", { className: "text-muted-foreground", children: ["Liggend wijzig je met Aanpassen of Afboeken \u00B7 Telmoment ", detail.telmoment ? new Date(detail.telmoment).toLocaleString("nl-NL") : _jsx("span", { className: "text-destructive", children: "ontbreekt" })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => setAanpassen(true), disabled: Boolean(detail.gearchiveerd_at), children: "Aanpassen" }), _jsx(Button, { variant: "outline", onClick: () => setAfboeken(true), disabled: Boolean(detail.gearchiveerd_at), children: "Afboeken" })] })] }), _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [_jsx(StockMutationHistoryCard, { tradeItemKey: detail.voorraad_sleutel, productId: detail.id, sleutels: detail.controle?.mutatie_sleutels ?? undefined }), _jsx(OrderHistoryCard, { tradeItemKey: detail.voorraad_sleutel, productName: detail.naam, productId: detail.id, productIds: detail.controle?.zelfde_sleutel_ids }), _jsx(ProductCustomFieldsCard, { productId: detail.id, beginVelden: detail.eigen_velden?.definities, beginWaarden: detail.eigen_velden?.waarden })] })] }), _jsx(TabsContent, { value: "verpakking", forceMount: bezocht.has("verpakking") ? true : undefined, className: "space-y-4 data-[state=inactive]:hidden", children: _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [_jsx(ProductStructureCard, { productId: detail.id }), _jsx(ArtikelCompositionCard, { productId: detail.id, floridayId: detail.floriday_id }), _jsx(TrayStickerCard, { productId: detail.id })] }) }), _jsxs(TabsContent, { value: "koppelingen", className: "space-y-4", children: [_jsx(TwijfelKiezen, { productId: detail.id, onGekozen: () => void laad() }), _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [detail.floriday_id && detail.heeft_koppeling ? (_jsx(FloridayInfoCard, { floridayTradeItemId: detail.floriday_id, local: { name: detail.naam, articleNumber: detail.artikelnummer, gtin: detail.velden.barcode ?? null } })) : (_jsx(Card, { className: "p-4 text-sm text-muted-foreground", children: detail.heeft_koppeling ? (detail.floriday_verwijderd_at ? "Dit artikel is in Floriday verwijderd." : "Dit artikel is niet aan Floriday gekoppeld.") : "Er is geen Floriday-koppeling in deze omgeving; het artikel werkt volledig lokaal." })), _jsx(ProductLinkCard, { tradeItemKey: detail.voorraad_sleutel, productName: detail.naam })] })] }), tabs.includes("floriday") && (_jsx(TabsContent, { value: "floriday", children: _jsx(Suspense, { fallback: _jsx(Laden, {}), children: _jsx(ArtikelWizard, { productId: detail.id, resumeJobId: resumeJobId, embedded: true }) }) }))] }) }), _jsx(AanpassenDialog, { rij: aanpassen ? rij : null, onClose: () => setAanpassen(false), onDone: () => { setAanpassen(false); void laad(); } }), _jsx(AfboekenDialog, { rij: afboeken ? rij : null, connectionId: null, onClose: () => setAfboeken(false), onDone: () => { setAfboeken(false); void laad(); } }), _jsx(Dialog, { open: Boolean(conflicten && conflicten.length), onOpenChange: (o) => !o && setConflicten(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Iemand anders heeft dit ook gewijzigd" }), _jsx(DialogDescription, { children: "Er is niets opgeslagen. Kies per veld welke waarde blijft." })] }), _jsx("div", { className: "space-y-3", children: (conflicten ?? []).map((c) => (_jsxs("div", { className: "rounded-md border p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: VELD_LABEL[c.veld] ?? VOORRAAD_LABEL[c.veld] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Jouw wijziging" }), toon(c.jouw)] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Hun wijziging" }), toon(c.hun)] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => houdJouw(c), children: "Mijn waarde houden" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => neemHunOver(c), children: "Hun waarde overnemen" })] })] }, c.veld))) })] }) }), _jsx(Dialog, { open: archief !== null, onOpenChange: (o) => !o && setArchief(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Artikel archiveren" }), _jsx(DialogDescription, { children: "Het artikel wordt nooit verwijderd. Je kunt het later terugzetten." })] }), archief?.waarschuwing && (_jsxs("div", { className: "flex gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm", children: [_jsx(AlertTriangle, { className: "h-4 w-4 shrink-0 text-destructive" }), _jsxs("span", { children: ["Let op: er ", archief.waarschuwing.open_orders === 1 ? "is 1 open order" : `zijn ${archief.waarschuwing.open_orders} open orders`, " en ", aantal(archief.waarschuwing.gereserveerd), " gereserveerd. Die blijven bestaan."] })] })), _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("label", { className: "flex gap-2 items-start", children: [_jsx("input", { type: "radio", checked: archiefKeuze === "hier", onChange: () => setArchiefKeuze("hier") }), _jsxs("span", { children: [_jsx("strong", { children: detail.floriday_id && detail.heeft_koppeling ? "Alleen hier." : "Archiveren." }), " ", detail.floriday_id && detail.heeft_koppeling && !detail.floriday_verwijderd_at
327
+ const opslaanUit = !vuil || opslaan || Boolean(prijsFout) || Boolean(aantalFout);
328
+ const gearchiveerd = Boolean(detail.gearchiveerd_at);
329
+ return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-[calc(7rem+env(safe-area-inset-bottom))] 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"] }), media: _jsx("div", { className: "flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-xl border border-border bg-muted", children: detail.foto ? _jsx("img", { src: detail.foto, alt: detail.naam, className: "h-full w-full object-cover" }) : _jsx(Package, { className: "h-7 w-7 text-muted-foreground" }) }), title: detail.naam, updated: _jsx("span", { className: "block break-words", children: detail.artikelnummer ?? "Geen artikelnummer" }), meta: _jsxs(_Fragment, { children: [_jsx(ArtikelStatusIcoon, { status: status }), _jsx("span", { className: status === "ok" ? "text-muted-foreground" : "font-medium", children: info.label }), info.actie && info.tab && tabs.includes(info.tab) && (_jsx("button", { type: "button", className: "text-primary underline-offset-2 hover:underline", onClick: () => setTab(info.tab), children: info.actie })), _jsx("span", { className: "text-muted-foreground", children: "\u00B7" }), _jsxs("span", { className: "tabular-nums", children: [geld(detail.velden.sale_price === null ? null : Number(detail.velden.sale_price)), " per stuk"] }), detail.floriday_id && detail.heeft_koppeling && (_jsx(HoverTip, { label: "Gekoppeld met Floriday", content: "Gekoppeld met Floriday", className: "h-5 w-5 text-muted-foreground [&_svg]:h-3.5 [&_svg]:w-3.5", children: _jsx(Link2, {}) })), vuil && _jsx("span", { className: "text-xs font-medium text-warning", children: "Niet opgeslagen" })] }), actions: _jsxs(_Fragment, { children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-10 w-10 p-0 sm:h-9 sm:w-auto sm:px-3", "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, { disabled: gearchiveerd, onSelect: () => setAanpassen(true), children: [_jsx(SlidersHorizontal, { className: "mr-2 h-4 w-4" }), "Voorraad aanpassen"] }), _jsxs(DropdownMenuItem, { disabled: gearchiveerd, onSelect: () => setAfboeken(true), children: [_jsx(Minus, { className: "mr-2 h-4 w-4" }), "Afboeken"] }), actie && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: () => { setFloridayOpen(true); setTab("floriday"); }, children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), actie === "sturen" ? "Naar Floriday sturen" : "Op Floriday zetten"] })] })), _jsx(DropdownMenuSeparator, {}), gearchiveerd
330
+ ? _jsxs(DropdownMenuItem, { onSelect: terugzetten, children: [_jsx(ArchiveRestore, { className: "mr-2 h-4 w-4" }), "Terugzetten in voorraad"] })
331
+ : _jsxs(DropdownMenuItem, { onSelect: () => { setArchiefKeuze("hier"); setArchief({}); }, children: [_jsx(Archive, { className: "mr-2 h-4 w-4" }), "Archiveren\u2026"] })] })] }), _jsxs(Button, { size: "sm", className: "hidden sm:inline-flex", onClick: doeOpslaan, disabled: opslaanUit, children: [opslaan && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] })] }) }), gearchiveerd && (detail.orders_gearchiveerd ?? 0) > 0 && (_jsxs(InlineAlert, { tone: "let_op", action: _jsx(Button, { size: "sm", variant: "outline", className: "h-8", onClick: terugzetten, children: "Terugzetten in voorraad" }), children: ["Dit artikel is gearchiveerd, maar er ", detail.orders_gearchiveerd === 1 ? "is 1 open orderregel" : `zijn ${detail.orders_gearchiveerd} open orderregels`, " binnengekomen. De orders zijn gewoon gekoppeld."] })), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Liggend", icon: _jsx(Package, {}), value: aantal(detail.liggend), sub: _jsxs(_Fragment, { children: ["Telmoment ", detail.telmoment ? datum(detail.telmoment) : _jsx("span", { className: "text-destructive", children: "ontbreekt" })] }) }), _jsx(KpiTile, { title: "Gereserveerd", icon: _jsx(Lock, {}), value: aantal(detail.gereserveerd), sub: "Voor open orders" }), _jsx(KpiTile, { title: "Vrij beschikbaar", icon: _jsx(Layers, {}), tone: rij.onder_minimum ? "warning" : "default", value: _jsx("span", { className: rij.onder_minimum ? "text-destructive" : "", children: aantal(detail.vrij) }), sub: detail.minimum > 0 ? (rij.onder_minimum ? _jsxs("span", { className: "text-destructive", children: ["Onder minimum (", aantal(detail.minimum), ")"] }) : `Minimum ${aantal(detail.minimum)}`) : "Geen minimum" }), _jsx(KpiTile, { title: "Inkomend", icon: _jsx(ArrowDownToLine, {}), value: aantal(detail.inkomend), sub: "Stuks verwacht" })] }), _jsx(OpslaanContext.Provider, { value: register, children: _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsx(PillTabs, { tabs: tabs.map((t) => ({ value: t, label: TAB_LABEL[t], icon: TAB_ICON[t] })), value: tab, onChange: setTab, label: "Onderdelen van het artikel" }), _jsxs(TabsContent, { value: "voorraad", forceMount: bezocht.has("voorraad") ? true : undefined, className: "mt-4 space-y-4 data-[state=inactive]:hidden", children: [_jsxs(Card, { className: "space-y-4 rounded-xl p-4 shadow-sm sm:p-5", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("h2", { className: "text-base font-semibold", children: "Artikelgegevens" }), detail.heeft_koppeling && _jsx("span", { className: "text-xs text-muted-foreground", children: "Opslaan bewaart alleen in je eigen administratie. Naar Floriday sturen doe je apart." })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-5", children: ARTIKEL_VELDEN.map((k) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `veld-${k}`, children: VELD_LABEL[k] }), _jsx(Input, { id: `veld-${k}`, className: "h-10 rounded-lg text-base sm:h-9 sm:text-sm", value: invoer[k] ?? "", inputMode: k === "sale_price" ? "decimal" : undefined, onChange: (e) => setInvoer((s) => ({ ...s, [k]: e.target.value })) }), k === "sale_price" && prijsFout && _jsx("p", { className: "text-xs text-destructive", children: prijsFout })] }, k))) })] }), _jsxs(Card, { className: "space-y-4 rounded-xl p-4 shadow-sm sm:p-5", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-base font-semibold", children: "Voorraad" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Liggend wijzig je met Aanpassen of Afboeken \u00B7 Telmoment ", detail.telmoment ? new Date(detail.telmoment).toLocaleString("nl-NL") : _jsx("span", { className: "text-destructive", children: "ontbreekt" })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", className: "h-9", onClick: () => setAanpassen(true), disabled: gearchiveerd, children: [_jsx(SlidersHorizontal, { className: "mr-2 h-4 w-4" }), "Aanpassen"] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-9", onClick: () => setAfboeken(true), disabled: gearchiveerd, children: [_jsx(Minus, { className: "mr-2 h-4 w-4" }), "Afboeken"] })] })] }), _jsxs("div", { className: "grid w-full gap-3 sm:grid-cols-3", children: [VOORRAAD_VELDEN.map((k) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `voorraad-${k}`, children: VOORRAAD_LABEL[k] }), _jsx(Input, { id: `voorraad-${k}`, className: "h-10 rounded-lg text-base sm:h-9 sm:text-sm", value: invoer[k] ?? "", inputMode: k === "notes" ? undefined : "numeric", onChange: (e) => setInvoer((s) => ({ ...s, [k]: e.target.value })) })] }, k))), aantalFout && _jsx("p", { className: "text-xs text-destructive sm:col-span-3", children: aantalFout })] })] }), _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [_jsx(StockMutationHistoryCard, { tradeItemKey: detail.voorraad_sleutel, productId: detail.id, sleutels: detail.controle?.mutatie_sleutels ?? undefined }), _jsx(OrderHistoryCard, { tradeItemKey: detail.voorraad_sleutel, productName: detail.naam, productId: detail.id, productIds: detail.controle?.zelfde_sleutel_ids }), _jsx(ProductCustomFieldsCard, { productId: detail.id, beginVelden: detail.eigen_velden?.definities, beginWaarden: detail.eigen_velden?.waarden })] })] }), _jsx(TabsContent, { value: "verpakking", forceMount: bezocht.has("verpakking") ? true : undefined, className: "mt-4 space-y-4 data-[state=inactive]:hidden", children: _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [_jsx(ProductStructureCard, { productId: detail.id }), _jsx(ArtikelCompositionCard, { productId: detail.id, floridayId: detail.floriday_id }), _jsx(TrayStickerCard, { productId: detail.id })] }) }), _jsxs(TabsContent, { value: "koppelingen", className: "mt-4 space-y-4", children: [_jsx(TwijfelKiezen, { productId: detail.id, onGekozen: () => void laad() }), _jsxs(Suspense, { fallback: _jsx(Laden, {}), children: [detail.floriday_id && detail.heeft_koppeling ? (_jsx(FloridayInfoCard, { floridayTradeItemId: detail.floriday_id, local: { name: detail.naam, articleNumber: detail.artikelnummer, gtin: detail.velden.barcode ?? null } })) : detail.heeft_koppeling ? (_jsx(Card, { className: "rounded-xl p-4 text-sm text-muted-foreground shadow-sm", children: detail.floriday_verwijderd_at ? "Dit artikel is in Floriday verwijderd." : "Dit artikel is niet aan Floriday gekoppeld." })) : null, _jsx(ProductLinkCard, { tradeItemKey: detail.voorraad_sleutel, productName: detail.naam })] })] }), tabs.includes("floriday") && (_jsx(TabsContent, { value: "floriday", className: "mt-4", children: _jsx(Suspense, { fallback: _jsx(Laden, {}), children: _jsx(ArtikelWizard, { productId: detail.id, resumeJobId: resumeJobId, embedded: true }) }) }))] }) }), _jsx(MobilePrimaryAction, { onClick: () => void doeOpslaan(), disabled: opslaanUit, children: opslaan ? "Opslaan…" : "Opslaan" }), _jsx(AanpassenDialog, { rij: aanpassen ? rij : null, onClose: () => setAanpassen(false), onDone: () => { setAanpassen(false); void laad(); } }), _jsx(AfboekenDialog, { rij: afboeken ? rij : null, connectionId: null, onClose: () => setAfboeken(false), onDone: () => { setAfboeken(false); void laad(); } }), _jsx(Dialog, { open: Boolean(conflicten && conflicten.length), onOpenChange: (o) => !o && setConflicten(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Iemand anders heeft dit ook gewijzigd" }), _jsx(DialogDescription, { children: "Er is niets opgeslagen. Kies per veld welke waarde blijft." })] }), _jsx("div", { className: "space-y-3", children: (conflicten ?? []).map((c) => (_jsxs("div", { className: "rounded-md border p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: VELD_LABEL[c.veld] ?? VOORRAAD_LABEL[c.veld] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Jouw wijziging" }), toon(c.jouw)] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Hun wijziging" }), toon(c.hun)] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => houdJouw(c), children: "Mijn waarde houden" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => neemHunOver(c), children: "Hun waarde overnemen" })] })] }, c.veld))) })] }) }), _jsx(Dialog, { open: archief !== null, onOpenChange: (o) => !o && setArchief(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Artikel archiveren" }), _jsx(DialogDescription, { children: "Het artikel wordt nooit verwijderd. Je kunt het later terugzetten." })] }), archief?.waarschuwing && (_jsxs("div", { className: "flex gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm", children: [_jsx(AlertTriangle, { className: "h-4 w-4 shrink-0 text-destructive" }), _jsxs("span", { children: ["Let op: er ", archief.waarschuwing.open_orders === 1 ? "is 1 open order" : `zijn ${archief.waarschuwing.open_orders} open orders`, " en ", aantal(archief.waarschuwing.gereserveerd), " gereserveerd. Die blijven bestaan."] })] })), _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("label", { className: "flex gap-2 items-start", children: [_jsx("input", { type: "radio", checked: archiefKeuze === "hier", onChange: () => setArchiefKeuze("hier") }), _jsxs("span", { children: [_jsx("strong", { children: detail.floriday_id && detail.heeft_koppeling ? "Alleen hier." : "Archiveren." }), " ", detail.floriday_id && detail.heeft_koppeling && !detail.floriday_verwijderd_at
309
332
  ? "Floriday blijft ongewijzigd: het artikel blijft op Floriday te koop."
310
333
  : "Het artikel verdwijnt uit de voorraadlijst en de tellers."] })] }), detail.floriday_id && detail.heeft_koppeling && !detail.floriday_verwijderd_at && (_jsxs("label", { className: "flex gap-2 items-start", children: [_jsx("input", { type: "radio", checked: archiefKeuze === "floriday", onChange: () => setArchiefKeuze("floriday") }), _jsxs("span", { children: [_jsx("strong", { children: "Ook in Floriday." }), " De voorraad op Floriday wordt 0."] })] })), archiefKeuze === "floriday" && (_jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsx("div", { className: "font-medium", children: "Overzicht vooraf" }), _jsx("div", { children: "Voorraad op Floriday: wordt 0 (niet meer te koop)." }), _jsxs("div", { children: ["Je eigen voorraad blijft ", aantal(detail.liggend), " liggend."] }), _jsx("div", { className: "text-muted-foreground", children: detail.floriday_stuurt
311
- ? "Gaat via de gewone Floriday-wachtrij, met de bestaande schrijfstop."
334
+ ? "Wordt op de gewone manier naar Floriday gestuurd. Staat het versturen naar Floriday stil, dan wacht dit tot het weer aan staat."
312
335
  : "Floriday is voor dit artikel leidend: er wordt niets verstuurd tot het systeem de voorraad stuurt." })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => setArchief(null), children: "Annuleren" }), _jsx(Button, { variant: "destructive", disabled: bezig, onClick: () => archiveren(Boolean(archief?.waarschuwing)), children: archief?.waarschuwing ? "Toch archiveren" : "Archiveren" })] })] }) })] }));
313
336
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImporteerKnop.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/ImporteerKnop.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,+BAmBlH"}
1
+ {"version":3,"file":"ImporteerKnop.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/ImporteerKnop.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,+BAkBlH"}
@@ -16,5 +16,5 @@ function ImportInhoud({ connectionId, onImported }) {
16
16
  }
17
17
  export function ImporteerKnop({ connectionId, onImported }) {
18
18
  const [open, setOpen] = useState(false);
19
- return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setOpen(true), children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), "Importeren"] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Importeren" }), _jsx(DialogDescription, { children: "Download het sjabloon of kies een bestand. Je krijgt eerst een proefcontrole." })] }), open && _jsx(ImportInhoud, { connectionId: connectionId, onImported: onImported })] }) })] }));
19
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setOpen(true), "aria-label": "Importeren", className: "h-10 w-10 p-0 sm:h-9 sm:w-auto sm:px-3", children: [_jsx(Upload, { className: "h-4 w-4 sm:mr-2" }), " ", _jsx("span", { className: "hidden sm:inline", children: "Importeren" })] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Importeren" }), _jsx(DialogDescription, { children: "Download het sjabloon of kies een bestand. Je krijgt eerst een proefcontrole." })] }), open && _jsx(ImportInhoud, { connectionId: connectionId, onImported: onImported })] }) })] }));
20
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LijstExportKnop.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/LijstExportKnop.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAGrF,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAA;CAAE,+BAuC3F"}
1
+ {"version":3,"file":"LijstExportKnop.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/LijstExportKnop.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAGrF,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAA;CAAE,+BAsC3F"}
@@ -39,5 +39,5 @@ export function LijstExportKnop({ vraag }) {
39
39
  setBezig(null);
40
40
  }
41
41
  };
42
- return (_jsxs(Button, { variant: "outline", size: "sm", onClick: exporteer, disabled: !!bezig, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), bezig ?? "Exporteren"] }));
42
+ return (_jsxs(Button, { variant: "outline", size: "sm", onClick: exporteer, disabled: !!bezig, "aria-label": "Exporteren", className: "h-10 w-10 p-0 sm:h-9 sm:w-auto sm:px-3", children: [_jsx(Download, { className: "h-4 w-4 sm:mr-2" }), " ", _jsx("span", { className: "hidden sm:inline", children: bezig ?? "Exporteren" })] }));
43
43
  }
@@ -1,10 +1,14 @@
1
- import { type LijstRij, type LijstSort } from "../../../lib/voorraad-lijst.js";
1
+ import type { ReactNode } from "react";
2
+ import { type LijstRij, type LijstSort, type LijstStatus } from "../../../lib/voorraad-lijst.js";
2
3
  export type OptioneleKolom = "artikelnummer" | "minimum" | "inkomend" | "prijs";
3
4
  export declare const OPTIONELE_KOLOMMEN: Array<{
4
5
  key: OptioneleKolom;
5
6
  label: string;
6
7
  }>;
7
- export declare function VoorraadLijstTabel({ rijen, loading, sort, oplopend, onSort, kolommen, selected, onToggleSelect, onToggleAll, onAanpassen, onAfboeken, heeftKoppeling, }: {
8
+ export declare function ArtikelStatusIcoon({ status }: {
9
+ status: LijstStatus;
10
+ }): import("react").JSX.Element;
11
+ export declare function VoorraadLijstTabel({ rijen, loading, sort, oplopend, onSort, kolommen, selected, onToggleSelect, onToggleAll, onAanpassen, onAfboeken, heeftKoppeling, leeg, }: {
8
12
  heeftKoppeling?: boolean;
9
13
  rijen: LijstRij[];
10
14
  loading: boolean;
@@ -17,5 +21,7 @@ export declare function VoorraadLijstTabel({ rijen, loading, sort, oplopend, onS
17
21
  onToggleAll: (ids: string[]) => void;
18
22
  onAanpassen: (r: LijstRij) => void;
19
23
  onAfboeken: (r: LijstRij) => void;
24
+ /** Lege staat (verschilt per filter/zoekopdracht). */
25
+ leeg?: ReactNode;
20
26
  }): import("react").JSX.Element;
21
27
  //# sourceMappingURL=VoorraadLijstTabel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadLijstTabel.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/VoorraadLijstTabel.tsx"],"names":[],"mappings":"AAeA,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,SAAS,EAEf,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAChF,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAK5E,CAAC;AA+CF,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAsB,GACvB,EAAE;IACD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;CACnC,+BAyFA"}
1
+ {"version":3,"file":"VoorraadLijstTabel.d.ts","sourceRoot":"","sources":["../../../../src/components/voorraad/lijst/VoorraadLijstTabel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,OAAO,CAAC;AAOrD,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAChF,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAK5E,CAAC;AAmBF,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,+BAGrE;AAmBD,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAsB,EACtB,IAAI,GACL,EAAE;IACD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClC,sDAAsD;IACtD,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,+BA6FA"}
@@ -1,8 +1,10 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Link } from "@tanstack/react-router";
3
- import { Button, Checkbox } from "@flowselections/core";
4
- import { ArrowDown, ArrowUp, CheckCircle2, Euro, ExternalLink, Link2Off, Minus, Package, PackageX, SlidersHorizontal, } from "lucide-react";
5
- import floridayLogo from "../../../assets/floriday-logo.png.asset.json";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useNavigate } from "@tanstack/react-router";
3
+ import { CheckCircle2, Euro, Link2, Link2Off, Minus, Package, PackageSearch, PackageX, SlidersHorizontal } from "lucide-react";
4
+ import { DataTable } from "../../fs-ui/DataTable.js";
5
+ import { EmptyState } from "../../fs-ui/EmptyState.js";
6
+ import { HoverTip } from "../../fs-ui/HoverTip.js";
7
+ import { StatusIcon } from "../../fs-ui/StatusIcon.js";
6
8
  import { aantal, geld, toonFloridayLabel, onderMinimum, STATUS_INFO, } from "../../../lib/voorraad-lijst.js";
7
9
  export const OPTIONELE_KOLOMMEN = [
8
10
  { key: "artikelnummer", label: "Artikelnummer" },
@@ -10,30 +12,63 @@ export const OPTIONELE_KOLOMMEN = [
10
12
  { key: "inkomend", label: "Inkomend" },
11
13
  { key: "prijs", label: "Prijs" },
12
14
  ];
13
- function StatusIcoon({ status }) {
15
+ const STATUS_TONE = {
16
+ ok: "klaar",
17
+ verpakking: "fout",
18
+ verwijderd_floriday: "fout",
19
+ niet_gekoppeld: "open",
20
+ prijs: "fout",
21
+ gearchiveerd: "open",
22
+ };
23
+ const STATUS_ICON = {
24
+ ok: _jsx(CheckCircle2, {}),
25
+ verpakking: _jsx(PackageX, {}),
26
+ verwijderd_floriday: _jsx(Link2Off, {}),
27
+ niet_gekoppeld: _jsx(Link2Off, {}),
28
+ prijs: _jsx(Euro, {}),
29
+ gearchiveerd: _jsx(Minus, {}),
30
+ };
31
+ export function ArtikelStatusIcoon({ status }) {
14
32
  const info = STATUS_INFO[status];
15
- const titel = info.actie ? `${info.label} — ${info.actie}` : info.label;
16
- const cls = "h-4 w-4";
17
- const icon = status === "verpakking" ? _jsx(PackageX, { className: `${cls} text-destructive` }) :
18
- status === "verwijderd_floriday" ? _jsx(Link2Off, { className: `${cls} text-destructive` }) :
19
- status === "niet_gekoppeld" ? _jsx(Link2Off, { className: `${cls} text-muted-foreground` }) :
20
- status === "prijs" ? _jsx(Euro, { className: `${cls} text-destructive` }) :
21
- status === "gearchiveerd" ? _jsx(Minus, { className: `${cls} text-muted-foreground` }) :
22
- _jsx(CheckCircle2, { className: `${cls} text-primary` });
23
- return (_jsxs("span", { title: titel, "aria-label": titel, className: "inline-flex items-center gap-1.5", children: [icon, status !== "ok" && _jsx("span", { className: "text-xs text-muted-foreground hidden xl:inline", children: info.label })] }));
33
+ return _jsx(StatusIcon, { tone: STATUS_TONE[status], icon: STATUS_ICON[status], label: info.label, detail: info.actie ?? undefined });
24
34
  }
25
- function Kop({ kolom, label, sort, oplopend, onSort, rechts, }) {
26
- const actief = sort === kolom;
27
- return (_jsx("th", { className: `px-3 py-2 font-medium ${rechts ? "text-right" : "text-left"}`, "aria-sort": actief ? (oplopend ? "ascending" : "descending") : "none", children: _jsxs("button", { type: "button", onClick: () => onSort(kolom), className: `inline-flex items-center gap-1 hover:text-foreground ${actief ? "text-foreground" : ""}`, children: [label, actief && (oplopend ? _jsx(ArrowUp, { className: "h-3 w-3" }) : _jsx(ArrowDown, { className: "h-3 w-3" }))] }) }));
35
+ function Foto({ r }) {
36
+ return r.foto ? (_jsx("img", { src: r.foto, alt: "", loading: "lazy", className: "h-9 w-9 shrink-0 rounded-lg bg-muted object-cover" })) : (_jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }));
28
37
  }
29
- export function VoorraadLijstTabel({ rijen, loading, sort, oplopend, onSort, kolommen, selected, onToggleSelect, onToggleAll, onAanpassen, onAfboeken, heeftKoppeling = false, }) {
30
- const ids = rijen.map((r) => r.id);
31
- const allesGekozen = ids.length > 0 && ids.every((id) => selected.has(id));
38
+ function Koppeling({ r, heeftKoppeling }) {
39
+ if (!toonFloridayLabel(r, heeftKoppeling))
40
+ return null;
41
+ return (_jsx(HoverTip, { label: "Gekoppeld met Floriday", content: "Gekoppeld met Floriday", className: "h-5 w-5 text-muted-foreground [&_svg]:h-3.5 [&_svg]:w-3.5", children: _jsx(Link2, {}) }));
42
+ }
43
+ export function VoorraadLijstTabel({ rijen, loading, sort, oplopend, onSort, kolommen, selected, onToggleSelect, onToggleAll, onAanpassen, onAfboeken, heeftKoppeling = false, leeg, }) {
44
+ const navigate = useNavigate();
32
45
  const k = (s) => kolommen.has(s);
33
- 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: "w-10 px-3 py-2", children: _jsx(Checkbox, { checked: allesGekozen, onCheckedChange: () => onToggleAll(ids), "aria-label": "Alles op deze pagina kiezen" }) }), _jsx(Kop, { kolom: "naam", label: "Artikel", sort: sort, oplopend: oplopend, onSort: onSort }), k("artikelnummer") && _jsx(Kop, { kolom: "artikelnummer", label: "Artikelnummer", sort: sort, oplopend: oplopend, onSort: onSort }), _jsx(Kop, { kolom: "liggend", label: "Liggend", sort: sort, oplopend: oplopend, onSort: onSort, rechts: true }), _jsx(Kop, { kolom: "gereserveerd", label: "Gereserveerd", sort: sort, oplopend: oplopend, onSort: onSort, rechts: true }), _jsx(Kop, { kolom: "vrij", label: "Vrij", sort: sort, oplopend: oplopend, onSort: onSort, rechts: true }), k("minimum") && _jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Minimum" }), k("inkomend") && _jsx(Kop, { kolom: "inkomend", label: "Inkomend", sort: sort, oplopend: oplopend, onSort: onSort, rechts: true }), k("prijs") && _jsx(Kop, { kolom: "prijs", label: "Prijs", sort: sort, oplopend: oplopend, onSort: onSort, rechts: true }), _jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Acties" })] }) }), _jsxs("tbody", { children: [loading && rijen.length === 0 &&
34
- Array.from({ length: 6 }).map((_, i) => (_jsx("tr", { className: "border-b", children: _jsx("td", { colSpan: 12, className: "px-3 py-3", children: _jsx("div", { className: "h-6 animate-pulse rounded bg-muted" }) }) }, `s${i}`))), !loading && rijen.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 12, className: "px-3 py-10 text-center text-muted-foreground", children: "Geen artikelen gevonden." }) })), rijen.map((r) => {
35
- const rood = onderMinimum(r);
36
- const gearchiveerd = !!r.gearchiveerd_at;
37
- return (_jsxs("tr", { className: `border-b hover:bg-muted/30 ${loading ? "opacity-60" : ""}`, children: [_jsx("td", { className: "px-3 py-2", children: _jsx(Checkbox, { checked: selected.has(r.id), onCheckedChange: () => onToggleSelect(r.id), "aria-label": `Kies ${r.naam}` }) }), _jsx("td", { className: "px-3 py-2", children: _jsxs(Link, { to: "/voorraad/$id", params: { id: r.id }, className: "flex items-center gap-3 min-w-0", children: [r.foto ? (_jsx("img", { src: r.foto, alt: "", loading: "lazy", className: "h-9 w-9 shrink-0 rounded object-cover bg-muted" })) : (_jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded bg-muted", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })), _jsxs("span", { className: "min-w-0", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-medium text-foreground truncate", children: [r.naam, toonFloridayLabel(r, heeftKoppeling) && _jsx("img", { src: floridayLogo.url, alt: "Floriday", title: "Gekoppeld aan Floriday", className: "h-3.5 w-3.5 shrink-0" })] }), _jsxs("span", { className: "block text-xs text-muted-foreground truncate", children: [r.artikelnummer || "Geen artikelnummer", r.soort === "SET" ? " · Samenstelling" : r.soort === "GROUP" ? " · Productgroep" : ""] })] })] }) }), k("artikelnummer") && _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: r.artikelnummer ?? "—" }), _jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: aantal(r.liggend) }), _jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: aantal(r.gereserveerd) }), _jsx("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${rood ? "text-destructive" : ""}`, title: rood ? `Onder minimum (${aantal(r.minimum)})` : undefined, children: aantal(r.vrij) }), k("minimum") && _jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: aantal(r.minimum) }), k("inkomend") && _jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: aantal(r.inkomend) }), k("prijs") && _jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: geld(r.prijs) }), _jsx("td", { className: "px-3 py-2", children: _jsx(StatusIcoon, { status: r.status }) }), _jsx("td", { className: "px-3 py-2", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", title: "Voorraad aanpassen (+/\u2212)", "aria-label": "Voorraad aanpassen", disabled: gearchiveerd || r.soort === "GROUP", onClick: () => onAanpassen(r), children: _jsx(SlidersHorizontal, { className: "h-4 w-4" }) }), _jsx(Button, { size: "sm", variant: "ghost", title: r.voorraad_sleutel ? "Afboeken" : "Nog geen voorraad om af te boeken", "aria-label": "Afboeken", disabled: gearchiveerd || !r.voorraad_sleutel || r.liggend <= 0, onClick: () => onAfboeken(r), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Button, { asChild: true, size: "sm", variant: "ghost", title: "Openen", "aria-label": "Openen", children: _jsx(Link, { to: "/voorraad/$id", params: { id: r.id }, children: _jsx(ExternalLink, { className: "h-4 w-4" }) }) })] }) })] }, r.id));
38
- })] })] }));
46
+ const getal = (n, extra = "") => _jsxs("span", { className: "tabular-nums " + extra, children: [aantal(n), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "stuks" })] });
47
+ const kolomDefs = [
48
+ {
49
+ key: "naam", header: "Artikel", width: "260px", sortKey: "naam",
50
+ render: (r) => (_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(Foto, { r: r }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "truncate font-medium text-foreground", children: r.naam }), _jsx(Koppeling, { r: r, heeftKoppeling: heeftKoppeling })] }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [r.artikelnummer || "Geen artikelnummer", r.soort === "SET" ? " · Samenstelling" : r.soort === "GROUP" ? " · Productgroep" : ""] })] })] })),
51
+ },
52
+ k("artikelnummer") && { key: "artikelnummer", header: "Artikelnummer", width: "132px", sortKey: "artikelnummer", render: (r) => _jsx("span", { className: "text-muted-foreground", children: r.artikelnummer ?? "—" }) },
53
+ { key: "liggend", header: "Liggend", width: "112px", sortKey: "liggend", render: (r) => getal(r.liggend) },
54
+ { key: "gereserveerd", header: "Gereserveerd", width: "120px", sortKey: "gereserveerd", render: (r) => getal(r.gereserveerd, "text-muted-foreground") },
55
+ {
56
+ key: "vrij", header: "Vrij", width: "112px", sortKey: "vrij",
57
+ render: (r) => {
58
+ const rood = onderMinimum(r);
59
+ return rood ? (_jsx(HoverTip, { label: `Onder minimum (${aantal(r.minimum)})`, content: `Onder minimum (${aantal(r.minimum)})`, className: "rounded-sm", children: getal(r.vrij, "font-semibold text-destructive") })) : getal(r.vrij, "font-medium");
60
+ },
61
+ },
62
+ k("minimum") && { key: "minimum", header: "Minimum", width: "112px", render: (r) => getal(r.minimum, "text-muted-foreground") },
63
+ k("inkomend") && { key: "inkomend", header: "Inkomend", width: "112px", sortKey: "inkomend", render: (r) => getal(r.inkomend, "text-muted-foreground") },
64
+ k("prijs") && { key: "prijs", header: "Prijs", width: "104px", sortKey: "prijs", render: (r) => _jsx("span", { className: "tabular-nums", children: geld(r.prijs) }) },
65
+ { key: "status", header: "Status", width: "76px", render: (r) => _jsx(ArtikelStatusIcoon, { status: r.status }) },
66
+ ];
67
+ const columns = kolomDefs.filter(Boolean);
68
+ const open = (r) => navigate({ to: "/voorraad/$id", params: { id: r.id } });
69
+ return (_jsx(DataTable, { columns: columns, rows: rijen, getId: (r) => r.id, loading: loading, sort: { key: sort, asc: oplopend }, onSort: (key) => onSort(key), onOpen: open, openLabel: "Artikel openen", selection: { selected, onToggle: onToggleSelect, onToggleAll, label: (r) => r.naam }, empty: leeg ?? _jsx(EmptyState, { icon: _jsx(PackageSearch, { className: "h-6 w-6" }), title: "Geen artikelen gevonden" }), rowActions: (r) => {
70
+ const gearchiveerd = !!r.gearchiveerd_at;
71
+ const knop = "inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
72
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: knop, title: "Voorraad aanpassen (+/\u2212)", "aria-label": "Voorraad aanpassen", disabled: gearchiveerd || r.soort === "GROUP", onClick: () => onAanpassen(r), children: _jsx(SlidersHorizontal, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", className: knop, title: r.voorraad_sleutel ? "Afboeken" : "Nog geen voorraad om af te boeken", "aria-label": "Afboeken", disabled: gearchiveerd || !r.voorraad_sleutel || r.liggend <= 0, onClick: () => onAfboeken(r), children: _jsx(Minus, { className: "h-4 w-4" }) })] }));
73
+ }, mobileCard: (r) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Foto, { r: r }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "truncate font-medium", children: r.naam }), _jsx(Koppeling, { r: r, heeftKoppeling: heeftKoppeling })] }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: r.artikelnummer || "Geen artikelnummer" }), _jsxs("div", { className: "mt-1 flex flex-wrap gap-x-3 text-xs tabular-nums text-muted-foreground", children: [_jsxs("span", { children: ["Liggend ", aantal(r.liggend)] }), _jsxs("span", { className: onderMinimum(r) ? "font-semibold text-destructive" : "text-foreground", children: ["Vrij ", aantal(r.vrij)] }), _jsx("span", { children: geld(r.prijs) })] })] }), _jsx(ArtikelStatusIcoon, { status: r.status })] })) }));
39
74
  }