@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,12 +1,33 @@
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 { useEffect, useMemo, useState } from "react";
3
- import { Boxes, Minus, Plus, RefreshCw, Search } from "lucide-react";
4
- import { Button, Card, Input, Badge, toast } from "@flowselections/core";
3
+ import { AlertTriangle, Boxes, CalendarClock, Layers, Minus, Plus, RefreshCw, TrendingDown, PackageX, CircleSlash } from "lucide-react";
4
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast } from "@flowselections/core";
5
+ import { fustReden } from "../../lib/labels.js";
6
+ import { datum, getal } from "../../lib/notatie.js";
7
+ import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
8
+ import { NoticeBar } from "../fs-ui/NoticeBar.js";
9
+ import { KpiRow } from "../fs-ui/KpiRow.js";
10
+ import { KpiTile } from "../fs-ui/KpiTile.js";
11
+ import { PillTabs } from "../fs-ui/PillTabs.js";
12
+ import { Toolbar } from "../fs-ui/Toolbar.js";
13
+ import { SearchInput } from "../fs-ui/SearchInput.js";
14
+ import { FiltersButton } from "../fs-ui/FiltersButton.js";
15
+ import { FilterChips } from "../fs-ui/FilterChips.js";
16
+ import { FilterPanel, FilterGroup } from "../fs-ui/FilterPanel.js";
17
+ import { DataTable } from "../fs-ui/DataTable.js";
18
+ import { EmptyState } from "../fs-ui/EmptyState.js";
19
+ import { Pagination } from "../fs-ui/Pagination.js";
20
+ import { HoverTip } from "../fs-ui/HoverTip.js";
21
+ import { RowMenu } from "../fs-ui/RowMenu.js";
22
+ import { HeaderButton } from "../fs-ui/HeaderButton.js";
23
+ import { Sneltoetsen } from "./Sneltoetsen.js";
24
+ const BRON_LABEL = { alles: "Alle bronnen", vbn: "Standaard fust", leverancier: "Leverancier" };
5
25
  import { FustStartmomentCard, FustInstellingenCell } from "./FustAdministratie.js";
6
26
  import { fustNaam, fustTekorten, saldoBijhouden } from "../../lib/fust-administratie.js";
7
27
  import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
8
28
  import { useFusten } from "../../hooks/useFusten.js";
