@flowselections/mailbox-orders 1.0.53 → 1.0.57
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.
- package/dist-lib/components/MailboxAuthBanner.d.ts +3 -3
- package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -1
- package/dist-lib/components/MailboxAuthBanner.js +20 -7
- package/dist-lib/components/MailboxPageFrame.d.ts +25 -0
- package/dist-lib/components/MailboxPageFrame.d.ts.map +1 -0
- package/dist-lib/components/MailboxPageFrame.js +103 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProcessedPage.js +102 -61
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +191 -76
- package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxSkippedPage.js +97 -19
- package/dist-lib/components/MailboxStatusLine.d.ts +8 -2
- package/dist-lib/components/MailboxStatusLine.d.ts.map +1 -1
- package/dist-lib/components/MailboxStatusLine.js +54 -8
- package/dist-lib/components/TablePagination.d.ts +2 -2
- package/dist-lib/components/TablePagination.d.ts.map +1 -1
- package/dist-lib/components/TablePagination.js +8 -12
- package/dist-lib/components/fs-ui/DataTable.d.ts +42 -0
- package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/DataTable.js +40 -0
- package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
- package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/EmptyState.js +5 -0
- package/dist-lib/components/fs-ui/FilterChips.d.ts +13 -0
- package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/FilterChips.js +8 -0
- package/dist-lib/components/fs-ui/FilterPanel.d.ts +32 -0
- package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/FilterPanel.js +20 -0
- package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
- package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/HoverTip.js +24 -0
- package/dist-lib/components/fs-ui/InlineAlert.d.ts +9 -0
- package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/InlineAlert.js +6 -0
- package/dist-lib/components/fs-ui/KpiRow.d.ts +6 -0
- package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/KpiRow.js +7 -0
- package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
- package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/KpiTile.js +40 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +10 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.js +7 -0
- package/dist-lib/components/fs-ui/NoticeBar.d.ts +18 -0
- package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/NoticeBar.js +24 -0
- package/dist-lib/components/fs-ui/PageHeader.d.ts +8 -0
- package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/PageHeader.js +5 -0
- package/dist-lib/components/fs-ui/PillTabs.d.ts +16 -0
- package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/PillTabs.js +29 -0
- package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
- package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/RowMenu.js +10 -0
- package/dist-lib/components/fs-ui/SearchInput.d.ts +12 -0
- package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/SearchInput.js +38 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +7 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
- package/dist-lib/components/fs-ui/StatusIcon.d.ts +8 -0
- package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/StatusIcon.js +16 -0
- package/dist-lib/components/fs-ui/TableSkeleton.d.ts +6 -0
- package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
- package/dist-lib/components/fs-ui/Toolbar.d.ts +13 -0
- package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/Toolbar.js +10 -0
- package/dist-lib/components/fs-ui/UpdatedAt.d.ts +8 -0
- package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/UpdatedAt.js +17 -0
- package/dist-lib/components/fs-ui/useListHotkeys.d.ts +18 -0
- package/dist-lib/components/fs-ui/useListHotkeys.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/useListHotkeys.js +48 -0
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -1
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -1
- package/dist-lib/components/settings/MailboxAiPromptCard.js +9 -1
- package/dist-lib/components/settings/MailboxFilterSettingsCard.js +1 -1
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -1
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -1
- package/dist-lib/components/settings/MailboxTestSetCard.js +10 -2
- package/dist-lib/integrations/supabase/types.d.ts +372 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/format-date.d.ts +5 -0
- package/dist-lib/lib/format-date.d.ts.map +1 -0
- package/dist-lib/lib/format-date.js +30 -0
- package/dist-lib/lib/labels.d.ts +15 -0
- package/dist-lib/lib/labels.d.ts.map +1 -0
- package/dist-lib/lib/labels.js +57 -0
- package/dist-lib/lib/mailbox-list.functions.d.ts +67 -0
- package/dist-lib/lib/mailbox-list.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-list.functions.js +61 -0
- package/dist-lib/lib/mailbox.functions.js +3 -3
- package/package.json +1 -1
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from "react";
|
|
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 {
|
|
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";
|
|
6
9
|
import { listSkippedMessages, analyzeMessageNow } from "../lib/mailbox.functions.js";
|
|
7
10
|
import { TablePagination } from "./TablePagination.js";
|
|
8
|
-
import {
|
|
11
|
+
import { usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
|
|
12
|
+
import { MailboxPageFrame } from "./MailboxPageFrame.js";
|
|
13
|
+
import { DataTable, CellText } from "./fs-ui/DataTable.js";
|
|
14
|
+
import { FilterPanel, FilterOptions, PERIOD_OPTIONS } from "./fs-ui/FilterPanel.js";
|
|
15
|
+
import { StatusIcon } from "./fs-ui/StatusIcon.js";
|
|
16
|
+
import { EmptyState } from "./fs-ui/EmptyState.js";
|
|
17
|
+
import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
|
|
18
|
+
import { Toolbar } from "./fs-ui/Toolbar.js";
|
|
19
|
+
import { SearchInput } from "./fs-ui/SearchInput.js";
|
|
20
|
+
import { FilterChips } from "./fs-ui/FilterChips.js";
|
|
21
|
+
import { useListHotkeys } from "./fs-ui/useListHotkeys.js";
|
|
9
22
|
const statusOf = (m) => m.analysis_state === "wacht"
|
|
10
|
-
? { label:
|
|
11
|
-
: m.filter_outcome === "te_oud"
|
|
12
|
-
? { label:
|
|
13
|
-
:
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
? { label: label(ANALYSIS_STATE, "wacht"), tone: "amber" }
|
|
24
|
+
: m.filter_outcome === "te_oud" || m.filter_outcome === "geen_bestelling"
|
|
25
|
+
? { label: label(FILTER_OUTCOME, m.filter_outcome), tone: "grey" }
|
|
26
|
+
: { label: "Overgeslagen", tone: "grey" };
|
|
27
|
+
/** Reden in gewone woorden (codes uit de verwerking worden vertaald). */
|
|
28
|
+
const reasonText = (m) => {
|
|
29
|
+
const raw = String(m.analysis_note || m.filter_reason || "");
|
|
30
|
+
if (m.filter_outcome === "te_oud")
|
|
31
|
+
return "Te oud bij eerste ophalen";
|
|
32
|
+
if (/niet uit een gevolgde map/i.test(raw))
|
|
33
|
+
return "Uit een map die niet gevolgd wordt";
|
|
34
|
+
if (/map onbekend/i.test(raw))
|
|
35
|
+
return "Map niet bekend";
|
|
36
|
+
// Nooit ruwe codes (snake_case, HOOFDLETTERCODES, [haakjes]) in beeld.
|
|
37
|
+
if (/[a-z]+_[a-z_]+|\b[A-Z]{2,}_[A-Z_]+\b|\[[^\]]*\]/.test(raw)) {
|
|
38
|
+
console.warn("[mailbox] reden", raw);
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
return raw;
|
|
42
|
+
};
|
|
43
|
+
const PERIOD_LABEL = { today: "Vandaag", "7d": "Laatste 7 dagen", "30d": "Laatste 30 dagen" };
|
|
16
44
|
export function MailboxSkippedPage() {
|
|
17
45
|
const qc = useQueryClient();
|
|
18
46
|
const fetchList = useServerFn(listSkippedMessages);
|
|
19
47
|
const analyze = useServerFn(analyzeMessageNow);
|
|
20
|
-
const { data, isLoading, error } = useQuery({
|
|
48
|
+
const { data, isLoading, error, refetch } = useQuery({
|
|
21
49
|
queryKey: ["mailbox-skipped"],
|
|
22
50
|
queryFn: () => fetchList(),
|
|
23
51
|
refetchInterval: 60000,
|
|
52
|
+
placeholderData: (prev) => prev,
|
|
24
53
|
});
|
|
25
54
|
const items = Array.isArray(data?.items) ? data.items : [];
|
|
26
55
|
const [busyId, setBusyId] = useState(null);
|
|
27
56
|
const [page, setPage] = useState(1);
|
|
28
|
-
const [pageSize, setPageSize] = useState(
|
|
57
|
+
const [pageSize, setPageSize] = useState(50);
|
|
29
58
|
const [search, setSearch] = usePersistedState("mailbox-skipped:search", "");
|
|
30
59
|
const [period, setPeriod] = usePersistedState("mailbox-skipped:period", "all");
|
|
31
60
|
const [sortOrder, setSortOrder] = usePersistedState("mailbox-skipped:sort", "date-desc");
|
|
61
|
+
const [cursor, setCursor] = useState(null);
|
|
62
|
+
const [helpOpen, setHelpOpen] = useState(false);
|
|
63
|
+
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
64
|
+
const searchRef = useRef(null);
|
|
32
65
|
const filtered = useMemo(() => {
|
|
33
66
|
const q = normalizeText(search).trim();
|
|
34
67
|
return items
|
|
@@ -44,30 +77,75 @@ export function MailboxSkippedPage() {
|
|
|
44
77
|
: String(a.received_at ?? "").localeCompare(String(b.received_at ?? "")));
|
|
45
78
|
}, [items, search, period, sortOrder]);
|
|
46
79
|
const waiting = items.filter((m) => m.analysis_state === "wacht").length;
|
|
80
|
+
useEffect(() => { setPage(1); setCursor(null); }, [search, period, sortOrder]);
|
|
47
81
|
const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize));
|
|
48
82
|
const current = Math.min(page, pageCount);
|
|
49
83
|
const paged = filtered.slice((current - 1) * pageSize, current * pageSize);
|
|
84
|
+
const hasActiveFilters = !!search || period !== "all";
|
|
85
|
+
const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); };
|
|
50
86
|
const run = async (id) => {
|
|
51
87
|
setBusyId(id);
|
|
52
88
|
try {
|
|
53
89
|
const res = await analyze({ data: { messageId: id } });
|
|
54
90
|
if (res?.ok) {
|
|
55
|
-
toast.success("Mail
|
|
91
|
+
toast.success("Mail uitgelezen. Je vindt hem nu bij Te controleren.");
|
|
56
92
|
void qc.invalidateQueries({ queryKey: ["mailbox-skipped"] });
|
|
57
93
|
void qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
|
|
94
|
+
void qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (res?.error)
|
|
98
|
+
console.warn("[mailbox] toch uitlezen", res.error);
|
|
99
|
+
toast.error(res?.error === "Deze mail wordt al geanalyseerd." ? "Deze mail wordt al uitgelezen." : "Uitlezen lukt niet. Probeer het opnieuw.");
|
|
58
100
|
}
|
|
59
|
-
else
|
|
60
|
-
toast.error(res?.error ?? "Analyseren mislukt");
|
|
61
101
|
}
|
|
62
102
|
catch (e) {
|
|
63
|
-
|
|
103
|
+
console.warn("[mailbox] toch uitlezen", e);
|
|
104
|
+
toast.error("Uitlezen lukt niet. Probeer het opnieuw.");
|
|
64
105
|
}
|
|
65
106
|
finally {
|
|
66
107
|
setBusyId(null);
|
|
67
108
|
}
|
|
68
109
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
useListHotkeys({
|
|
111
|
+
disabled: helpOpen || filtersOpen,
|
|
112
|
+
onSearch: () => searchRef.current?.focus(),
|
|
113
|
+
onNext: () => setCursor((c) => (paged.length ? Math.min((c ?? -1) + 1, paged.length - 1) : null)),
|
|
114
|
+
onPrev: () => setCursor((c) => (paged.length ? Math.max((c ?? 1) - 1, 0) : null)),
|
|
115
|
+
onEscape: hasActiveFilters ? clearFilters : undefined,
|
|
116
|
+
onHelp: () => setHelpOpen(true),
|
|
117
|
+
});
|
|
118
|
+
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 columns = [
|
|
120
|
+
{
|
|
121
|
+
key: "afzender", header: "Afzender", width: 200,
|
|
122
|
+
cell: (m) => _jsx(CellText, { primary: m.from_name || m.from_email || "Onbekend", secondary: m.from_name ? m.from_email : undefined, strong: true }),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: "bericht", header: "Bericht", width: 300,
|
|
126
|
+
cell: (m) => _jsx(CellText, { primary: m.subject || "(geen onderwerp)", secondary: m.preview || "" }),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "datum", header: "Ontvangen", width: 132, sortable: true,
|
|
130
|
+
cell: (m) => _jsx("span", { className: "block truncate tabular-nums", children: fmtDayTime(m.received_at) }),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "reden", header: "Reden", width: 200,
|
|
134
|
+
cell: (m) => {
|
|
135
|
+
const st = statusOf(m);
|
|
136
|
+
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) })] }));
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{ key: "actie", header: "Actie", width: 136, cell: (m) => actionBtn(m) },
|
|
140
|
+
];
|
|
141
|
+
const chips = period !== "all" ? [{ key: "period", label: PERIOD_LABEL[period] ?? period, icon: _jsx(CalendarClock, {}), onRemove: () => setPeriod("all") }] : [];
|
|
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, 136] })) : 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
|
|
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" }) })
|
|
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, 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] }), _jsx("div", { className: "pt-1", children: actionBtn(m) })] })), footer: _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) }))] }));
|
|
73
151
|
}
|
|
@@ -1,5 +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
|
-
|
|
4
|
-
|
|
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;
|
|
9
|
+
/** Meldingen over het ophalen (alleen als de gebruiker er iets mee moet). */
|
|
10
|
+
export declare function useMailboxStatusNotices(s: MailboxStatus | null): Notice[];
|
|
5
11
|
//# sourceMappingURL=MailboxStatusLine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MailboxStatusLine.d.ts","sourceRoot":"","sources":["../../src/components/MailboxStatusLine.tsx"],"names":[],"mappings":"
|
|
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,23 +1,69 @@
|
|
|
1
|
-
import {
|
|
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
|
-
import {
|
|
4
|
+
import { useIsSuperadmin } from "@flowselections/core";
|
|
5
5
|
import { getMailboxStatus } from "../lib/mailbox.functions.js";
|
|
6
|
+
import { fmtDayTime } from "../lib/format-date.js";
|
|
6
7
|
/** Pad naar de AI-koppelingen in de shell (Instellingen › AI-Authenticatie). */
|
|
7
8
|
export const AI_AUTH_PATH = "/ai-authenticatie";
|
|
8
|
-
|
|
9
|
-
/** Kleine statusregel: laatste geslaagde ronde, wachtende mails, problemen in gewone taal. */
|
|
10
|
-
export function MailboxStatusLine() {
|
|
9
|
+
export function useMailboxStatus() {
|
|
11
10
|
const fetchStatus = useServerFn(getMailboxStatus);
|
|
12
11
|
const { data } = useQuery({
|
|
13
12
|
queryKey: ["mailbox-status"],
|
|
14
13
|
queryFn: () => fetchStatus(),
|
|
15
14
|
refetchInterval: 60000,
|
|
16
15
|
});
|
|
17
|
-
|
|
16
|
+
return (data ?? null);
|
|
17
|
+
}
|
|
18
|
+
/** Inhoud van de infoballon bij "Bijgewerkt": wanneer opgehaald, hoeveel wachten, adressen. */
|
|
19
|
+
export function statusInfo(s) {
|
|
18
20
|
if (!s)
|
|
19
21
|
return null;
|
|
20
|
-
const disconnected = Array.isArray(s.disconnected) ? s.disconnected : [];
|
|
21
22
|
const addresses = Array.isArray(s.addresses) ? s.addresses : [];
|
|
22
|
-
|
|
23
|
+
const lines = [];
|
|
24
|
+
lines.push(s.lastOkAt ? `Mails opgehaald ${fmtDayTime(s.lastOkAt)}` : "Nog geen mails opgehaald");
|
|
25
|
+
if (s.waiting > 0)
|
|
26
|
+
lines.push(`${s.waiting} wachten op verwerking`);
|
|
27
|
+
if (addresses.length > 0)
|
|
28
|
+
lines.push(addresses.join(", "));
|
|
29
|
+
if (s.ignoredNotFollowed > 0)
|
|
30
|
+
lines.push(`${s.ignoredNotFollowed} uit mappen die niet gevolgd worden`);
|
|
31
|
+
if (s.ignoredUnknown > 0)
|
|
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)) });
|
|
34
|
+
}
|
|
35
|
+
/** Meldingen over het ophalen (alleen als de gebruiker er iets mee moet). */
|
|
36
|
+
export function useMailboxStatusNotices(s) {
|
|
37
|
+
const { isAdmin } = useIsSuperadmin();
|
|
38
|
+
if (!s)
|
|
39
|
+
return [];
|
|
40
|
+
const out = [];
|
|
41
|
+
const disconnected = Array.isArray(s.disconnected) ? s.disconnected : [];
|
|
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;
|
|
23
69
|
}
|
|
@@ -6,7 +6,7 @@ type Props = {
|
|
|
6
6
|
onPageChange: (page: number) => void;
|
|
7
7
|
onPageSizeChange: (size: number) => void;
|
|
8
8
|
};
|
|
9
|
-
/** Paginering
|
|
10
|
-
export declare function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange
|
|
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":"
|
|
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 {
|
|
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,
|
|
4
|
-
/** Paginering
|
|
5
|
-
export function TablePagination({ total, page, pageSize, onPageChange, onPageSizeChange
|
|
6
|
-
const
|
|
7
|
-
const current = Math.min(Math.max(1, page),
|
|
8
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface DataColumn<T> {
|
|
3
|
+
key: string;
|
|
4
|
+
header: string;
|
|
5
|
+
/** Vaste breedte in px (colgroup, table-fixed). */
|
|
6
|
+
width: number;
|
|
7
|
+
cell: (row: T) => ReactNode;
|
|
8
|
+
/** Klikbare kop voor sorteren. */
|
|
9
|
+
sortable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
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
|
+
*/
|
|
16
|
+
export declare function DataTable<T>({ rows, columns, rowKey, onRowClick, activeIndex, mobileCard, sort, onSortChange, footer, rowAction, }: {
|
|
17
|
+
rows: T[];
|
|
18
|
+
columns: DataColumn<T>[];
|
|
19
|
+
rowKey: (row: T) => string;
|
|
20
|
+
onRowClick?: (row: T, index: number) => void;
|
|
21
|
+
activeIndex?: number | null;
|
|
22
|
+
mobileCard: (row: T) => ReactNode;
|
|
23
|
+
sort?: {
|
|
24
|
+
key: string;
|
|
25
|
+
dir: "asc" | "desc";
|
|
26
|
+
} | null;
|
|
27
|
+
onSortChange?: (key: string) => void;
|
|
28
|
+
footer?: ReactNode;
|
|
29
|
+
/** Rijmenu of knop rechts in de rij; klik opent de rij niet. */
|
|
30
|
+
rowAction?: (row: T) => ReactNode;
|
|
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;
|
|
38
|
+
/** Initialen-avatar voor klantnamen. */
|
|
39
|
+
export declare function InitialsAvatar({ name }: {
|
|
40
|
+
name: string;
|
|
41
|
+
}): import("react").JSX.Element;
|
|
42
|
+
//# sourceMappingURL=DataTable.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
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-4";
|
|
7
|
+
/**
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
export function DataTable({ rows, columns, rowKey, onRowClick, activeIndex, mobileCard, sort, onSortChange, footer, rowAction, }) {
|
|
13
|
+
const hasEnd = !!(rowAction || onRowClick);
|
|
14
|
+
const activeRef = useRef(null);
|
|
15
|
+
useEffect(() => { activeRef.current?.scrollIntoView({ block: "nearest" }); }, [activeIndex]);
|
|
16
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-clip 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("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: "min-h-0 flex-1 divide-y divide-border overflow-y-auto overscroll-contain 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 })] }));
|
|
34
|
+
}
|
|
35
|
+
/** Initialen-avatar voor klantnamen. */
|
|
36
|
+
export function InitialsAvatar({ name }) {
|
|
37
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
38
|
+
const ini = ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase() || "?";
|
|
39
|
+
return (_jsx("span", { className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-semibold text-primary", "aria-hidden": true, children: ini }));
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** Lege toestand: groot zacht icoon, korte zin, één actie. */
|
|
3
|
+
export declare function EmptyState({ icon, title, text, action, }: {
|
|
4
|
+
icon: ReactNode;
|
|
5
|
+
title: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=EmptyState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/EmptyState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,MAAM,GACP,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,+BAaA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Lege toestand: groot zacht icoon, korte zin, één actie. */
|
|
3
|
+
export function EmptyState({ icon, title, text, action, }) {
|
|
4
|
+
return (_jsxs("div", { className: "flex flex-col items-center gap-3 px-4 py-14 text-center", children: [_jsx("span", { className: "inline-flex h-14 w-14 items-center justify-center rounded-full bg-muted text-muted-foreground", "aria-hidden": true, children: icon }), _jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: title }), text && _jsx("div", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: text })] }), action] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface FilterChip {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: ReactNode;
|
|
6
|
+
onRemove: () => void;
|
|
7
|
+
}
|
|
8
|
+
/** Actieve filters als FilterChip uit Verkooporders (hele chip wist), plus "Alles wissen". */
|
|
9
|
+
export declare function FilterChips({ chips, onClearAll }: {
|
|
10
|
+
chips: FilterChip[];
|
|
11
|
+
onClearAll?: () => void;
|
|
12
|
+
}): import("react").JSX.Element | null;
|
|
13
|
+
//# sourceMappingURL=FilterChips.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
/** Actieve filters als FilterChip uit Verkooporders (hele chip wist), plus "Alles wissen". */
|
|
4
|
+
export function FilterChips({ chips, onClearAll }) {
|
|
5
|
+
if (chips.length === 0)
|
|
6
|
+
return null;
|
|
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
|
+
}
|
|
@@ -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
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Tooltip op basis van Popover (core heeft geen Tooltip-export): opent bij
|
|
4
|
+
* aanwijzen én bij toetsenbordfocus, sluit met Esc of bij verlaten. De
|
|
5
|
+
* trigger is een knop met een toegankelijke naam.
|
|
6
|
+
*/
|
|
7
|
+
export declare function HoverTip({ content, label, children, side, align, className, }: {
|
|
8
|
+
align?: "start" | "center" | "end";
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
label: string;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
13
|
+
className?: string;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=HoverTip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HoverTip.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/HoverTip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGzD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,IAAY,EACZ,KAAgB,EAChB,SAAc,GACf,EAAE;IACD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA4CA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@flowselections/core";
|
|
4
|
+
/**
|
|
5
|
+
* Tooltip op basis van Popover (core heeft geen Tooltip-export): opent bij
|
|
6
|
+
* aanwijzen én bij toetsenbordfocus, sluit met Esc of bij verlaten. De
|
|
7
|
+
* trigger is een knop met een toegankelijke naam.
|
|
8
|
+
*/
|
|
9
|
+
export function HoverTip({ content, label, children, side = "top", align = "center", className = "", }) {
|
|
10
|
+
const [open, setOpen] = useState(false);
|
|
11
|
+
const timer = useRef(null);
|
|
12
|
+
const show = () => {
|
|
13
|
+
if (timer.current)
|
|
14
|
+
clearTimeout(timer.current);
|
|
15
|
+
setOpen(true);
|
|
16
|
+
};
|
|
17
|
+
const hide = () => {
|
|
18
|
+
if (timer.current)
|
|
19
|
+
clearTimeout(timer.current);
|
|
20
|
+
timer.current = setTimeout(() => setOpen(false), 80);
|
|
21
|
+
};
|
|
22
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { type: "button", "aria-label": label, onPointerEnter: show, onPointerLeave: hide, onFocus: show, onBlur: hide, onClick: (e) => e.stopPropagation(), className: "inline-flex shrink-0 items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " +
|
|
23
|
+
className, children: children }), _jsx(PopoverContent, { side: side, align: align, avoidCollisions: true, collisionPadding: 8, onOpenAutoFocus: (e) => e.preventDefault(), onCloseAutoFocus: (e) => e.preventDefault(), onPointerEnter: show, onPointerLeave: hide, onClick: (e) => e.stopPropagation(), className: "w-auto min-w-[14rem] max-w-[20rem] rounded-lg p-3 text-xs leading-snug shadow-lg motion-reduce:animate-none", children: content })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** Losse melding in dezelfde vorm als de NoticeBar (één regel, details uitklapbaar). */
|
|
3
|
+
export declare function InlineAlert({ tone, title, children, action, }: {
|
|
4
|
+
tone?: "warning" | "error" | "info";
|
|
5
|
+
title: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=InlineAlert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
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). */
|
|
4
|
+
export function InlineAlert({ tone = "warning", title, children, action, }) {
|
|
5
|
+
return _jsx(NoticeRow, { notice: { key: title, tone, title, details: children, action } });
|
|
6
|
+
}
|