@flowselections/mailbox-orders 1.0.55 → 1.0.58

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 (72) hide show
  1. package/dist-lib/components/EmailPreview.d.ts.map +1 -1
  2. package/dist-lib/components/EmailPreview.js +2 -1
  3. package/dist-lib/components/MailboxAuthBanner.d.ts +3 -3
  4. package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -1
  5. package/dist-lib/components/MailboxAuthBanner.js +18 -6
  6. package/dist-lib/components/MailboxPageFrame.d.ts +11 -5
  7. package/dist-lib/components/MailboxPageFrame.d.ts.map +1 -1
  8. package/dist-lib/components/MailboxPageFrame.js +24 -16
  9. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
  10. package/dist-lib/components/MailboxProcessedPage.js +30 -21
  11. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  12. package/dist-lib/components/MailboxProposalsPage.js +38 -30
  13. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
  14. package/dist-lib/components/MailboxSkippedPage.js +35 -23
  15. package/dist-lib/components/MailboxStatusLine.d.ts +7 -3
  16. package/dist-lib/components/MailboxStatusLine.d.ts.map +1 -1
  17. package/dist-lib/components/MailboxStatusLine.js +42 -19
  18. package/dist-lib/components/TablePagination.d.ts +2 -2
  19. package/dist-lib/components/TablePagination.d.ts.map +1 -1
  20. package/dist-lib/components/TablePagination.js +8 -12
  21. package/dist-lib/components/fs-ui/DataTable.d.ts +12 -6
  22. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -1
  23. package/dist-lib/components/fs-ui/DataTable.js +26 -19
  24. package/dist-lib/components/fs-ui/FilterChips.d.ts +1 -1
  25. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -1
  26. package/dist-lib/components/fs-ui/FilterChips.js +3 -3
  27. package/dist-lib/components/fs-ui/FilterPanel.d.ts +32 -0
  28. package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
  29. package/dist-lib/components/fs-ui/FilterPanel.js +20 -0
  30. package/dist-lib/components/fs-ui/InlineAlert.d.ts +2 -2
  31. package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -1
  32. package/dist-lib/components/fs-ui/InlineAlert.js +4 -12
  33. package/dist-lib/components/fs-ui/KpiRow.js +1 -1
  34. package/dist-lib/components/fs-ui/NoticeBar.d.ts +18 -0
  35. package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
  36. package/dist-lib/components/fs-ui/NoticeBar.js +24 -0
  37. package/dist-lib/components/fs-ui/PageHeader.d.ts +1 -1
  38. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -1
  39. package/dist-lib/components/fs-ui/PageHeader.js +2 -2
  40. package/dist-lib/components/fs-ui/PillTabs.d.ts +3 -2
  41. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -1
  42. package/dist-lib/components/fs-ui/PillTabs.js +26 -11
  43. package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
  44. package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
  45. package/dist-lib/components/fs-ui/RowMenu.js +10 -0
  46. package/dist-lib/components/fs-ui/SearchInput.d.ts +2 -2
  47. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -1
  48. package/dist-lib/components/fs-ui/SearchInput.js +11 -6
  49. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +3 -3
  50. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -1
  51. package/dist-lib/components/fs-ui/ShortcutHelp.js +2 -2
  52. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +2 -2
  53. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -1
  54. package/dist-lib/components/fs-ui/TableSkeleton.js +2 -2
  55. package/dist-lib/components/fs-ui/Toolbar.d.ts +8 -3
  56. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -1
  57. package/dist-lib/components/fs-ui/Toolbar.js +8 -3
  58. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +5 -4
  59. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -1
  60. package/dist-lib/components/fs-ui/UpdatedAt.js +12 -4
  61. package/dist-lib/integrations/supabase/types.d.ts +76 -0
  62. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  63. package/dist-lib/lib/format-date.d.ts +5 -0
  64. package/dist-lib/lib/format-date.d.ts.map +1 -0
  65. package/dist-lib/lib/format-date.js +30 -0
  66. package/dist-lib/lib/labels.d.ts +15 -0
  67. package/dist-lib/lib/labels.d.ts.map +1 -0
  68. package/dist-lib/lib/labels.js +57 -0
  69. package/dist-lib/lib/mailbox-list.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/mailbox-list.functions.js +0 -2
  71. package/dist-lib/lib/mailbox.functions.js +3 -3
  72. package/package.json +1 -1
@@ -2,28 +2,29 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
- import { CalendarClock, Filter, MailX, Search } from "lucide-react";
6
- import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast } from "@flowselections/core";
5
+ import { CalendarClock, MailX, Search } from "lucide-react";
6
+ import { Button, toast } from "@flowselections/core";
7
+ import { fmtDayTime } from "../lib/format-date.js";
8
+ import { label, ANALYSIS_STATE, FILTER_OUTCOME } from "../lib/labels.js";
7
9
  import { listSkippedMessages, analyzeMessageNow } from "../lib/mailbox.functions.js";
8
10
  import { TablePagination } from "./TablePagination.js";