9
29
  import { fetchFloricodeFustMap, fetchFloridayCustomPackageMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
30
+ import { PageHeader } from "../fs-ui/PageHeader.js";
10
31
  /** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
11
32
  function DimensionCell({ fust, floricode, customPackages, onSave, }) {
12
33
  const [editing, setEditing] = useState(false);
@@ -22,7 +43,7 @@ function DimensionCell({ fust, floricode, customPackages, onSave, }) {
22
43
  return Number.isFinite(n) && n > 0 ? n : null;
23
44
  };
24
45
  if (!editing) {
25
- return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Floricode" : resolved.source === "floriday" ? "Floriday eigen fust" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
46
+ return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Standaardmaat" : resolved.source === "floriday" ? "Floriday eigen fust" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
26
47
  setDraft({
27
48
  length_mm: fust.length_mm?.toString() ?? "",
28
49
  width_mm: fust.width_mm?.toString() ?? "",
@@ -56,20 +77,20 @@ function MutationHistory({ fust }) {
56
77
  }
57
78
  };
58
79
  return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggle, className: "text-xs text-primary hover:underline", children: open ? "Historie verbergen" : "Historie" }), open && (_jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/30 p-2 text-xs", children: [rows === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), rows !== null && rows.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen mutaties." })), rows !== null &&
59
- rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(m.created_at).toLocaleString("nl-NL") }), _jsxs("span", { className: "flex-1 truncate", children: [m.reason === "sale"
60
- ? "Verkoop"
61
- : m.reason === "sale_cancelled"
62
- ? "Order geannuleerd"
63
- : m.reason === "manual_sale"
64
- ? "Handmatige verkoop"
65
- : m.reason === "startmoment"
66
- ? "Startmoment / beginstand"
67
- : "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
80
+ rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: datum(m.created_at, true) }), _jsxs("span", { className: "flex-1 truncate", children: [fustReden(m.reason), m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-destructive" : "text-primary"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
68
81
  }
69
82
  export function FustenPage() {
70
83
  const { activeId } = useFloridayConnection();
71
84
  const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
72
85
  const [search, setSearch] = useState("");
86
+ const [tab, setTab] = useState("alles");
87
+ const [bron, setBron] = useState("alles");
88
+ const [filterOpen, setFilterOpen] = useState(false);
89
+ const [startOpen, setStartOpen] = useState(false);
90
+ const [detailId, setDetailId] = useState(null);
91
+ const [pagina, setPagina] = useState(1);
92
+ const [grootte, setGrootte] = useState(25);
93
+ const [bijgewerkt, setBijgewerkt] = useState(null);
73
94
  const [floricode, setFloricode] = useState(null);
74
95
  const [floricodeError, setFloricodeError] = useState(null);
75
96
  useEffect(() => {
@@ -84,14 +105,29 @@ export function FustenPage() {
84
105
  const [busyId, setBusyId] = useState(null);
85
106
  const [amounts, setAmounts] = useState({});
86
107
  const negative = useMemo(() => fustTekorten(items), [items]);
108
+ const onderMin = (f) => f.minimum_stock > 0 && f.stock < f.minimum_stock;
109
+ const tekort = (f) => saldoBijhouden(f) && f.stock < 0;
87
110
  const filtered = useMemo(() => {
88
111
  const q = search.trim().toLowerCase();
89
- if (!q)
90
- return items;
91
- return items.filter((f) => f.name.toLowerCase().includes(q) ||
92
- String(f.vbn_code ?? "").includes(q) ||
93
- f.fust_key.toLowerCase().includes(q));
94
- }, [items, search]);
112
+ return items.filter((f) => {
113
+ if (tab === "onder_minimum" && !onderMin(f))
114
+ return false;
115
+ if (tab === "tekort" && !tekort(f))
116
+ return false;
117
+ if (tab === "niet_in_gebruik" && f.in_use)
118
+ return false;
119
+ if (bron === "leverancier" && f.source !== "SUPPLIER")
120
+ return false;
121
+ if (bron === "vbn" && f.source === "SUPPLIER")
122
+ return false;
123
+ if (!q)
124
+ return true;
125
+ return fustNaam(f).toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || String(f.vbn_code ?? "").includes(q) || f.fust_key.toLowerCase().includes(q);
126
+ });
127
+ }, [items, search, tab, bron]); // eslint-disable-line react-hooks/exhaustive-deps
128
+ useEffect(() => setPagina(1), [search, tab, bron, grootte]);
129
+ useEffect(() => { if (!loading)
130
+ setBijgewerkt(new Date()); }, [loading, items]);
95
131
  const totals = useMemo(() => ({
96
132
  types: items.length,
97
133
  stock: items.reduce((sum, f) => sum + f.stock, 0),
@@ -114,11 +150,41 @@ export function FustenPage() {
114
150
  setBusyId(null);
115
151
  }
116
152
  };
117
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is sinds het startmoment meer uitgeleverd dan ingeboekt. Alleen fusten waarvan je het saldo bijhoudt staan hier. Controleer ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [fustNaam(n), ": tekort van ", Math.abs(n.stock).toLocaleString("nl-NL"), " fusten"] }, n.id))) })] })), _jsx(FustStartmomentCard, { items: items, onSaved: () => void sync() }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: fustNaam(f) }), f.eigen_naam?.trim() && _jsx("div", { className: "text-xs text-muted-foreground", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx(FustInstellingenCell, { fust: f, onSaved: () => void sync() }), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, floricode: floricode, customPackages: customPackages, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 && saldoBijhouden(f) ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
118
- ? "text-rose-600"
119
- : ""}`, children: f.stock })) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
120
- const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
121
- if (v !== f.minimum_stock)
122
- void patch(f.id, { minimum_stock: v });
123
- } }) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, -1), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { className: "h-8 w-28 text-center", type: "number", placeholder: "Aantal", value: amounts[f.id] ?? "", onChange: (e) => setAmounts((a) => ({ ...a, [f.id]: e.target.value })) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] }) })] }, f.id))) })] }) })) })] }));
153
+ const tabs = [
154
+ { value: "alles", label: "Alles", icon: _jsx(Layers, {}), count: items.length },
155
+ { value: "onder_minimum", label: "Onder minimum", icon: _jsx(TrendingDown, {}), count: totals.belowMin },
156
+ { value: "tekort", label: "Tekort", icon: _jsx(AlertTriangle, {}), count: negative.length },
157
+ { value: "niet_in_gebruik", label: "Niet in gebruik", icon: _jsx(CircleSlash, {}), count: items.filter((f) => !f.in_use).length },
158
+ ];
159
+ const TAB_ICON = { alles: _jsx(Layers, {}), onder_minimum: _jsx(TrendingDown, {}), tekort: _jsx(AlertTriangle, {}), niet_in_gebruik: _jsx(CircleSlash, {}) };
160
+ const notices = [
161
+ ...(error ? ["Fusten laden is niet gelukt. Probeer het opnieuw."] : []),
162
+ ...(floricodeError ? ["De standaardmaten van fusten konden niet geladen worden. Alleen zelf ingevoerde maten worden getoond."] : []),
163
+ ...(negative.length > 0 ? [
164
+ _jsxs("span", { role: "button", tabIndex: 0, className: "hover:underline", onClick: (e) => { e.stopPropagation(); setTab("tekort"); }, children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort sinds het startmoment"] }, "tekort"),
165
+ ] : []),
166
+ ];
167
+ const detail = items.find((f) => f.id === detailId) ?? null;
168
+ const zichtbaar = filtered.slice((pagina - 1) * grootte, pagina * grootte);
169
+ const chips = [
170
+ ...(tab !== "alles" ? [{ key: "tab", label: tabs.find((t) => t.value === tab).label, icon: TAB_ICON[tab], onRemove: () => setTab("alles") }] : []),
171
+ ...(bron !== "alles" ? [{ key: "bron", label: BRON_LABEL[bron], onRemove: () => setBron("alles") }] : []),
172
+ ];
173
+ 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, {}), _jsx(PageHeader, { title: "Fusten", updated: _jsx(UpdatedAt, { at: bijgewerkt, info: "Fusttypen komen vanzelf uit de beladingen van je artikelen." }), actions: _jsxs(_Fragment, { children: [_jsx(HeaderButton, { icon: _jsx(CalendarClock, {}), label: "Startmoment", onClick: () => setStartOpen(true) }), _jsx(HeaderButton, { icon: _jsx(RefreshCw, { className: syncing ? "animate-spin motion-reduce:animate-none" : "" }), label: "Bijwerken", onClick: () => void sync(), disabled: syncing })] }) }), _jsx(NoticeBar, { items: notices }), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Fusttypen", icon: _jsx(Boxes, {}), loading: loading, value: getal(totals.types), sub: "Uit beladingen", onClick: () => setTab("alles"), active: false }), _jsx(KpiTile, { title: "Totale fustvoorraad", icon: _jsx(Layers, {}), loading: loading, value: getal(totals.stock), sub: "Fust" }), _jsx(KpiTile, { title: "Onder minimum", icon: _jsx(TrendingDown, {}), loading: loading, tone: totals.belowMin > 0 ? "warning" : "default", value: getal(totals.belowMin), sub: "Fusttypen", onClick: () => setTab("onder_minimum"), active: tab === "onder_minimum" }), _jsx(KpiTile, { title: "Tekort", icon: _jsx(PackageX, {}), loading: loading, tone: negative.length > 0 ? "warning" : "default", value: getal(negative.length), sub: "Sinds het startmoment", onClick: () => setTab("tekort"), active: tab === "tekort" })] }), _jsx(PillTabs, { tabs: tabs, value: tab, onChange: setTab, label: "Fusten filteren" }), _jsx(Toolbar, { filters: _jsx(FiltersButton, { count: bron !== "alles" ? 1 : 0, onClick: () => setFilterOpen(true) }), chips: _jsx(FilterChips, { chips: chips, onClearAll: () => { setTab("alles"); setBron("alles"); } }), children: _jsx(SearchInput, { value: search, onChange: setSearch, placeholder: "Zoek fust op naam of code" }) }), _jsx(DataTable, { rows: zichtbaar, getId: (f) => f.id, loading: loading, onOpen: (f) => setDetailId(f.id), openLabel: "Fust openen", empty: _jsx(EmptyState, { icon: _jsx(Boxes, { className: "h-6 w-6" }), title: items.length === 0 ? "Nog geen fusttypen" : "Niets gevonden", text: items.length === 0 ? "Voeg een belading toe aan een artikel; het fust verschijnt dan vanzelf." : "Geen fusten in deze weergave." }), rowActions: (f) => (_jsx(RowMenu, { label: fustNaam(f), items: [
174
+ { label: "Details en instellingen", onClick: () => setDetailId(f.id) },
175
+ ] })), columns: [
176
+ { key: "fust", header: "Fust", width: "240px", render: (f) => (_jsxs("div", { className: "min-w-0", children: [_jsx(HoverTip, { label: fustNaam(f), content: _jsxs("div", { className: "text-xs", children: [fustNaam(f), f.eigen_naam?.trim() ? ` (${f.name})` : "", f.vbn_code ? ` · code ${f.vbn_code}` : ""] }), align: "start", className: "block max-w-full truncate text-left", children: _jsx("span", { className: "block truncate font-medium", children: fustNaam(f) }) }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [f.source === "SUPPLIER" ? "Leverancier" : "Standaard fust", f.in_use ? "" : " · niet meer in gebruik"] })] })) },
177
+ { key: "afmeting", header: "Afmeting", width: "160px", render: (f) => {
178
+ const r = resolveFustDimensions(f, floricode, customPackages);
179
+ const l = formatFustDimensions(r);
180
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate " + (l ? "tabular-nums" : "text-muted-foreground"), children: l ?? "Onbekend" }), r.source !== "onbekend" && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: r.source === "floricode" ? "Standaardmaat" : r.source === "floriday" ? "Eigen fust uit Floriday" : "Handmatig" })] }));
181
+ } },
182
+ { key: "gebruikt", header: "Gebruikt door", width: "128px", render: (f) => _jsxs("span", { className: "tabular-nums", children: [getal(f.used_by_count), " ", _jsx("span", { className: "text-xs text-muted-foreground", children: "artikelen" })] }) },
183
+ { key: "voorraad", header: "Voorraad", width: "152px", render: (f) => (_jsxs("div", { className: "min-w-0", children: [tekort(f)
184
+ ? _jsxs("div", { className: "truncate font-semibold tabular-nums text-destructive", children: ["Tekort ", getal(Math.abs(f.stock)), " fust"] })
185
+ : _jsxs("div", { className: "truncate font-semibold tabular-nums " + (onderMin(f) ? "text-destructive" : ""), children: [getal(f.stock), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "fust" })] }), !saldoBijhouden(f) && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: "Saldo niet bijgehouden" })] })) },
186
+ { key: "minimum", header: "Minimum", width: "120px", render: (f) => (_jsx(Input, { type: "number", "aria-label": `Minimum ${fustNaam(f)}`, className: "h-8 w-24", defaultValue: f.minimum_stock, onClick: (e) => e.stopPropagation(), onBlur: (e) => { const v = Math.max(0, Math.trunc(Number(e.target.value) || 0)); if (v !== f.minimum_stock)
187
+ void patch(f.id, { minimum_stock: v }); } })) },
188
+ { key: "aanpassen", header: "Aanpassen", width: "184px", render: (f) => (_jsxs("div", { className: "flex items-center gap-1", onClick: (e) => e.stopPropagation(), children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Afboeken", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, -1), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { className: "h-8 w-20 text-center", type: "number", placeholder: "Aantal", "aria-label": `Aantal ${fustNaam(f)}`, value: amounts[f.id] ?? "", onChange: (e) => setAmounts((a) => ({ ...a, [f.id]: e.target.value })) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Bijboeken", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] })) },
189
+ ] }), _jsx(Pagination, { page: pagina, pageSize: grootte, totalCount: filtered.length, onPage: setPagina, onPageSize: setGrootte }), _jsx(FilterPanel, { open: filterOpen, onOpenChange: setFilterOpen, onClear: () => setBron("alles"), children: _jsx(FilterGroup, { title: "Bron", value: bron, onChange: setBron, options: Object.keys(BRON_LABEL).map((b) => ({ value: b, label: BRON_LABEL[b] })) }) }), _jsx(Dialog, { open: startOpen, onOpenChange: setStartOpen, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Startmoment" }) }), _jsx(FustStartmomentCard, { items: items, onSaved: () => void sync() })] }) }), _jsx(Dialog, { open: !!detail, onOpenChange: (o) => !o && setDetailId(null), children: _jsxs(DialogContent, { className: "left-auto right-0 top-0 h-[100dvh] max-w-md translate-x-0 translate-y-0 overflow-auto rounded-none sm:rounded-none", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: detail ? fustNaam(detail) : "" }) }), detail && (_jsxs("div", { className: "space-y-5 text-sm", children: [_jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Naam en saldo" }), _jsx(FustInstellingenCell, { fust: detail, onSaved: () => void sync() })] }), _jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Afmeting" }), _jsx(DimensionCell, { fust: detail, floricode: floricode, customPackages: customPackages, onSave: (v) => patch(detail.id, v) })] }), _jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Boekingen" }), _jsx(MutationHistory, { fust: detail })] })] }))] }) })] }));
124
190
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,gBAAgB,gCA4Y/B"}
1
+ {"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,gBAAgB,gCAoY/B"}
@@ -20,6 +20,7 @@ import { saveOwnPackings } from "../../lib/product-packings.js";
20
20
  import { assertArticleCode } from "../../lib/article-code.js";
21
21
  import { EMPTY_SIMPLE_PACKING, engineOordeelRegel, missingPackingFields, simplePackingToRow, } from "../../lib/simple-packing.js";
22
22
  import { resolveEngineCarrier } from "../../lib/engine-carrier.js";
23
+ import { PageHeader } from "../fs-ui/PageHeader.js";
23
24
  const STEPS = ["Naam en code", "Voorraad", "Prijs", "Belading"];
24
25
  export function NieuwArtikelPage() {
25
26
  const carriers = useLoadCarriers();
@@ -186,5 +187,5 @@ export function NieuwArtikelPage() {
186
187
  ? "rounded-md border p-3 text-sm"
187
188
  : "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsxs("p", { className: "font-medium", children: ["Controle karindeling: ", oordeel.tekst] }), oordeel.regels.length > 0 && (_jsx("ul", { className: "list-disc pl-5", children: oordeel.regels.map((r) => (_jsx("li", { children: r }, r))) }))] })), hasFloriday ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "De voorraad is opgeslagen als eigen voorraad. Die gaat niet naar Floriday." })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [hasFloriday && (_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] })), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
188
189
  }
189
- return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: "Koppeling wordt gecontroleerd\u2026" })), canSaveStock && !hasFloriday && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), step === 3 && (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zonder volledige belading kan de karindeling dit artikel niet plannen. Alle vijf velden zijn verplicht." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-fust", children: "Fust (VBN-code)" }), _jsxs("select", { id: "na-fust", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value), children: [_jsx("option", { value: "", children: "Kies een fust\u2026" }), fusts.map((f) => (_jsxs("option", { value: String(f.vbn), children: [f.vbn, " \u2013 ", f.name] }, f.vbn)))] }), _jsx(Input, { "aria-label": "Andere VBN-fustcode", placeholder: "Of typ een VBN-fustcode, bijv. 412", inputMode: "numeric", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-ppc", children: "Planten per fust" }), _jsx(Input, { id: "na-ppc", inputMode: "numeric", value: packing.piecesPerPackage, onChange: (e) => setPack("piecesPerPackage", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-cpl", children: "Fusten per laag" }), _jsx(Input, { id: "na-cpl", inputMode: "numeric", value: packing.packagesPerLayer, onChange: (e) => setPack("packagesPerLayer", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lpc", children: "Lagen per kar" }), _jsx(Input, { id: "na-lpc", inputMode: "numeric", value: packing.layersPerLoadCarrier, onChange: (e) => setPack("layersPerLoadCarrier", e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lc", children: "Ladingdrager" }), _jsxs("select", { id: "na-lc", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.loadCarrier, onChange: (e) => setPack("loadCarrier", e.target.value), children: [_jsx("option", { value: "", children: "Kies een ladingdrager\u2026" }), carriers.items.map((c) => (_jsx("option", { value: c.code, children: c.naam }, c.code)))] }), carriers.error && _jsx("p", { className: "text-xs text-destructive", children: carriers.error }), engineCarrier.melding && _jsx("p", { className: "text-xs text-destructive", children: engineCarrier.melding })] }), missingPacking.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Nog invullen: ", missingPacking.join(", "), "."] }))] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim() || !packingReady, onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), hasFloriday && (_jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" }))] }));
190
+ return (_jsxs("main", { className: "flex max-w-3xl flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { crumb: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " Voorraad"] }), title: "Artikel toevoegen", meta: _jsxs("span", { children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] }) }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "space-y-4 rounded-xl p-4 shadow-sm sm:p-5", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: "Koppeling wordt gecontroleerd\u2026" })), canSaveStock && !hasFloriday && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), step === 3 && (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zonder volledige belading kan de karindeling dit artikel niet plannen. Alle vijf velden zijn verplicht." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-fust", children: "Fust (VBN-code)" }), _jsxs("select", { id: "na-fust", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value), children: [_jsx("option", { value: "", children: "Kies een fust\u2026" }), fusts.map((f) => (_jsxs("option", { value: String(f.vbn), children: [f.vbn, " \u2013 ", f.name] }, f.vbn)))] }), _jsx(Input, { "aria-label": "Andere VBN-fustcode", placeholder: "Of typ een VBN-fustcode, bijv. 412", inputMode: "numeric", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-ppc", children: "Planten per fust" }), _jsx(Input, { id: "na-ppc", inputMode: "numeric", value: packing.piecesPerPackage, onChange: (e) => setPack("piecesPerPackage", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-cpl", children: "Fusten per laag" }), _jsx(Input, { id: "na-cpl", inputMode: "numeric", value: packing.packagesPerLayer, onChange: (e) => setPack("packagesPerLayer", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lpc", children: "Lagen per kar" }), _jsx(Input, { id: "na-lpc", inputMode: "numeric", value: packing.layersPerLoadCarrier, onChange: (e) => setPack("layersPerLoadCarrier", e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lc", children: "Ladingdrager" }), _jsxs("select", { id: "na-lc", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.loadCarrier, onChange: (e) => setPack("loadCarrier", e.target.value), children: [_jsx("option", { value: "", children: "Kies een ladingdrager\u2026" }), carriers.items.map((c) => (_jsx("option", { value: c.code, children: c.naam }, c.code)))] }), carriers.error && _jsx("p", { className: "text-xs text-destructive", children: carriers.error }), engineCarrier.melding && _jsx("p", { className: "text-xs text-destructive", children: engineCarrier.melding })] }), missingPacking.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Nog invullen: ", missingPacking.join(", "), "."] }))] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim() || !packingReady, onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), hasFloriday && (_jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" }))] }));
190
191
  }
@@ -4,7 +4,9 @@ interface Props {
4
4
  productId?: string | null;
5
5
  /** Artikelen met dezelfde voorraadsleutel, uit voorraad_artikel_detail. */
6
6
  productIds?: string[];
7
+ /** Stuks per fust van de hoofdverpakking (voor omrekenen naar stuks). */
8
+ stuksPerFust?: number | null;
7
9
  }
