@flowselections/floriday-voorraad 1.0.152 → 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 (86) hide show
  1. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -1
  2. package/dist-lib/components/fs-ui/DataTable.js +8 -8
  3. package/dist-lib/components/fs-ui/FilterChips.d.ts +1 -1
  4. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -1
  5. package/dist-lib/components/fs-ui/FilterChips.js +3 -3
  6. package/dist-lib/components/fs-ui/FilterPanel.d.ts +19 -0
  7. package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
  8. package/dist-lib/components/fs-ui/FilterPanel.js +10 -0
  9. package/dist-lib/components/fs-ui/FiltersButton.d.ts +6 -0
  10. package/dist-lib/components/fs-ui/FiltersButton.d.ts.map +1 -0
  11. package/dist-lib/components/fs-ui/FiltersButton.js +7 -0
  12. package/dist-lib/components/fs-ui/HeaderButton.d.ts +16 -0
  13. package/dist-lib/components/fs-ui/HeaderButton.d.ts.map +1 -0
  14. package/dist-lib/components/fs-ui/HeaderButton.js +10 -0
  15. package/dist-lib/components/fs-ui/KpiRow.d.ts +2 -2
  16. package/dist-lib/components/fs-ui/KpiRow.js +3 -3
  17. package/dist-lib/components/fs-ui/NoticeBar.d.ts +10 -0
  18. package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
  19. package/dist-lib/components/fs-ui/NoticeBar.js +16 -0
  20. package/dist-lib/components/fs-ui/PageHeader.d.ts +6 -5
  21. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -1
  22. package/dist-lib/components/fs-ui/PageHeader.js +4 -3
  23. package/dist-lib/components/fs-ui/Pagination.d.ts +10 -0
  24. package/dist-lib/components/fs-ui/Pagination.d.ts.map +1 -0
  25. package/dist-lib/components/fs-ui/Pagination.js +10 -0
  26. package/dist-lib/components/fs-ui/PillTabs.d.ts +3 -5
  27. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -1
  28. package/dist-lib/components/fs-ui/PillTabs.js +25 -15
  29. package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
  30. package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
  31. package/dist-lib/components/fs-ui/RowMenu.js +7 -0
  32. package/dist-lib/components/fs-ui/SearchInput.d.ts +1 -4
  33. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -1
  34. package/dist-lib/components/fs-ui/SearchInput.js +23 -19
  35. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +8 -0
  36. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
  37. package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
  38. package/dist-lib/components/fs-ui/Toolbar.d.ts +7 -3
  39. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -1
  40. package/dist-lib/components/fs-ui/Toolbar.js +6 -3
  41. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +6 -2
  42. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -1
  43. package/dist-lib/components/fs-ui/UpdatedAt.js +16 -8
  44. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts +2 -1
  45. package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts.map +1 -1
  46. package/dist-lib/components/voorraad/FloridayConnectionNotice.js +9 -4
  47. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  48. package/dist-lib/components/voorraad/FustenPage.js +90 -26
  49. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +3 -1
  50. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -1
  51. package/dist-lib/components/voorraad/OrderHistoryCard.js +26 -50
  52. package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -1
  53. package/dist-lib/components/voorraad/PublicatiesPage.js +41 -36
  54. package/dist-lib/components/voorraad/Sneltoetsen.d.ts +1 -1
  55. package/dist-lib/components/voorraad/Sneltoetsen.d.ts.map +1 -1
  56. package/dist-lib/components/voorraad/Sneltoetsen.js +12 -13
  57. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts +42 -1
  58. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
  59. package/dist-lib/components/voorraad/StockIntegrityAlert.js +30 -6
  60. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
  61. package/dist-lib/components/voorraad/StockMutationHistoryCard.js +10 -46
  62. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  63. package/dist-lib/components/voorraad/ValidationOverviewTable.js +7 -38
  64. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
  65. package/dist-lib/components/voorraad/ValidationPage.js +3 -3
  66. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  67. package/dist-lib/components/voorraad/VoorraadPage.js +25 -10
  68. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
  69. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +16 -3
  70. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -1
  71. package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +1 -1
  72. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -1
  73. package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +1 -1
  74. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +10 -10
  75. package/dist-lib/integrations/supabase/types.d.ts +26 -0
  76. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  77. package/dist-lib/lib/labels.d.ts +42 -0
  78. package/dist-lib/lib/labels.d.ts.map +1 -0
  79. package/dist-lib/lib/labels.js +152 -0
  80. package/dist-lib/lib/notatie.d.ts +4 -0
  81. package/dist-lib/lib/notatie.d.ts.map +1 -0
  82. package/dist-lib/lib/notatie.js +18 -0
  83. package/dist-lib/lib/voorraad-lijst.d.ts +2 -0
  84. package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -1
  85. package/dist-lib/lib/voorraad-lijst.js +14 -3
  86. package/package.json +1 -1