9
11
  import { usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
10
12
  import { MailboxPageFrame } from "./MailboxPageFrame.js";
11
- import { DataTable } from "./fs-ui/DataTable.js";
13
+ import { DataTable, CellText } from "./fs-ui/DataTable.js";
14
+ import { FilterPanel, FilterOptions, PERIOD_OPTIONS } from "./fs-ui/FilterPanel.js";
12
15
  import { StatusIcon } from "./fs-ui/StatusIcon.js";
13
16
  import { EmptyState } from "./fs-ui/EmptyState.js";
14
- import { InlineAlert } from "./fs-ui/InlineAlert.js";
15
17
  import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
16
18
  import { Toolbar } from "./fs-ui/Toolbar.js";
17
19
  import { SearchInput } from "./fs-ui/SearchInput.js";
18
20
  import { FilterChips } from "./fs-ui/FilterChips.js";
19
21
  import { useListHotkeys } from "./fs-ui/useListHotkeys.js";
22
+ import { RowMenu } from "./fs-ui/RowMenu.js";
20
23
  const statusOf = (m) => m.analysis_state === "wacht"
21
- ? { label: "Wacht op uitlezen", tone: "amber" }
22
- : m.filter_outcome === "te_oud"
23
- ? { label: "Te oud", tone: "grey" }
24
- : m.filter_outcome === "geen_bestelling"
25
- ? { label: "Geen bestelling", tone: "grey" }
26
- : { label: "Overgeslagen", tone: "grey" };
24
+ ? { label: label(ANALYSIS_STATE, "wacht"), tone: "amber" }
25
+ : m.filter_outcome === "te_oud" || m.filter_outcome === "geen_bestelling"
26
+ ? { label: label(FILTER_OUTCOME, m.filter_outcome), tone: "grey" }
27
+ : { label: "Overgeslagen", tone: "grey" };
27
28
  /** Reden in gewone woorden (codes uit de verwerking worden vertaald). */
28
29
  const reasonText = (m) => {
29
30
  const raw = String(m.analysis_note || m.filter_reason || "");
@@ -33,6 +34,11 @@ const reasonText = (m) => {
33
34
  return "Uit een map die niet gevolgd wordt";
34
35
  if (/map onbekend/i.test(raw))
35
36
  return "Map niet bekend";
37
+ // Nooit ruwe codes (snake_case, HOOFDLETTERCODES, [haakjes]) in beeld.
38
+ if (/[a-z]+_[a-z_]+|\b[A-Z]{2,}_[A-Z_]+\b|\[[^\]]*\]/.test(raw)) {
39
+ console.warn("[mailbox] reden", raw);
40
+ return "";
41
+ }
36
42
  return raw;
37
43
  };
38
44
  const PERIOD_LABEL = { today: "Vandaag", "7d": "Laatste 7 dagen", "30d": "Laatste 30 dagen" };
@@ -55,6 +61,7 @@ export function MailboxSkippedPage() {
55
61
  const [sortOrder, setSortOrder] = usePersistedState("mailbox-skipped:sort", "date-desc");
56
62
  const [cursor, setCursor] = useState(null);
57
63
  const [helpOpen, setHelpOpen] = useState(false);
64
+ const [filtersOpen, setFiltersOpen] = useState(false);
58
65
  const searchRef = useRef(null);
59
66
  const filtered = useMemo(() => {
60
67
  const q = normalizeText(search).trim();
@@ -102,38 +109,43 @@ export function MailboxSkippedPage() {
102
109
  }
103
110
  };
104
111
  useListHotkeys({
105
- disabled: helpOpen,
112
+ disabled: helpOpen || filtersOpen,
106
113
  onSearch: () => searchRef.current?.focus(),
107
114
  onNext: () => setCursor((c) => (paged.length ? Math.min((c ?? -1) + 1, paged.length - 1) : null)),
108
115
  onPrev: () => setCursor((c) => (paged.length ? Math.max((c ?? 1) - 1, 0) : null)),
109
116
  onEscape: hasActiveFilters ? clearFilters : undefined,
110
117
  onHelp: () => setHelpOpen(true),
111
118
  });
112
- const actionBtn = (m) => (_jsx(Button, { size: "sm", variant: "outline", className: "h-8", disabled: busyId === m.id, onClick: () => run(m.id), children: busyId === m.id ? "Bezig…" : m.analysis_state === "wacht" ? "Nu uitlezen" : "Toch uitlezen" }));
119
+ const rowAction = (m) => (_jsx(RowMenu, { label: `Acties voor ${m.from_name || m.from_email || "mail"}`, items: [{ label: busyId === m.id ? "Bezig…" : m.analysis_state === "wacht" ? "Nu uitlezen" : "Toch uitlezen", disabled: busyId === m.id, onSelect: () => void run(m.id) }] }));
113
120
  const columns = [
114
121
  {
115
- key: "afzender", header: "Afzender", width: "minmax(160px,1.2fr)",
116
- cell: (m) => (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: m.from_name || m.from_email || "Onbekend" }), m.from_name && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: m.from_email })] })),
122
+ key: "afzender", header: "Afzender", width: 200,
123
+ cell: (m) => _jsx(CellText, { primary: m.from_name || m.from_email || "Onbekend", secondary: m.from_name ? m.from_email : undefined, strong: true }),
117
124
  },
118
125
  {
119
- key: "bericht", header: "Bericht", width: "minmax(240px,2.2fr)",
120
- cell: (m) => (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: m.subject || "(geen onderwerp)" }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: m.preview || "" })] })),
126
+ key: "bericht", header: "Bericht", width: 300,
127
+ cell: (m) => _jsx(CellText, { primary: m.subject || "(geen onderwerp)", secondary: m.preview || "" }),
121
128
  },
122
129
  {
123
- key: "datum", header: "Ontvangen", width: "120px", sortable: true,
124
- cell: (m) => _jsx("span", { className: "tabular-nums text-muted-foreground", children: m.received_at ? new Date(m.received_at).toLocaleDateString("nl-NL") : "–" }),
130
+ key: "datum", header: "Ontvangen", width: 132, sortable: true,
131
+ cell: (m) => _jsx("span", { className: "block truncate tabular-nums", children: fmtDayTime(m.received_at) }),
125
132
  },
126
133
  {
127
- key: "reden", header: "Reden", width: "minmax(180px,1.4fr)",
134
+ key: "reden", header: "Reden", width: 200,
128
135
  cell: (m) => {
129
136
  const st = statusOf(m);
130
- return (_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(StatusIcon, { tone: st.tone === "amber" ? "busy" : "open", label: st.label, detail: reasonText(m) || undefined }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm", children: st.label }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: reasonText(m) })] })] }));
137
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(StatusIcon, { tone: st.tone === "amber" ? "busy" : "open", label: st.label, detail: reasonText(m) || undefined }), _jsx(CellText, { primary: st.label, secondary: reasonText(m) })] }));
131
138
  },
132
139
  },
133
- { key: "actie", header: "", width: "130px", cell: (m) => actionBtn(m) },
134
140
  ];
135
141
  const chips = period !== "all" ? [{ key: "period", label: PERIOD_LABEL[period] ?? period, icon: _jsx(CalendarClock, {}), onRemove: () => setPeriod("all") }] : [];