8
- export declare function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds }: Props): import("react").JSX.Element;
10
+ export declare function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds, stuksPerFust }: Props): import("react").JSX.Element;
9
11
  export {};
10
12
  //# sourceMappingURL=OrderHistoryCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OrderHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/OrderHistoryCard.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAgDD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,KAAK,+BAsKvG"}
1
+ {"version":3,"file":"OrderHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/OrderHistoryCard.tsx"],"names":[],"mappings":"AAmBA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgBD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,KAAK,+BA6JrH"}
@@ -1,49 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { Card, CardContent, Badge } from "@flowselections/core";
4
- import { Loader2, ClipboardList, ExternalLink } from "lucide-react";
5
- import { supabase } from "../../integrations/supabase/client.js";
6
- const STATUS_TONE = {
7
- "nog te picken": "bg-amber-100 text-amber-800 border-amber-200",
8
- "klaar voor verzending": "bg-blue-100 text-blue-800 border-blue-200",
9
- verzonden: "bg-emerald-100 text-emerald-800 border-emerald-200",
10
- geannuleerd: "bg-rose-100 text-rose-800 border-rose-200",
11
- concept: "bg-slate-100 text-slate-700 border-slate-200",
12
- };
13
- // Legacy waarden mappen naar de nieuwe fases zolang de backfill/trigger
14
- // nog niet elke rij heeft omgezet.
15
- function normalizeStatus(raw) {
16
- const s = (raw ?? "").trim().toLowerCase();
17
- if (!s)
18
- return "Nog te picken";
19
- if (["verzonden", "geleverd", "shipped", "delivered"].includes(s))
20
- return "Verzonden";
21
- if (["nieuw", "open", "new", "pending"].includes(s))
22
- return "Nog te picken";
23
- if (["ready", "klaar", "klaar voor verzending"].includes(s))
24
- return "Klaar voor verzending";
25
- if (["geannuleerd", "cancelled", "canceled"].includes(s))
26
- return "geannuleerd";
27
- if (s === "concept")
28
- return "concept";
29
- if (s === "nog te picken")
30
- return "Nog te picken";
31
- return raw ?? "Nog te picken";
32
- }
33
- function formatDate(v) {
34
- if (!v)
35
- return "—";
36
- try {
37
- const d = new Date(v);
38
- if (Number.isNaN(d.getTime()))
39
- return v;
40
- return d.toLocaleDateString("nl-NL", { day: "2-digit", month: "short", year: "numeric" });
41
- }
42
- catch {
43
- return v;
44
- }
3
+ import { Card, CardContent } from "@flowselections/core";
4
+ import { Loader2, ClipboardList, ExternalLink, Circle, CircleDot, CheckCircle2, XCircle, CircleDashed } from "lucide-react";
5
+ import { orderStatus, EENHEID } from "../../lib/labels.js";
6
+ import { datum, getal } from "../../lib/notatie.js";
7
+ import { StatusIcon } from "../fs-ui/StatusIcon.js";
8
+ const TONE = { open: "open", bezig: "bezig", klaar: "klaar", fout: "fout", concept: "open" };
9
+ const ICON = { open: _jsx(Circle, {}), bezig: _jsx(CircleDot, {}), klaar: _jsx(CheckCircle2, {}), fout: _jsx(XCircle, {}), concept: _jsx(CircleDashed, {}) };
10
+ /** Aantal altijd in stuks; fust als tweede regel wanneer de verpakking bekend is. */
11
+ function stuksEnFust(qty, unit, stuksPerFust) {
12
+ const e = EENHEID[(unit ?? "stuks").trim().toLowerCase()] ?? "stuks";
13
+ const spf = stuksPerFust && stuksPerFust > 0 ? stuksPerFust : null;
14
+ if (e === "fust")
15
+ return { stuks: spf ? qty * spf : null, fust: qty };
16
+ return { stuks: qty, fust: spf ? Math.round((qty / spf) * 100) / 100 : null };
45
17
  }
46
- export function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds }) {
18
+ import { supabase } from "../../integrations/supabase/client.js";
19
+ export function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds, stuksPerFust }) {
47
20
  const [rows, setRows] = useState(null);
48
21
  const [error, setError] = useState(null);
49
22
  useEffect(() => {
@@ -86,7 +59,7 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
86
59
  }
87
60
  const { data, error } = await supabase
88
61
  .from("order_items")
89
- .select("order_id,quantity,unit,orders!inner(id,order_number,customer_name,order_date,created_at,status,order_source,deleted_at)")
62
+ .select("order_id,quantity,unit,plants_per_crate,orders!inner(id,order_number,customer_name,order_date,created_at,status,order_source,deleted_at)")
90
63
  .or(orFilters.join(","))
91
64
  .is("orders.deleted_at", null)
92
65
  .order("created_at", { ascending: false, foreignTable: "orders" })
@@ -102,6 +75,7 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
102
75
  customer_name: r.orders?.customer_name ?? null,
103
76
  quantity: r.quantity ?? 0,
104
77
  unit: r.unit ?? null,
78
+ plants_per_crate: r.plants_per_crate == null ? null : Number(r.plants_per_crate),
105
79
  order_date: r.orders?.order_date ?? null,
106
80
  created_at: r.orders?.created_at ?? "",
107
81
  status: r.orders?.status ?? null,
@@ -111,8 +85,10 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
111
85
  setRows(mapped);
112
86
  }
113
87
  catch (e) {
114
- if (!cancelled)
88
+ if (!cancelled) {
89
+ console.warn("Bestelgeschiedenis laden mislukt:", e);
115
90
  setError(e.message);
91
+ }
116
92
  }
117
93
  };