@@ -1,5 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /** Werkbalk: zoekbalk links, knoppen (Filters, Kolommen) rechts; chips op de regel eronder. */
3
- export function Toolbar({ children, right, chips }) {
4
- return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("div", { className: "min-w-0 flex-1 basis-60", children: children }), right && _jsx("div", { className: "flex flex-wrap items-center gap-2", children: right })] }), chips] }));
2
+ /**
3
+ * Werkbalk exact naar Verkooporders: zoekveld, direct rechts "Filters", dan de
4
+ * actieve chips op dezelfde regel; uiterst rechts alleen "Kolommen".
5
+ */
6
+ export function Toolbar({ children, filters, chips, right }) {
7
+ return (_jsxs("div", { className: "shrink-0 flex flex-wrap items-center gap-2", children: [_jsx("div", { className: "min-w-0 flex-1 sm:flex-none", children: children }), filters, chips, right && _jsx("div", { className: "ml-auto hidden items-center gap-2 md:flex", children: right })] }));
5
8
  }
@@ -1,6 +1,10 @@
1
- /** "Bijgewerkt hh:mm" in 12 px grijs; "Laden…" zolang er nog geen tijd is. */
2
- export declare function UpdatedAt({ at, prefix }: {
1
+ import type { ReactNode } from "react";
2
+ /** "Bijgewerkt hh:mm" met klokicoon (12 px tekst); extra uitleg alleen in de infoballon. */
3
+ export declare function UpdatedAt({ at, prefix, info, stale, }: {
3
4
  at: string | Date | null | undefined;
4
5
  prefix?: string;
6
+ /** Extra informatie, alleen zichtbaar in de infoballon. */
7
+ info?: ReactNode;
8
+ stale?: boolean;
5
9
  }): import("react").JSX.Element | null;
6
10
  //# sourceMappingURL=UpdatedAt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"UpdatedAt.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/UpdatedAt.tsx"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,EAAE,EAAE,EAAE,MAAqB,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,sCASjH"}
1
+ {"version":3,"file":"UpdatedAt.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/UpdatedAt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,MAAqB,EACrB,IAAI,EACJ,KAAK,GACN,EAAE;IACD,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sCAgBA"}
@@ -1,10 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /** "Bijgewerkt hh:mm" in 12 px grijs; "Laden…" zolang er nog geen tijd is. */
3
- export function UpdatedAt({ at, prefix = "Bijgewerkt" }) {
4
- if (!at)
5
- return _jsx("span", { className: "tabular-nums", children: "Laden\u2026" });
6
- const d = typeof at === "string" ? new Date(at) : at;
7
- if (Number.isNaN(d.getTime()))
8
- return null;
9
- return (_jsxs("span", { className: "tabular-nums", children: [prefix, " ", d.toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" })] }));
2
+ import { Clock } from "lucide-react";
3
+ import { HoverTip } from "./HoverTip.js";
4
+ /** "Bijgewerkt hh:mm" met klokicoon (12 px tekst); extra uitleg alleen in de infoballon. */
5
+ export function UpdatedAt({ at, prefix = "Bijgewerkt", info, stale, }) {
6
+ let tekst = "Laden…";
7
+ if (at) {
8
+ const d = typeof at === "string" ? new Date(at) : at;
9
+ if (Number.isNaN(d.getTime()))
10
+ return null;
11
+ const t = d.toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" });
12
+ tekst = stale ? `Niet bijgewerkt sinds ${t}` : `${prefix} ${t}`;
13
+ }
14
+ const inhoud = (_jsxs("span", { className: "inline-flex items-center gap-1 tabular-nums " + (stale ? "text-warning" : ""), children: [_jsx(Clock, { className: "h-3.5 w-3.5", "aria-hidden": true }), tekst] }));
15
+ if (!info)
16
+ return inhoud;
17
+ return _jsx(HoverTip, { label: tekst, content: _jsx("div", { className: "max-w-xs text-xs", children: info }), align: "start", children: inhoud });
10
18
  }
@@ -1,9 +1,10 @@
1
1
  import type { ConnectionStatus } from "../../lib/stock-scope.js";
2
2
  /**
3
3
  * Werkpagina's tonen geen uitnodiging om een koppeling in te stellen (dat hoort bij Instellingen).
4
- * Alleen als een bestaande koppeling niet geladen kan worden: één rustige melding.
4
+ * Alleen als een bestaande koppeling niet geladen kan worden: één rustige meldingsregel.
5
5
  * De technische reden gaat naar de console.
6
6
  */
7
+ export declare function floridayConnectionNotice(status: ConnectionStatus, error: string | null): string | null;
7
8
  export declare function FloridayConnectionNotice({ status, error, }: {
8
9
  status: ConnectionStatus;
9
10
  environment?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"FloridayConnectionNotice.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayConnectionNotice.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,KAAK,GACN,EAAE;IACD,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,sCAQA"}
1
+ {"version":3,"file":"FloridayConnectionNotice.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayConnectionNotice.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAItG;AAED,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,KAAK,GACN,EAAE;IACD,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,sCAMA"}
@@ -1,14 +1,19 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { InlineAlert } from "../fs-ui/InlineAlert.js";
3
2
  /**
4
3
  * Werkpagina's tonen geen uitnodiging om een koppeling in te stellen (dat hoort bij Instellingen).
5
- * Alleen als een bestaande koppeling niet geladen kan worden: één rustige melding.
4
+ * Alleen als een bestaande koppeling niet geladen kan worden: één rustige meldingsregel.
6
5
  * De technische reden gaat naar de console.
7
6
  */
8
- export function FloridayConnectionNotice({ status, error, }) {
7
+ export function floridayConnectionNotice(status, error) {
9
8
  if (status !== "fout")
10
9
  return null;
11
10
  if (error)
12
11
  console.warn("Floriday-koppeling laden mislukt:", error);
13
- return (_jsx(InlineAlert, { tone: "let_op", children: "De koppeling met Floriday kon niet geladen worden. Je eigen artikelen werken gewoon; probeer het later opnieuw." }));
12
+ return "De koppeling met Floriday kon niet geladen worden. Je eigen artikelen werken gewoon; probeer het later opnieuw.";
13
+ }
14
+ export function FloridayConnectionNotice({ status, error, }) {
15
+ const t = floridayConnectionNotice(status, error);
16
+ if (!t)
17
+ return null;
18
+ return (_jsx("div", { role: "status", className: "shrink-0 rounded-lg border border-border bg-warning-soft px-3 py-2 text-sm text-warning-soft-foreground", children: t }));
14
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAqLA,wBAAgB,UAAU,gCAoPzB"}
1
+ {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAmMA,wBAAgB,UAAU,gCAiNzB"}
@@ -1,14 +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";
10
30
  import { PageHeader } from "../fs-ui/PageHeader.js";
11
- import { InlineAlert } from "../fs-ui/InlineAlert.js";
12
31
  /** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
13
32
  function DimensionCell({ fust, floricode, customPackages, onSave, }) {
14
33
  const [editing, setEditing] = useState(false);
@@ -58,20 +77,20 @@ function MutationHistory({ fust }) {
58
77
  }
59
78
  };
60
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 &&
61
- 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"
62
- ? "Verkoop"
63
- : m.reason === "sale_cancelled"
64
- ? "Order geannuleerd"
65
- : m.reason === "manual_sale"
66
- ? "Handmatige verkoop"
67
- : m.reason === "startmoment"
68
- ? "Startmoment / beginstand"
69
- : "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)))] }))] }));
70
81
  }
71
82
  export function FustenPage() {
72
83
  const { activeId } = useFloridayConnection();
73
84
  const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
74
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);
75
94
  const [floricode, setFloricode] = useState(null);
76
95
  const [floricodeError, setFloricodeError] = useState(null);
77
96
  useEffect(() => {
@@ -86,14 +105,29 @@ export function FustenPage() {
86
105
  const [busyId, setBusyId] = useState(null);
87
106
  const [amounts, setAmounts] = useState({});
88
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;
89
110
  const filtered = useMemo(() => {
90
111
  const q = search.trim().toLowerCase();
91
- if (!q)
92
- return items;
93
- return items.filter((f) => f.name.toLowerCase().includes(q) ||
94
- String(f.vbn_code ?? "").includes(q) ||
95
- f.fust_key.toLowerCase().includes(q));
96
- }, [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]);
97
131
  const totals = useMemo(() => ({
98
132
  types: items.length,
99
133
  stock: items.reduce((sum, f) => sum + f.stock, 0),
@@ -116,11 +150,41 @@ export function FustenPage() {
116
150
  setBusyId(null);
117
151
  }
118
152
  };
119
- return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { title: "Fusten", meta: _jsx("span", { children: "Fusttypen komen vanzelf uit de beladingen van je artikelen." }), actions: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin motion-reduce:animate-none" : ""}` }), "Bijwerken"] }) }), error && (_jsx(InlineAlert, { tone: "fout", children: "Fusten laden is niet gelukt. Probeer het opnieuw." })), floricodeError && (_jsx(InlineAlert, { tone: "let_op", children: "De standaardmaten van fusten konden niet geladen worden. Alleen zelf 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
120
- ? "text-rose-600"
121
- : ""}`, 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) => {
122
- const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
123
- if (v !== f.minimum_stock)
124
- void patch(f.id, { minimum_stock: v });
125
- } }) }), _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 })] })] }))] }) })] }));
126
190
  }
@@ -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":"AAgCA,wBAAgB,eAAe,gCA8I9B"}
1
+ {"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,eAAe,gCA0F9B"}
@@ -1,27 +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
8
  import { PageHeader } from "../fs-ui/PageHeader.js";
9
- const STATUS_LABEL = {
10
- running: "Bezig",
11
- succeeded: "Gepubliceerd",
12
- succeeded_with_warnings: "Aandachtspunten",
13
- failed: "Actie nodig",
14
- };
15
- function StatusChip({ status }) {
16
- const tone = status === "succeeded"
17
- ? "bg-emerald-100 text-emerald-800"
18
- : status === "failed"
19
- ? "bg-destructive/10 text-destructive"
20
- : status === "running"
21
- ? "bg-muted text-muted-foreground"
22
- : "bg-amber-100 text-amber-800";
23
- return (_jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${tone}`, children: STATUS_LABEL[status] ?? status }));
24
- }
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, {}) };
25
19
  export function PublicatiesPage() {
26
20
  const { jobs, loading, reload } = usePublishJobs(50);
27
21
  const [stepsByJob, setStepsByJob] = useState({});
@@ -48,22 +42,33 @@ export function PublicatiesPage() {
48
42
  cancelled = true;
49
43
  };
50
44
  }, [jobs]);
51
- return (_jsxs("main", { className: "flex flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { crumb: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " Voorraad"] }), title: "Naar Floriday gestuurd", meta: _jsx("span", { children: "Artikelen die je naar Floriday hebt gestuurd, met wat er eventueel nog moet gebeuren." }), actions: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Vernieuwen"] }) }), 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) => {
52
- const target = job.product_id ?? job.trade_item_id;
53
- // Oude "Actie nodig" sluit vanzelf zodra een latere publicatie van
54
- // hetzelfde artikel (op id) wel gelukt is.
55
- const repaired = job.status === "failed" &&
56
- jobs.some((o) => o.id !== job.id &&
57
- (o.product_id ?? o.trade_item_id) === target &&
58
- target != null &&
59
- new Date(o.created_at) > new Date(job.created_at) &&
60
- (o.status === "succeeded" || o.status === "succeeded_with_warnings"));
61
- const steps = stepsByJob[job.id] ?? [];
62
- const failed = repaired ? [] : steps.filter((s) => s.status === "failed");
63
- 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
64
- .filter((s) => s.status !== "skipped")
65
- .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"
66
- ? "text-destructive"
67
- : "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));
68
- }) }))] }));
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); } })] }));
69
74
  }
@@ -7,7 +7,7 @@ export declare function Sneltoetsen({ onNieuw, nieuwLabel }: {
7
7
  onNieuw?: () => void;
8
8
  nieuwLabel?: string;
9
9
  }): import("react").JSX.Element;
10
- /** Kleine sneltoets-hint in een knop; alleen vanaf tablet. */
10
+ /** Kleine sneltoets-hint in een primaire knop, exact naar Verkooporders. */
11
11
  export declare function Kbd({ children }: {
12
12
  children: string;
13
13
  }): import("react").JSX.Element;
@@ -1 +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,+BAwCnH;AAED,8DAA8D;AAC9D,wBAAgB,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAErD"}
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"}