136
- return (_jsxs(MailboxPageFrame, { tab: "overgeslagen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, children: [waiting > 0 && (_jsx(InlineAlert, { tone: "warning", title: `${waiting} mail(s) wachten op uitlezen.`, children: "De limiet voor vandaag is bereikt of automatisch uitlezen staat uit. Je kunt een mail ook zelf laten uitlezen." })), _jsx(Toolbar, { chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), right: _jsxs(Select, { value: period, onValueChange: (v) => setPeriod(v), children: [_jsxs(SelectTrigger, { className: "h-9 w-[150px]", "aria-label": "Periode", children: [_jsx(Filter, { className: "mr-1 h-4 w-4" }), _jsx(SelectValue, {})] }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle periodes" }), _jsx(SelectItem, { value: "today", children: "Vandaag" }), _jsx(SelectItem, { value: "7d", children: "Laatste 7 dagen" }), _jsx(SelectItem, { value: "30d", children: "Laatste 30 dagen" })] })] }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op afzender, onderwerp of reden" }) }), isLoading && !data ? (_jsx(TableSkeleton, { columns: ["1.2fr", "2.2fr", "120px", "1.4fr", "130px"] })) : error && !data ? (_jsx(InlineAlert, { tone: "error", title: "De lijst kon niet worden geladen.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: "Opnieuw proberen" }), children: "Probeer het opnieuw." })) : filtered.length === 0 ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: hasActiveFilters
142
+ const notices = waiting > 0 ? [{
143
+ key: "waiting",
144
+ tone: "warning",
145
+ title: `${waiting} ${waiting === 1 ? "mail wacht" : "mails wachten"} op uitlezen.`,
146
+ details: "De limiet voor vandaag is bereikt of automatisch uitlezen staat uit. Je kunt een mail ook zelf laten uitlezen.",
147
+ }] : [];
148
+ return (_jsxs(MailboxPageFrame, { tab: "overgeslagen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, notices: notices, children: [_jsx(Toolbar, { onOpenFilters: () => setFiltersOpen(true), filterCount: chips.length, chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op afzender, onderwerp of reden" }) }), _jsx(FilterPanel, { open: filtersOpen, onOpenChange: setFiltersOpen, onClear: clearFilters, children: _jsx(FilterOptions, { label: "Periode", value: period, options: PERIOD_OPTIONS, onChange: (v) => setPeriod(v) }) }), isLoading && !data ? (_jsx(TableSkeleton, { columns: [200, 300, 132, 200] })) : error && !data ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "De lijst kon niet worden geladen", text: "Probeer het opnieuw.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: "Opnieuw proberen" }) }) })) : filtered.length === 0 ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: hasActiveFilters
137
149
  ? _jsx(EmptyState, { icon: _jsx(Search, { className: "h-6 w-6" }), title: "Niets gevonden", text: "Er zijn geen overgeslagen mails die bij je zoekopdracht passen.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: clearFilters, children: "Filters wissen" }) })
138
- : _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "Geen overgeslagen mails", text: "Mails die geen bestelling lijken, verschijnen hier met de reden." }) })) : (_jsx(DataTable, { rows: paged, columns: columns, rowKey: (m) => m.id, activeIndex: cursor, sort: { key: "datum", dir: sortOrder === "date-asc" ? "asc" : "desc" }, onSortChange: () => setSortOrder(sortOrder === "date-asc" ? "date-desc" : "date-asc"), mobileCard: (m) => (_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("div", { className: "truncate font-semibold", children: m.from_name || m.from_email || "Onbekend" }), _jsx("div", { className: "truncate text-muted-foreground", children: m.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [m.received_at ? new Date(m.received_at).toLocaleDateString("nl-NL") : "–", " \u00B7 ", statusOf(m).label] }), _jsx("div", { className: "pt-1", children: actionBtn(m) })] })), footer: _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) }))] }));
150
+ : _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "Geen overgeslagen mails", text: "Mails die geen bestelling lijken, verschijnen hier met de reden." }) })) : (_jsx(DataTable, { rows: paged, columns: columns, rowKey: (m) => m.id, activeIndex: cursor, rowAction: rowAction, sort: { key: "datum", dir: sortOrder === "date-asc" ? "asc" : "desc" }, onSortChange: () => setSortOrder(sortOrder === "date-asc" ? "date-desc" : "date-asc"), mobileCard: (m) => (_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("div", { className: "truncate font-semibold", children: m.from_name || m.from_email || "Onbekend" }), _jsx("div", { className: "truncate text-muted-foreground", children: m.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [fmtDayTime(m.received_at), " \u00B7 ", statusOf(m).label] })] })), footer: _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) }))] }));
139
151
  }
@@ -1,7 +1,11 @@
1
+ import type { ReactNode } from "react";
2
+ import { type MailboxStatus } from "../lib/mailbox.functions.js";
3
+ import type { Notice } from "./fs-ui/NoticeBar.js";
1
4
  /** Pad naar de AI-koppelingen in de shell (Instellingen › AI-Authenticatie). */
2
5
  export declare const AI_AUTH_PATH = "/ai-authenticatie";
3
- /** Korte regel onder de paginatitel: wanneer opgehaald, hoeveel wachten. */
4
- export declare function MailboxStatusMeta(): import("react").JSX.Element | null;
6
+ export declare function useMailboxStatus(): MailboxStatus | null;
7
+ /** Inhoud van de infoballon bij "Bijgewerkt": wanneer opgehaald, hoeveel wachten, adressen. */
8
+ export declare function statusInfo(s: MailboxStatus | null): ReactNode;
5
9
  /** Meldingen over het ophalen (alleen als de gebruiker er iets mee moet). */
6
- export declare function MailboxStatusLine(): import("react").JSX.Element | null;
10
+ export declare function useMailboxStatusNotices(s: MailboxStatus | null): Notice[];
7
11
  //# sourceMappingURL=MailboxStatusLine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MailboxStatusLine.d.ts","sourceRoot":"","sources":["../../src/components/MailboxStatusLine.tsx"],"names":[],"mappings":"AAMA,gFAAgF;AAChF,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAehD,4EAA4E;AAC5E,wBAAgB,iBAAiB,uCAYhC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,uCAgChC"}