118
94
  load();
@@ -127,11 +103,11 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
127
103
  supabase.removeChannel(channel);
128
104
  };
129
105
  }, [tradeItemKey, productName, productId, bekendeIds?.join("|")]); // eslint-disable-line react-hooks/exhaustive-deps
130
- return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon bestelgeschiedenis niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Ordernummer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Klant" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Fusten" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Besteldatum" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Status" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
131
- const label = normalizeStatus(r.status);
132
- const tone = STATUS_TONE[label.toLowerCase()] ??
133
- "bg-slate-100 text-slate-700 border-slate-200";
106
+ return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsx("div", { className: "text-sm text-destructive", children: "Bestelgeschiedenis laden lukt niet. Probeer opnieuw." })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border border-border", children: _jsxs("table", { className: "w-full min-w-[640px] table-fixed text-sm [&_thead_th]:h-10 [&_thead_th]:px-2 [&_thead_th]:text-left [&_thead_th]:align-middle [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: "140px" } }), _jsx("col", {}), _jsx("col", { style: { width: "132px" } }), _jsx("col", { style: { width: "120px" } }), _jsx("col", { style: { width: "184px" } }), _jsx("col", { style: { width: "44px" } })] }), _jsx("thead", { children: _jsxs("tr", { className: "border-b border-border", children: [_jsx("th", { children: "Ordernummer" }), _jsx("th", { children: "Klant" }), _jsx("th", { children: "Aantal" }), _jsx("th", { children: "Besteldatum" }), _jsx("th", { children: "Status" }), _jsx("th", { children: _jsx("span", { className: "sr-only", children: "Openen" }) })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
107
+ const st = orderStatus(r.status);
108
+ const q = stuksEnFust(Number(r.quantity ?? 0), r.unit, r.plants_per_crate ?? stuksPerFust);
134
109
  const href = `/floriday-verkoop/verkooporders/${r.order_id}`;
135
- return (_jsxs("tr", { className: "border-t hover:bg-muted/40 cursor-pointer", onClick: () => { window.location.href = href; }, children: [_jsx("td", { className: "px-3 py-2 font-medium", children: _jsx("a", { href: href, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: r.order_number }) }), _jsx("td", { className: "px-3 py-2", children: r.customer_name ?? "—" }), _jsxs("td", { className: "px-3 py-2 text-right tabular-nums", children: [r.quantity, r.unit ? _jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: r.unit }) : null] }), _jsx("td", { className: "px-3 py-2", children: formatDate(r.order_date ?? r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${tone}`, children: label }) }), _jsx("td", { className: "px-3 py-2 text-right text-muted-foreground", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5 inline" }) })] }, `${r.order_id}-${i}`));
110
+ const cel = "truncate px-2 py-0 align-middle";
111
+ return (_jsxs("tr", { className: "h-[60px] border-b border-border last:border-0 hover:bg-muted/40", children: [_jsx("td", { className: cel, children: _jsx("a", { href: href, className: "font-medium text-primary hover:underline", children: r.order_number }) }), _jsx("td", { className: cel, children: r.customer_name ?? "—" }), _jsxs("td", { className: cel + " tabular-nums", children: [_jsx("div", { className: "truncate", children: q.stuks == null ? "—" : `${getal(q.stuks)} stuks` }), q.fust != null && _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [getal(q.fust), " fust"] })] }), _jsx("td", { className: cel + " tabular-nums", children: datum(r.order_date ?? r.created_at) }), _jsx("td", { className: cel, children: _jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [_jsx(StatusIcon, { tone: TONE[st.tone], icon: ICON[st.tone], label: st.label }), _jsx("span", { className: "truncate", children: st.label })] }) }), _jsx("td", { className: cel + " text-muted-foreground", children: _jsx("a", { href: href, "aria-label": "Order openen", className: "inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-muted", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5" }) }) })] }, `${r.order_id}-${i}`));
136
112
  }) })] }) }))] })] }));
137
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,eAAe,gCAwJ9B"}
1
+ {"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,eAAe,gCA0F9B"}
@@ -1,26 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link } from "@tanstack/react-router";
3
- import { Card, Button } from "@flowselections/core";
4
- import { ArrowLeft, Check, AlertTriangle, Loader2, RefreshCw } from "lucide-react";
3
+ import { Button } from "@flowselections/core";
4
+ import { ArrowLeft, CheckCircle2, CircleDot, XCircle, CircleDashed, RefreshCw, Send } from "lucide-react";
5
5
  import { useEffect, useState } from "react";
6
6
  import { usePublishJobs } from "../../hooks/usePublishJob.js";
7
7
  import { supabase } from "../../integrations/supabase/client.js";
8
- const STATUS_LABEL = {
9
- running: "Bezig",
10
- succeeded: "Gepubliceerd",
11
- succeeded_with_warnings: "Aandachtspunten",
12
- failed: "Actie nodig",
13
- };
14
- function StatusChip({ status }) {
15
- const tone = status === "succeeded"
16
- ? "bg-emerald-100 text-emerald-800"
17
- : status === "failed"
18
- ? "bg-destructive/10 text-destructive"
19
- : status === "running"
20
- ? "bg-muted text-muted-foreground"
21
- : "bg-amber-100 text-amber-800";
22
- return (_jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${tone}`, children: STATUS_LABEL[status] ?? status }));
23
- }
8
+ import { PageHeader } from "../fs-ui/PageHeader.js";
9
+ import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
10
+ import { HeaderButton } from "../fs-ui/HeaderButton.js";
11
+ import { DataTable } from "../fs-ui/DataTable.js";
12
+ import { EmptyState } from "../fs-ui/EmptyState.js";
13
+ import { Pagination } from "../fs-ui/Pagination.js";
14
+ import { StatusIcon } from "../fs-ui/StatusIcon.js";
15
+ import { publicatieStatus } from "../../lib/labels.js";
16
+ import { datum } from "../../lib/notatie.js";
17
+ const TONE = { open: "open", bezig: "bezig", klaar: "klaar", fout: "fout", concept: "open" };
18
+ const ICON = { open: _jsx(CircleDashed, {}), bezig: _jsx(CircleDot, {}), klaar: _jsx(CheckCircle2, {}), fout: _jsx(XCircle, {}), concept: _jsx(CircleDashed, {}) };
24
19
  export function PublicatiesPage() {
25
20
  const { jobs, loading, reload } = usePublishJobs(50);
26
21
  const [stepsByJob, setStepsByJob] = useState({});
@@ -47,22 +42,33 @@ export function PublicatiesPage() {
47
42
  cancelled = true;
48
43
  };
49
44
  }, [jobs]);
50
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-1 h-3 w-3" }), "Vernieuwen"] })] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Publicaties" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle producten die je hebt gepubliceerd, met wat er eventueel nog moet gebeuren." })] }), loading ? (_jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Laden\u2026"] })) : jobs.length === 0 ? (_jsx(Card, { className: "p-6 text-sm text-muted-foreground", children: "Er zijn nog geen publicaties." })) : (_jsx("div", { className: "space-y-3", children: jobs.map((job) => {
51
- const target = job.product_id ?? job.trade_item_id;
52
- // Oude "Actie nodig" sluit vanzelf zodra een latere publicatie van
53
- // hetzelfde artikel (op id) wel gelukt is.
54
- const repaired = job.status === "failed" &&
55
- jobs.some((o) => o.id !== job.id &&
56
- (o.product_id ?? o.trade_item_id) === target &&
57
- target != null &&
58
- new Date(o.created_at) > new Date(job.created_at) &&
59
- (o.status === "succeeded" || o.status === "succeeded_with_warnings"));
60
- const steps = stepsByJob[job.id] ?? [];
61
- const failed = repaired ? [] : steps.filter((s) => s.status === "failed");
62
- return (_jsxs(Card, { className: "p-4 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium", children: job.title || "Product" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [new Date(job.created_at).toLocaleString("nl-NL"), job.environment !== "live" ? " · testomgeving" : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [repaired ? (_jsx("span", { className: "rounded-full px-2 py-0.5 text-xs font-medium bg-emerald-100 text-emerald-800", children: "Hersteld" })) : (_jsx(StatusChip, { status: job.status })), target && failed.length > 0 && (_jsx(Button, { asChild: true, size: "sm", variant: "outline", children: _jsx(Link, { to: "/voorraad/$id", params: { id: String(target) }, search: { hervat: job.id }, children: "Afronden" }) }))] })] }), steps.length > 0 && (_jsx("ul", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs", children: steps
63
- .filter((s) => s.status !== "skipped")
64
- .map((s) => (_jsxs("li", { className: "flex items-center gap-1", children: [s.status === "done" ? (_jsx(Check, { className: "h-3 w-3 text-emerald-600" })) : s.status === "failed" ? (_jsx(AlertTriangle, { className: "h-3 w-3 text-destructive" })) : (_jsx(Loader2, { className: "h-3 w-3 text-muted-foreground" })), _jsx("span", { className: s.status === "failed"
65
- ? "text-destructive"
66
- : "text-muted-foreground", children: s.label })] }, s.id))) })), failed.map((s) => (_jsxs("p", { className: "text-xs text-destructive", children: [s.label, ": ", s.error_message] }, `err-${s.id}`)))] }, job.id));
67
- }) }))] }));
45
+ const [pagina, setPagina] = useState(1);
46
+ const [grootte, setGrootte] = useState(25);
47
+ const [bijgewerkt, setBijgewerkt] = useState(null);
48
+ useEffect(() => { if (!loading)
49
+ setBijgewerkt(new Date()); }, [loading, jobs]);
50
+ const rijen = jobs.map((job) => {
51
+ const target = (job.product_id ?? job.trade_item_id);
52
+ // Oude "Actie nodig" sluit vanzelf zodra een latere publicatie van hetzelfde artikel (op id) wel gelukt is.
53
+ const repaired = job.status === "failed" &&
54
+ jobs.some((o) => o.id !== job.id && (o.product_id ?? o.trade_item_id) === target && target != null &&
55
+ new Date(o.created_at) > new Date(job.created_at) && (o.status === "succeeded" || o.status === "succeeded_with_warnings"));
56
+ const steps = (stepsByJob[job.id] ?? []).filter((s) => s.status !== "skipped");
57
+ const failed = repaired ? [] : steps.filter((s) => s.status === "failed");
58
+ for (const f of failed)
59
+ if (f.error_message)
60
+ console.warn("Publicatiestap mislukt:", f.label, f.error_message);
61
+ return { job, target, repaired, steps, failed };
62
+ });
63
+ const zichtbaar = rijen.slice((pagina - 1) * grootte, pagina * grootte);
64
+ 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"] }), title: "Naar Floriday gestuurd", updated: _jsx(UpdatedAt, { at: bijgewerkt, info: "Artikelen die je naar Floriday hebt gestuurd, met wat er eventueel nog moet gebeuren." }), actions: _jsx(HeaderButton, { icon: _jsx(RefreshCw, {}), label: "Vernieuwen", onClick: () => void reload() }) }), _jsx(DataTable, { rows: zichtbaar, getId: (r) => r.job.id, loading: loading, keyboard: false, empty: _jsx(EmptyState, { icon: _jsx(Send, { className: "h-6 w-6" }), title: "Nog niets verstuurd", text: "Er zijn nog geen artikelen naar Floriday gestuurd." }), columns: [
65
+ { key: "artikel", header: "Artikel", width: "260px", render: (r) => (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: r.job.title || "Artikel" }), r.job.environment !== "live" && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: "Testomgeving" })] })) },
66
+ { key: "moment", header: "Verstuurd", width: "152px", render: (r) => _jsx("span", { className: "tabular-nums", children: datum(r.job.created_at, true) }) },
67
+ { key: "stappen", header: "Stappen", width: "260px", render: (r) => (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "truncate tabular-nums", children: [r.steps.filter((s) => s.status === "done").length, " van ", r.steps.length, " klaar"] }), r.failed.length > 0 && _jsx("div", { className: "truncate text-xs text-destructive", children: r.failed.map((s) => s.label).join(", ") })] })) },
68
+ { key: "status", header: "Status", width: "176px", render: (r) => {
69
+ const st = publicatieStatus(r.repaired ? "repaired" : r.job.status);
70
+ return _jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [_jsx(StatusIcon, { tone: TONE[st.tone], icon: ICON[st.tone], label: st.label }), _jsx("span", { className: "truncate", children: st.label })] });
71
+ } },
72
+ { key: "actie", header: _jsx("span", { className: "sr-only", children: "Actie" }), width: "120px", render: (r) => r.target && r.failed.length > 0 ? (_jsx(Button, { asChild: true, size: "sm", variant: "outline", children: _jsx(Link, { to: "/voorraad/$id", params: { id: String(r.target) }, search: { hervat: r.job.id }, children: "Afronden" }) })) : null },
73
+ ] }), _jsx(Pagination, { page: pagina, pageSize: grootte, totalCount: rijen.length, onPage: setPagina, onPageSize: (n) => { setGrootte(n); setPagina(1); } })] }));
68
74
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Sneltoetsen zoals in Verkooporders: N = nieuw (alleen als er een maak-actie is),
3
+ * ? = overzicht. "/" (zoeken), J/K/Enter (rijen) en Esc zitten in SearchInput en DataTable.
4
+ * Nooit tijdens typen in een invoerveld en niet terwijl er een dialoog open is.
5
+ */
6
+ export declare function Sneltoetsen({ onNieuw, nieuwLabel }: {
7
+ onNieuw?: () => void;
8
+ nieuwLabel?: string;
9
+ }): import("react").JSX.Element;
10
+ /** Kleine sneltoets-hint in een primaire knop, exact naar Verkooporders. */
11
+ export declare function Kbd({ children }: {
12
+ children: string;
13
+ }): import("react").JSX.Element;
14
+ //# sourceMappingURL=Sneltoetsen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sneltoetsen.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/Sneltoetsen.tsx"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,UAA4B,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,+BAsBnH;AAED,4EAA4E;AAC5E,wBAAgB,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAErD"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { isTypen } from "../fs-ui/SearchInput.js";
4
+ import { ShortcutHelp } from "../fs-ui/ShortcutHelp.js";
5
+ /**
6
+ * Sneltoetsen zoals in Verkooporders: N = nieuw (alleen als er een maak-actie is),
7
+ * ? = overzicht. "/" (zoeken), J/K/Enter (rijen) en Esc zitten in SearchInput en DataTable.
8
+ * Nooit tijdens typen in een invoerveld en niet terwijl er een dialoog open is.
9
+ */
10
+ export function Sneltoetsen({ onNieuw, nieuwLabel = "Nieuw artikel" }) {
11
+ const [open, setOpen] = useState(false);
12
+ useEffect(() => {
13
+ const h = (e) => {
14
+ if (e.metaKey || e.ctrlKey || e.altKey || isTypen(e.target))
15
+ return;
16
+ if (e.key === "?") {
17
+ e.preventDefault();
18
+ setOpen(true);
19
+ return;
20
+ }
21
+ if (document.querySelector("[role=dialog]"))
22
+ return;
23
+ if ((e.key === "n" || e.key === "N") && onNieuw) {
24
+ e.preventDefault();
25
+ onNieuw();
26
+ }
27
+ };
28
+ window.addEventListener("keydown", h);
29
+ return () => window.removeEventListener("keydown", h);
30
+ }, [onNieuw]);
31
+ const shortcuts = [
32
+ [["/"], "Zoeken"],
33
+ ...(onNieuw ? [[["N"], nieuwLabel]] : []),
34
+ [["J", "K"], "Volgende / vorige rij"],
35
+ [["Enter"], "Geselecteerde rij openen"],
36
+ [["Esc"], "Zoeken wissen / sluiten"],
37
+ [["?"], "Dit overzicht"],
38
+ ];
39
+ return _jsx(ShortcutHelp, { open: open, onOpenChange: setOpen, shortcuts: shortcuts });
40
+ }
41
+ /** Kleine sneltoets-hint in een primaire knop, exact naar Verkooporders. */
42
+ export function Kbd({ children }) {
43
+ return _jsx("kbd", { "aria-hidden": true, className: "ml-2 hidden rounded border border-primary-foreground/30 bg-primary-foreground/15 px-1.5 font-mono text-[10px] leading-4 text-primary-foreground sm:inline-block", children: children });
44
+ }