1
+ {"version":3,"file":"MailboxStatusLine.d.ts","sourceRoot":"","sources":["../../src/components/MailboxStatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,gFAAgF;AAChF,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAEhD,wBAAgB,gBAAgB,IAOL,aAAa,GAAG,IAAI,CAC9C;AAED,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAU7D;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,EAAE,CA0CzE"}
@@ -1,13 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { useServerFn } from "@tanstack/react-start";
4
4
  import { useIsSuperadmin } from "@flowselections/core";
5
5
  import { getMailboxStatus } from "../lib/mailbox.functions.js";
6
- import { InlineAlert } from "./fs-ui/InlineAlert.js";
6
+ import { fmtDayTime } from "../lib/format-date.js";
7
7
  /** Pad naar de AI-koppelingen in de shell (Instellingen › AI-Authenticatie). */
8
8
  export const AI_AUTH_PATH = "/ai-authenticatie";
9
- const fmt = (iso) => iso ? new Date(iso).toLocaleString("nl-NL", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }) : null;
10
- function useMailboxStatus() {
9
+ export function useMailboxStatus() {
11
10
  const fetchStatus = useServerFn(getMailboxStatus);
12
11
  const { data } = useQuery({
13
12
  queryKey: ["mailbox-status"],
@@ -16,31 +15,55 @@ function useMailboxStatus() {
16
15
  });
17
16
  return (data ?? null);
18
17
  }
19
- /** Korte regel onder de paginatitel: wanneer opgehaald, hoeveel wachten. */
20
- export function MailboxStatusMeta() {
21
- const s = useMailboxStatus();
18
+ /** Inhoud van de infoballon bij "Bijgewerkt": wanneer opgehaald, hoeveel wachten, adressen. */
19
+ export function statusInfo(s) {
22
20
  if (!s)
23
21
  return null;
24
22
  const addresses = Array.isArray(s.addresses) ? s.addresses : [];
25
- const parts = [];
26
- const last = fmt(s.lastOkAt);
27
- parts.push(last ? `Mails opgehaald ${last}` : "Nog geen mails opgehaald");
23
+ const lines = [];
24
+ lines.push(s.lastOkAt ? `Mails opgehaald ${fmtDayTime(s.lastOkAt)}` : "Nog geen mails opgehaald");
28
25
  if (s.waiting > 0)
29
- parts.push(`${s.waiting} wachten op verwerking`);
26
+ lines.push(`${s.waiting} wachten op verwerking`);
30
27
  if (addresses.length > 0)
31
- parts.push(addresses.join(", "));
28
+ lines.push(addresses.join(", "));
32
29
  if (s.ignoredNotFollowed > 0)
33
- parts.push(`${s.ignoredNotFollowed} uit mappen die niet gevolgd worden`);
30
+ lines.push(`${s.ignoredNotFollowed} uit mappen die niet gevolgd worden`);
34
31
  if (s.ignoredUnknown > 0)
35
- parts.push(`${s.ignoredUnknown} uit een onbekende map overgeslagen`);
36
- return _jsx("span", { className: "tabular-nums", children: parts.join(" · ") });
32
+ lines.push(`${s.ignoredUnknown} uit een onbekende map overgeslagen`);
33
+ return _jsx(_Fragment, { children: lines.map((l) => _jsx("div", { className: "truncate", children: l }, l)) });
37
34
  }
38
35
  /** Meldingen over het ophalen (alleen als de gebruiker er iets mee moet). */
39
- export function MailboxStatusLine() {
40
- const s = useMailboxStatus();
36
+ export function useMailboxStatusNotices(s) {
41
37
  const { isAdmin } = useIsSuperadmin();
42
38
  if (!s)
43
- return null;
39
+ return [];
40
+ const out = [];
44
41
  const disconnected = Array.isArray(s.disconnected) ? s.disconnected : [];
45
- return (_jsxs(_Fragment, { children: [disconnected.map((d) => (_jsx(InlineAlert, { tone: "info", title: `${d.count} mails van ${d.email} niet verwerkt (adres ontkoppeld)` }, d.email))), s.problem && (_jsxs(InlineAlert, { tone: "error", title: "Er komen mails binnen, maar ze worden nu niet verwerkt.", children: ["We proberen het elke minuut opnieuw. Blijft dit staan, neem dan contact op met je beheerder.", isAdmin && (_jsxs("details", { className: "mt-1 text-xs text-muted-foreground", children: [_jsx("summary", { className: "cursor-pointer", children: "Details" }), s.problem] }))] })), !s.aiAgent && (_jsx(InlineAlert, { tone: "warning", title: "Automatisch uitlezen staat uit.", action: _jsx("a", { href: AI_AUTH_PATH, className: "text-sm font-medium underline underline-offset-2", children: "Naar Instellingen" }), children: "Bestellingen komen wel binnen, maar je vult ze zelf in. Een beheerder kan dit aanzetten bij Instellingen." }))] }));
42
+ if (disconnected.length > 0) {
43
+ const n = disconnected.reduce((a, d) => a + (Number(d.count) || 0), 0);
44
+ out.push({
45
+ key: "disconnected",
46
+ tone: "info",
47
+ title: `${n} mails niet verwerkt: adres ontkoppeld`,
48
+ details: `Mails van ${disconnected.length === 1 ? "een ontkoppeld adres" : `${disconnected.length} ontkoppelde adressen`} worden niet verwerkt. Koppel het adres opnieuw bij Instellingen.`,
49
+ });
50
+ }
51
+ if (s.problem) {
52
+ out.push({
53
+ key: "problem",
54
+ tone: "error",
55
+ title: "Er komen mails binnen, maar ze worden nu niet verwerkt.",
56
+ details: (_jsxs(_Fragment, { children: ["We proberen het elke minuut opnieuw. Blijft dit staan, neem dan contact op met je beheerder.", isAdmin && (_jsxs("details", { className: "mt-1 text-xs text-muted-foreground", children: [_jsx("summary", { className: "cursor-pointer", children: "Details" }), s.problem] }))] })),
57
+ });
58
+ }
59
+ if (!s.aiAgent) {
60
+ out.push({
61
+ key: "ai",
62
+ tone: "warning",
63
+ title: "Automatisch uitlezen staat uit.",
64
+ details: "Bestellingen komen wel binnen, maar je vult ze zelf in. Een beheerder kan dit aanzetten bij Instellingen.",
65
+ action: _jsx("a", { href: AI_AUTH_PATH, className: "text-sm font-medium underline underline-offset-2", children: "Naar Instellingen" }),
66
+ });
67
+ }
68
+ return out;
46
69
  }
@@ -6,7 +6,7 @@ type Props = {
6
6
  onPageChange: (page: number) => void;
7
7
  onPageSizeChange: (size: number) => void;
8
8
  };
9
- /** Paginering onderaan een tabel: "1–14 van 14 · Per pagina · Vorige/Volgende". */
10
- export declare function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange, }: Props): import("react").JSX.Element;
9
+ /** Paginering exact als Verkooporders: "1–25 van 120" · Per pagina · pijlen met "1 / 5". */
10
+ export declare function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange }: Props): import("react").JSX.Element;
11
11
  export {};
12
12
  //# sourceMappingURL=TablePagination.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TablePagination.d.ts","sourceRoot":"","sources":["../../src/components/TablePagination.tsx"],"names":[],"mappings":"AASA,eAAO,MAAM,iBAAiB,UAAoB,CAAC;AAEnD,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,gBAAgB,GACjB,EAAE,KAAK,+BAqDP"}
1
+ {"version":3,"file":"TablePagination.d.ts","sourceRoot":"","sources":["../../src/components/TablePagination.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,iBAAiB,UAAoB,CAAC;AAEnD,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,EAAE,KAAK,+BAkC/F"}
@@ -1,14 +1,10 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
2
3
  import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
3
- export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
4
- /** Paginering onderaan een tabel: "1–14 van 14 · Per pagina · Vorige/Volgende". */
5
- export function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange, }) {
6
- const pageCount = Math.max(1, Math.ceil(total / pageSize));
7
- const current = Math.min(Math.max(1, page), pageCount);
8
- const from = total === 0 ? 0 : (current - 1) * pageSize + 1;
9
- const to = Math.min(current * pageSize, total);
10
- return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t px-4 py-2 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [from, "\u2013", to, " van ", total] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: "Per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => {
11
- onPageSizeChange(Number(v));
12
- onPageChange(1);
13
- }, children: [_jsx(SelectTrigger, { className: "h-8 w-[80px]", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAGE_SIZE_OPTIONS.map((n) => (_jsx(SelectItem, { value: String(n), children: n }, n))) })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: current <= 1, onClick: () => onPageChange(current - 1), children: "Vorige" }), _jsxs("span", { className: "text-muted-foreground", children: [current, " / ", pageCount] }), _jsx(Button, { size: "sm", variant: "outline", disabled: current >= pageCount, onClick: () => onPageChange(current + 1), children: "Volgende" })] })] }));
4
+ export const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
5
+ /** Paginering exact als Verkooporders: "1–25 van 120" · Per pagina · pijlen met "1 / 5". */
6
+ export function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange }) {
7
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
8
+ const current = Math.min(Math.max(1, page), totalPages);
9
+ return (_jsxs("nav", { "aria-label": "Paginering", className: "shrink-0 flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground", children: [_jsx("div", { className: "tabular-nums", children: total === 0 ? "Geen resultaten" : `${(current - 1) * pageSize + 1}–${Math.min(current * pageSize, total)} van ${total}` }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "hidden items-center gap-2 sm:flex", children: [_jsx("span", { children: "Per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => { onPageSizeChange(Number(v)); onPageChange(1); }, children: [_jsx(SelectTrigger, { className: "h-8 w-20", "aria-label": "Resultaten per pagina", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAGE_SIZE_OPTIONS.map((n) => (_jsx(SelectItem, { value: String(n), children: n }, n))) })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-10 w-10 sm:h-8 sm:w-8", onClick: () => onPageChange(Math.max(1, current - 1)), disabled: current <= 1, "aria-label": "Vorige pagina", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsxs("div", { className: "px-2 text-foreground tabular-nums", "aria-current": "page", children: [current, " / ", totalPages] }), _jsx(Button, { variant: "outline", size: "icon", className: "h-10 w-10 sm:h-8 sm:w-8", onClick: () => onPageChange(Math.min(totalPages, current + 1)), disabled: current >= totalPages, "aria-label": "Volgende pagina", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] })] }));
14
10
  }
@@ -2,16 +2,16 @@ import { type ReactNode } from "react";
2
2
  export interface DataColumn<T> {
3
3
  key: string;
4
4
  header: string;
5
- /** CSS grid-breedte, bijv. "2fr" of "140px". Schaalt evenredig mee. */
6
- width: string;
5
+ /** Vaste breedte in px (colgroup, table-fixed). */
6
+ width: number;
7
7
  cell: (row: T) => ReactNode;
8
8
  /** Klikbare kop voor sorteren. */
9
9
  sortable?: boolean;
10
10
  }
11
11
  /**
12
- * Tabel in één afgerond vlak. Kop blijft zichtbaar bij scrollen, vaste
13
- * kolombreedtes, rij 60 px, geselecteerde rij met groene streep links.
14
- * Onder 768 px als kaarten (mobileCard).
12
+ * Tabel exact als Verkooporders: eigen <table> met table-fixed en colgroup in px,
13
+ * kop 40 px die plakt, rij 60 px, geselecteerde rij met 3 px groene streep.
14
+ * Onder 768 px als kaarten (mobileCard). Paginering (footer) staat onder het vlak.
15
15
  */
16
16
  export declare function DataTable<T>({ rows, columns, rowKey, onRowClick, activeIndex, mobileCard, sort, onSortChange, footer, rowAction, }: {
17
17
  rows: T[];
@@ -26,9 +26,15 @@ export declare function DataTable<T>({ rows, columns, rowKey, onRowClick, active
26
26
  } | null;
27
27
  onSortChange?: (key: string) => void;
28
28
  footer?: ReactNode;
29
- /** Extra actie (menu of knop) rechts in de rij; klik opent de rij niet. */
29
+ /** Rijmenu of knop rechts in de rij; klik opent de rij niet. */
30
30
  rowAction?: (row: T) => ReactNode;
31
31
  }): import("react").JSX.Element;
32
+ /** Cel met hooguit twee regels: 14 px en 12 px grijs; afkappen met volledige tekst als infoballon. */
33
+ export declare function CellText({ primary, secondary, strong }: {
34
+ primary: ReactNode;
35
+ secondary?: ReactNode;
36
+ strong?: boolean;
37
+ }): import("react").JSX.Element;
32
38
  /** Initialen-avatar voor klantnamen. */
33
39
  export declare function InitialsAvatar({ name }: {
34
40
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG1D,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,SAAS,GACV,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAClC,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;CACnC,+BA4EA;AAED,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAQxD"}
1
+ {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG1D,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAQD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,SAAS,GACV,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAClC,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;CACnC,+BA0FA;AAED,sGAAsG;AACtG,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,+BASvH;AAED,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAQxD"}
@@ -1,29 +1,36 @@
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, useRef } from "react";
3
3
  import { ArrowDown, ArrowUp, ChevronRight } from "lucide-react";
4
+ const TABLE_CLS = "w-full [&_thead_th]:h-10 [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground min-w-[900px] table-fixed caption-bottom text-sm [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-10 [&_thead_th]:bg-card [&_tbody_tr]:transition-colors";
5
+ /** Kop- en celruimte zijn gelijk, zodat koptekst en celtekst op dezelfde x beginnen. */
6
+ const PAD = "px-2";
4
7
  /**
5
- * Tabel in één afgerond vlak. Kop blijft zichtbaar bij scrollen, vaste
6
- * kolombreedtes, rij 60 px, geselecteerde rij met groene streep links.
7
- * Onder 768 px als kaarten (mobileCard).
8
+ * Tabel exact als Verkooporders: eigen <table> met table-fixed en colgroup in px,
9
+ * kop 40 px die plakt, rij 60 px, geselecteerde rij met 3 px groene streep.
10
+ * Onder 768 px als kaarten (mobileCard). Paginering (footer) staat onder het vlak.
8
11
  */
9
12
  export function DataTable({ rows, columns, rowKey, onRowClick, activeIndex, mobileCard, sort, onSortChange, footer, rowAction, }) {
10
- const grid = columns.map((c) => c.width).join(" ") + (rowAction || onRowClick ? " 48px" : "");
13
+ const hasEnd = !!(rowAction || onRowClick);
11
14
  const activeRef = useRef(null);
12
15
  useEffect(() => { activeRef.current?.scrollIntoView({ block: "nearest" }); }, [activeIndex]);
13
- return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border border-border bg-card shadow-sm", children: [_jsx("div", { className: "hidden min-h-0 flex-1 overflow-auto overscroll-contain md:block", children: _jsxs("div", { className: "min-w-[900px]", role: "table", children: [_jsxs("div", { role: "row", className: "sticky top-0 z-10 grid h-10 items-center gap-4 border-b border-border bg-card px-4", style: { gridTemplateColumns: grid }, children: [columns.map((c) => {
14
- const active = sort?.key === c.key;
15
- const label = _jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: c.header });
16
- return (_jsx("div", { role: "columnheader", className: "min-w-0 truncate text-left", children: c.sortable && onSortChange ? (_jsxs("button", { type: "button", onClick: () => onSortChange(c.key), className: "inline-flex items-center gap-1 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": `Sorteer op ${c.header}`, children: [label, active && (sort.dir === "asc" ? _jsx(ArrowUp, { className: "h-3 w-3 text-muted-foreground" }) : _jsx(ArrowDown, { className: "h-3 w-3 text-muted-foreground" }))] })) : label }, c.key));
17
- }), (rowAction || onRowClick) && _jsx("div", {})] }), rows.map((r, i) => {
18
- const active = activeIndex === i;
19
- return (_jsxs("div", { role: "row", ref: active ? activeRef : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, onKeyDown: onRowClick ? (e) => { if (e.key === "Enter" && e.target === e.currentTarget) {
20
- e.preventDefault();
21
- onRowClick(r, i);
22
- } } : undefined, className: "group relative grid h-[60px] items-center gap-4 border-b border-border px-4 text-sm text-foreground last:border-0 " +
23
- "transition-colors duration-150 ease-out motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring " +
24
- (onRowClick ? "cursor-pointer hover:bg-muted/50 " : "") +
25
- (active ? "bg-muted/50 before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-primary" : ""), style: { gridTemplateColumns: grid }, children: [columns.map((c) => _jsx("div", { role: "cell", className: "min-w-0", children: c.cell(r) }, c.key)), (rowAction || onRowClick) && (_jsx("div", { className: "flex justify-end", onClick: rowAction ? (e) => e.stopPropagation() : undefined, children: rowAction ? rowAction(r) : (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100", "aria-hidden": true })) }))] }, rowKey(r)));
26
- })] }) }), _jsx("div", { className: "min-h-0 flex-1 divide-y divide-border overflow-y-auto overscroll-contain md:hidden", children: rows.map((r, i) => (_jsx("div", { role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, className: "min-h-11 p-4 text-sm transition-colors active:bg-muted " + (activeIndex === i ? "bg-muted/50" : ""), children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: mobileCard(r) }), rowAction && _jsx("div", { onClick: (e) => e.stopPropagation(), children: rowAction(r) })] }) }, rowKey(r)))) }), footer && _jsx("div", { className: "shrink-0 border-t border-border", children: footer })] }));
16
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full min-w-0 flex-col overflow-clip rounded-xl border border-border bg-card shadow-sm", children: [_jsx("div", { className: "hidden md:block", children: _jsxs("table", { className: TABLE_CLS, children: [_jsxs("colgroup", { children: [columns.map((c) => _jsx("col", { style: { width: `${c.width}px` } }, c.key)), hasEnd && _jsx("col", { style: { width: "56px" } })] }), _jsx("thead", { className: "[&_tr]:border-b", children: _jsxs("tr", { className: "border-b border-border", children: [columns.map((c) => {
17
+ const active = sort?.key === c.key;
18
+ return (_jsx("th", { scope: "col", className: PAD + " text-left align-middle", "aria-sort": active ? (sort.dir === "asc" ? "ascending" : "descending") : undefined, children: c.sortable && onSortChange ? (_jsxs("button", { type: "button", onClick: () => onSortChange(c.key), className: "inline-flex max-w-full items-center gap-1 rounded-sm uppercase focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": `Sorteer op ${c.header}`, children: [_jsx("span", { className: "truncate", children: c.header }), active && (sort.dir === "asc" ? _jsx(ArrowUp, { className: "h-3 w-3 shrink-0", "aria-hidden": true }) : _jsx(ArrowDown, { className: "h-3 w-3 shrink-0", "aria-hidden": true }))] })) : _jsx("span", { className: "block truncate", children: c.header }) }, c.key));
19
+ }), hasEnd && _jsx("th", { scope: "col", className: PAD, children: _jsx("span", { className: "sr-only", children: "Acties" }) })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
20
+ const active = activeIndex === i;
21
+ return (_jsxs("tr", { ref: active ? activeRef : undefined, tabIndex: onRowClick ? 0 : undefined, "data-state": active ? "selected" : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, onKeyDown: onRowClick ? (e) => { if (e.key === "Enter" && e.target === e.currentTarget) {
22
+ e.preventDefault();
23
+ onRowClick(r, i);
24
+ } } : undefined, style: { height: 60 }, className: "group border-b border-border text-foreground last:border-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring " +
25
+ (onRowClick ? "cursor-pointer hover:bg-muted/50 " : "") +
26
+ (active ? "bg-muted/60" : ""), children: [columns.map((c, ci) => (_jsxs("td", { className: PAD + " relative overflow-hidden py-0 align-middle", children: [ci === 0 && active && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), c.cell(r)] }, c.key))), hasEnd && (_jsx("td", { className: PAD + " py-0 text-right align-middle", onClick: rowAction ? (e) => e.stopPropagation() : undefined, children: rowAction ? rowAction(r) : (_jsx(ChevronRight, { className: "ml-auto h-4 w-4 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100", "aria-hidden": true })) }))] }, rowKey(r)));
27
+ }) })] }) }), _jsx("div", { className: "divide-y divide-border md:hidden", children: rows.map((r, i) => (_jsxs("div", { role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, className: "relative min-h-11 p-4 text-sm transition-colors active:bg-muted " + (activeIndex === i ? "bg-muted/60" : ""), children: [activeIndex === i && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: mobileCard(r) }), rowAction && _jsx("div", { onClick: (e) => e.stopPropagation(), children: rowAction(r) })] })] }, rowKey(r)))) })] }), footer] }));
28
+ }
29
+ /** Cel met hooguit twee regels: 14 px en 12 px grijs; afkappen met volledige tekst als infoballon. */
30
+ export function CellText({ primary, secondary, strong }) {
31
+ const t1 = typeof primary === "string" ? primary : undefined;
32
+ const t2 = typeof secondary === "string" ? secondary : undefined;
33
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm " + (strong ? "font-medium" : ""), title: t1, children: primary }), secondary != null && secondary !== "" && _jsx("div", { className: "truncate text-xs text-muted-foreground", title: t2, children: secondary })] }));
27
34
  }
28
35
  /** Initialen-avatar voor klantnamen. */
29
36
  export function InitialsAvatar({ name }) {
@@ -5,7 +5,7 @@ export interface FilterChip {
5
5
  icon?: ReactNode;
6
6
  onRemove: () => void;
7
7
  }
8
- /** Actieve filters als ronde chips met kruisje, plus "Alles wissen". */
8
+ /** Actieve filters als FilterChip uit Verkooporders (hele chip wist), plus "Alles wissen". */
9
9
  export declare function FilterChips({ chips, onClearAll }: {
10
10
  chips: FilterChip[];
11
11
  onClearAll?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"FilterChips.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/FilterChips.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,sCAyBlG"}
1
+ {"version":3,"file":"FilterChips.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/FilterChips.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,sCAwBlG"}
@@ -1,8 +1,8 @@
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 { X } from "lucide-react";
3
- /** Actieve filters als ronde chips met kruisje, plus "Alles wissen". */
3
+ /** Actieve filters als FilterChip uit Verkooporders (hele chip wist), plus "Alles wissen". */
4
4
  export function FilterChips({ chips, onClearAll }) {
5
5
  if (chips.length === 0)
6
6
  return null;
7
- return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [chips.map((c) => (_jsxs("span", { className: "inline-flex h-8 items-center gap-1.5 rounded-full border border-border bg-card pl-3 pr-1 text-xs font-medium text-foreground [&_svg]:h-3.5 [&_svg]:w-3.5", children: [c.icon, c.label, _jsx("button", { type: "button", "aria-label": `Filter ${c.label} weghalen`, onClick: c.onRemove, className: "inline-flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(X, {}) })] }, c.key))), onClearAll && chips.length > 1 && (_jsx("button", { type: "button", onClick: onClearAll, className: "h-8 rounded-lg px-2 text-xs font-medium text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: "Alles wissen" }))] }));
7
+ return (_jsxs(_Fragment, { children: [chips.map((c) => (_jsxs("button", { type: "button", onClick: c.onRemove, "aria-label": `Filter ${c.label} wissen`, className: "inline-flex h-8 items-center gap-1.5 rounded-full border border-border bg-muted px-3 text-xs font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:h-3.5 [&_svg]:w-3.5", children: [c.icon, c.label, _jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true })] }, c.key))), onClearAll && (_jsx("button", { type: "button", onClick: onClearAll, className: "h-8 rounded-lg px-2 text-xs font-medium text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: "Alles wissen" }))] }));
8
8
  }
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from "react";
2
+ /** Zijpaneel met álle extra filters (achter de knop "Filters"). */
3
+ export declare function FilterPanel({ open, onOpenChange, onClear, children, }: {
4
+ open: boolean;
5
+ onOpenChange: (v: boolean) => void;
6
+ onClear?: () => void;
7
+ children: ReactNode;
8
+ }): import("react").JSX.Element;
9
+ /** Eén filtergroep in het paneel: titel + keuzeknoppen. */
10
+ export declare function FilterOptions<T extends string>({ label, value, options, onChange, }: {
11
+ label: string;
12
+ value: T;
13
+ options: Array<{
14
+ value: T;
15
+ label: string;
16
+ }>;
17
+ onChange: (v: T) => void;
18
+ }): import("react").JSX.Element;
19
+ export declare const PERIOD_OPTIONS: ({
20
+ value: "all";
21
+ label: string;
22
+ } | {
23
+ value: "today";
24
+ label: string;
25
+ } | {
26
+ value: "7d";
27
+ label: string;
28
+ } | {
29
+ value: "30d";
30
+ label: string;
31
+ })[];
32
+ //# sourceMappingURL=FilterPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/FilterPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAeA;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,EAC9C,KAAK,EACL,KAAK,EACL,OAAO,EACP,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;CAC1B,+BAyBA;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;IAK1B,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@flowselections/core";
3
+ /** Zijpaneel met álle extra filters (achter de knop "Filters"). */
4
+ export function FilterPanel({ open, onOpenChange, onClear, children, }) {
5
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "left-auto right-0 top-0 flex h-dvh max-h-dvh w-full max-w-sm translate-x-0 translate-y-0 flex-col gap-4 rounded-none border-l border-border p-6 sm:rounded-none", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Filters" }) }), _jsx("div", { className: "min-h-0 flex-1 space-y-6 overflow-y-auto", children: children }), _jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border pt-4", children: [onClear ? _jsx(Button, { variant: "ghost", size: "sm", onClick: onClear, children: "Alles wissen" }) : _jsx("span", {}), _jsx(Button, { size: "sm", onClick: () => onOpenChange(false), children: "Klaar" })] })] }) }));
6
+ }
7
+ /** Eén filtergroep in het paneel: titel + keuzeknoppen. */
8
+ export function FilterOptions({ label, value, options, onChange, }) {
9
+ return (_jsxs("fieldset", { className: "space-y-2", children: [_jsx("legend", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "flex flex-wrap gap-2", children: options.map((o) => {
10
+ const on = o.value === value;
11
+ return (_jsx("button", { type: "button", "aria-pressed": on, onClick: () => onChange(o.value), className: "inline-flex h-9 items-center rounded-lg border px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " +
12
+ (on ? "border-primary/30 bg-primary/10 font-semibold text-foreground" : "border-border bg-card text-foreground/80 hover:bg-muted"), children: o.label }, o.value));
13
+ }) })] }));
14
+ }
15
+ export const PERIOD_OPTIONS = [
16
+ { value: "all", label: "Alle periodes" },
17
+ { value: "today", label: "Vandaag" },
18
+ { value: "7d", label: "Laatste 7 dagen" },
19
+ { value: "30d", label: "Laatste 30 dagen" },
20
+ ];
@@ -1,5 +1,5 @@
1
- import { type ReactNode } from "react";
2
- /** Melding: zacht vlak, icoon links, actie rechts. Op mobiel ingeklapt tot één regel. */
1
+ import type { ReactNode } from "react";
2
+ /** Losse melding in dezelfde vorm als de NoticeBar (één regel, details uitklapbaar). */
3
3
  export declare function InlineAlert({ tone, title, children, action, }: {
4
4
  tone?: "warning" | "error" | "info";
5
5
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"InlineAlert.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/InlineAlert.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,EAC1B,IAAgB,EAChB,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,+BA0BA"}
1
+ {"version":3,"file":"InlineAlert.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/InlineAlert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,EAC1B,IAAgB,EAChB,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,+BAEA"}
@@ -1,14 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { AlertTriangle, Info, XCircle } from "lucide-react";
4
- /** Melding: zacht vlak, icoon links, actie rechts. Op mobiel ingeklapt tot één regel. */
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NoticeRow } from "./NoticeBar.js";
3
+ /** Losse melding in dezelfde vorm als de NoticeBar (één regel, details uitklapbaar). */
5
4
  export function InlineAlert({ tone = "warning", title, children, action, }) {
6
- const [open, setOpen] = useState(false);
7
- const Icon = tone === "error" ? XCircle : tone === "info" ? Info : AlertTriangle;
8
- const cls = tone === "error"
9
- ? "border-destructive/30 bg-destructive/5 text-foreground [&_.ia-icon]:text-destructive"
10
- : tone === "info"
11
- ? "border-border bg-muted/50 text-foreground [&_.ia-icon]:text-muted-foreground"
12
- : "border-warning/30 bg-warning-soft text-warning-soft-foreground [&_.ia-icon]:text-warning-soft-foreground";
13
- return (_jsxs("div", { role: tone === "error" ? "alert" : "status", className: "flex shrink-0 items-start gap-3 rounded-lg border px-3 py-2.5 text-sm " + cls, children: [_jsx(Icon, { className: "ia-icon mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("button", { type: "button", onClick: () => setOpen((v) => !v), className: "block w-full text-left font-medium sm:pointer-events-none", "aria-expanded": open, children: _jsx("span", { className: open ? "" : "line-clamp-1 sm:line-clamp-none", children: title }) }), children && _jsx("div", { className: "mt-0.5 text-sm opacity-90 " + (open ? "" : "hidden sm:block"), children: children })] }), action && _jsx("div", { className: "shrink-0", children: action })] }));
5
+ return _jsx(NoticeRow, { notice: { key: title, tone, title, details: children, action } });
14
6
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /** Rij KPI-tegels: 4 naast elkaar vanaf desktop, 2 op tablet, veegbaar op mobiel. */
3
3
  export function KpiRow({ children }) {
4
- return (_jsx("div", { className: "-mx-4 flex shrink-0 snap-x snap-mandatory gap-3 overflow-x-auto px-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden " +
4
+ return (_jsx("div", { className: "-mx-4 flex shrink-0 scroll-px-4 snap-x snap-mandatory gap-3 overflow-x-auto px-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden " +
5
5
  "md:mx-0 md:grid md:grid-cols-2 md:overflow-visible md:px-0 lg:grid-cols-4 " +
6
6
  "[&>*]:w-[78%] [&>*]:shrink-0 [&>*]:snap-start md:[&>*]:w-auto", children: children }));
7
7
  }
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ export interface Notice {
3
+ key: string;
4
+ tone?: "warning" | "error" | "info";
5
+ title: string;
6
+ /** Uitklapbare toelichting (nooit een lijst van items). */
7
+ details?: ReactNode;
8
+ action?: ReactNode;
9
+ }
10
+ /** Eén melding op één regel; details klappen uit (stijl NoticeBar uit Verkooporders). */
11
+ export declare function NoticeRow({ notice }: {
12
+ notice: Notice;
13
+ }): import("react").JSX.Element;
14
+ /** Meldingen direct onder de kop: hooguit 2 zichtbaar, de rest achter "+ n meldingen". */
15
+ export declare function NoticeBar({ notices }: {
16
+ notices: Notice[];
17
+ }): import("react").JSX.Element | null;
18
+ //# sourceMappingURL=NoticeBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoticeBar.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/NoticeBar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAQD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,+BAwBvD;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,sCAmB3D"}