@flowselections/mailbox-orders 1.0.30 → 1.0.33

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 (39) hide show
  1. package/dist-lib/components/MailboxProcessedPage.js +1 -1
  2. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  3. package/dist-lib/components/MailboxProposalsPage.js +202 -122
  4. package/dist-lib/components/MailboxSkippedPage.d.ts +2 -0
  5. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -0
  6. package/dist-lib/components/MailboxSkippedPage.js +71 -0
  7. package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts +2 -0
  8. package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts.map +1 -0
  9. package/dist-lib/components/settings/MailboxFilterSettingsCard.js +78 -0
  10. package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -0
  11. package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -0
  12. package/dist-lib/components/settings/MailboxTestSetCard.js +69 -0
  13. package/dist-lib/index.d.ts +1 -0
  14. package/dist-lib/index.d.ts.map +1 -1
  15. package/dist-lib/index.js +8 -2
  16. package/dist-lib/integrations/supabase/types.d.ts +1442 -0
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/mailbox-ai.server.d.ts +33 -12
  19. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
  20. package/dist-lib/lib/mailbox-ai.server.js +47 -17
  21. package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +143 -0
  22. package/dist-lib/lib/mailbox-filter-settings.functions.d.ts.map +1 -0
  23. package/dist-lib/lib/mailbox-filter-settings.functions.js +241 -0
  24. package/dist-lib/lib/mailbox-filter.d.ts +34 -0
  25. package/dist-lib/lib/mailbox-filter.d.ts.map +1 -0
  26. package/dist-lib/lib/mailbox-filter.js +121 -0
  27. package/dist-lib/lib/mailbox-ingest.server.d.ts +114 -17
  28. package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
  29. package/dist-lib/lib/mailbox-ingest.server.js +594 -266
  30. package/dist-lib/lib/mailbox-match.d.ts +28 -0
  31. package/dist-lib/lib/mailbox-match.d.ts.map +1 -0
  32. package/dist-lib/lib/mailbox-match.js +74 -0
  33. package/dist-lib/lib/mailbox-units.d.ts +18 -0
  34. package/dist-lib/lib/mailbox-units.d.ts.map +1 -0
  35. package/dist-lib/lib/mailbox-units.js +80 -0
  36. package/dist-lib/lib/mailbox.functions.d.ts +74 -0
  37. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/mailbox.functions.js +170 -25
  39. package/package.json +6 -1
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Badge, Button, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
+ import { listSkippedMessages, analyzeMessageNow } from "../lib/mailbox.functions.js";
7
+ import { TablePagination } from "./TablePagination.js";
8
+ import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
9
+ const statusOf = (m) => m.analysis_state === "wacht"
10
+ ? { label: "Wacht op analyse", tone: "amber" }
11
+ : m.filter_outcome === "geen_bestelling"
12
+ ? { label: "Geen bestelling", tone: "grey" }
13
+ : { label: "Overgeslagen", tone: "grey" };
14
+ export function MailboxSkippedPage() {
15
+ const qc = useQueryClient();
16
+ const fetchList = useServerFn(listSkippedMessages);
17
+ const analyze = useServerFn(analyzeMessageNow);
18
+ const { data, isLoading, error } = useQuery({
19
+ queryKey: ["mailbox-skipped"],
20
+ queryFn: () => fetchList(),
21
+ refetchInterval: 60000,
22
+ });
23
+ const items = Array.isArray(data?.items) ? data.items : [];
24
+ const [busyId, setBusyId] = useState(null);
25
+ const [page, setPage] = useState(1);
26
+ const [pageSize, setPageSize] = useState(25);
27
+ const [search, setSearch] = usePersistedState("mailbox-skipped:search", "");
28
+ const [period, setPeriod] = usePersistedState("mailbox-skipped:period", "all");
29
+ const [sortOrder, setSortOrder] = usePersistedState("mailbox-skipped:sort", "date-desc");
30
+ const filtered = useMemo(() => {
31
+ const q = normalizeText(search).trim();
32
+ return items
33
+ .filter((m) => matchesPeriod(m.received_at, period))
34
+ .filter((m) => {
35
+ if (!q)
36
+ return true;
37
+ const hay = normalizeText([m.from_email, m.from_name, m.subject, m.preview, m.filter_reason].join(" "));
38
+ return q.split(/\s+/).every((t) => hay.includes(t));
39
+ })
40
+ .sort((a, b) => sortOrder === "date-desc"
41
+ ? String(b.received_at ?? "").localeCompare(String(a.received_at ?? ""))
42
+ : String(a.received_at ?? "").localeCompare(String(b.received_at ?? "")));
43
+ }, [items, search, period, sortOrder]);
44
+ const waiting = items.filter((m) => m.analysis_state === "wacht").length;
45
+ const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize));
46
+ const current = Math.min(page, pageCount);
47
+ const paged = filtered.slice((current - 1) * pageSize, current * pageSize);
48
+ const run = async (id) => {
49
+ setBusyId(id);
50
+ try {
51
+ const res = await analyze({ data: { messageId: id } });
52
+ if (res?.ok) {
53
+ toast.success("Mail geanalyseerd. Je vindt hem nu bij Voorstellen.");
54
+ void qc.invalidateQueries({ queryKey: ["mailbox-skipped"] });
55
+ void qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
56
+ }
57
+ else
58
+ toast.error(res?.error ?? "Analyseren mislukt");
59
+ }
60
+ catch (e) {
61
+ toast.error(e?.message ?? "Analyseren mislukt");
62
+ }
63
+ finally {
64
+ setBusyId(null);
65
+ }
66
+ };
67
+ return (_jsxs("div", { className: "flex h-[calc(100dvh-var(--app-header-height)-var(--core-topbar-h,0px))] max-h-[calc(100dvh-var(--app-header-height)-var(--core-topbar-h,0px))] min-h-0 w-full flex-col gap-4 overflow-hidden p-6", children: [_jsxs("div", { className: "shrink-0", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Overgeslagen" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Mails die niet automatisch zijn geanalyseerd, met de reden. Klopt het niet? Gebruik \"Toch analyseren\"." })] }), waiting > 0 && (_jsxs("div", { className: "shrink-0 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800", children: [waiting, " mail(s) wachten op analyse, omdat de limiet is bereikt of automatisch analyseren uit staat."] })), items.length > 0 && (_jsx(MailboxFilterBar, { search: search, onSearchChange: setSearch, period: period, onPeriodChange: setPeriod, sortOrder: sortOrder, onSortOrderChange: setSortOrder, filteredCount: filtered.length, totalCount: items.length, countLabel: "mails", onClear: () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); }, hasActiveFilters: !!search || period !== "all" || sortOrder !== "date-desc" })), _jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain [scrollbar-gutter:stable]", children: isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Laden..." })) : error ? (_jsx("p", { className: "text-destructive text-sm", children: "De lijst kon niet worden opgehaald." })) : filtered.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen overgeslagen mails." })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "p-0", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Afzender" }), _jsx(TableHead, { children: "Bericht" }), _jsx(TableHead, { children: "Ontvangen" }), _jsx(TableHead, { children: "Reden" }), _jsx(TableHead, {})] }) }), _jsx(TableBody, { children: paged.map((m) => {
68
+ const st = statusOf(m);
69
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm", children: m.from_name || m.from_email || "Onbekend" }), _jsxs(TableCell, { className: "max-w-sm", children: [_jsx("div", { className: "truncate text-sm font-medium", children: m.subject || "(geen onderwerp)" }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: m.preview || "" })] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: m.received_at ? new Date(m.received_at).toLocaleDateString("nl-NL") : "-" }), _jsxs(TableCell, { className: "max-w-xs", children: [_jsx(Badge, { variant: "outline", className: st.tone === "amber" ? "border-amber-300 bg-amber-50 text-amber-800" : "", children: st.label }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: m.analysis_note || m.filter_reason })] }), _jsx(TableCell, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: busyId === m.id, onClick: () => run(m.id), children: busyId === m.id ? "Bezig…" : m.analysis_state === "wacht" ? "Nu analyseren" : "Toch analyseren" }) })] }, m.id));
70
+ }) })] }), _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }) })) })] }));
71
+ }
@@ -0,0 +1,2 @@
1
+ export declare function MailboxFilterSettingsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxFilterSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxFilterSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/MailboxFilterSettingsCard.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,yBAAyB,gCAyHxC"}
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Label, Textarea, Input, Switch, toast, } from "@flowselections/core";
6
+ import { getMailboxFilterSettings, saveMailboxFilterSettings, countLikelyFakeProposals, } from "../../lib/mailbox-filter-settings.functions.js";
7
+ const LISTS = [
8
+ { key: "order_keywords", label: "Bestelwoorden", help: "Eén per regel. Woorden die op een bestelling wijzen (NL, EN, DE)." },
9
+ { key: "order_patterns", label: "Bestelpatronen", help: "Eén per regel. Zoekpatronen, bijvoorbeeld een aantal gevolgd door fust of stuks." },
10
+ { key: "negative_keywords", label: "Woorden voor nieuwsbrieven en bonnen", help: "Komt een van deze woorden voor bij een onbekende afzender, dan is het geen bestelling." },
11
+ { key: "skip_senders", label: "Afzenders overslaan", help: "Eén per regel. Een volledig adres, of een deel zoals noreply." },
12
+ { key: "skip_domains", label: "Domeinen overslaan", help: "Eén per regel, bijvoorbeeld een webwinkel of vervoersdienst." },
13
+ ];
14
+ const NUMBERS = [
15
+ { key: "max_ai_per_day", label: "Maximaal aantal AI-analyses per dag" },
16
+ { key: "max_ai_per_month", label: "Maximaal aantal AI-analyses per maand" },
17
+ { key: "min_signals_unknown", label: "Benodigde bestel-signalen bij een onbekende afzender" },
18
+ { key: "max_candidates", label: "Aantal productkandidaten voor de AI" },
19
+ { key: "max_retry_attempts", label: "Pogingen per analyse bij een storing" },
20
+ { key: "max_retries_per_run", label: "Herstelpogingen per ronde" },
21
+ { key: "max_agent_reparse", label: "Automatisch opnieuw analyseren na wijziging AI (0 = alleen via knop)" },
22
+ { key: "duplicate_days", label: "Dagen terugkijken voor 'Mogelijk dubbel'" },
23
+ { key: "conf_green", label: "Drempel groen (0 tot 1)", step: "0.05" },
24
+ { key: "conf_orange", label: "Drempel oranje (0 tot 1)", step: "0.05" },
25
+ ];
26
+ export function MailboxFilterSettingsCard() {
27
+ const qc = useQueryClient();
28
+ const fetchSettings = useServerFn(getMailboxFilterSettings);
29
+ const save = useServerFn(saveMailboxFilterSettings);
30
+ const fetchCount = useServerFn(countLikelyFakeProposals);
31
+ const { data, error } = useQuery({ queryKey: ["mailbox-filter-settings"], queryFn: () => fetchSettings() });
32
+ const [form, setForm] = useState(null);
33
+ const [count, setCount] = useState(null);
34
+ const [counting, setCounting] = useState(false);
35
+ useEffect(() => {
36
+ if (!data?.settings || form)
37
+ return;
38
+ const s = data.settings;
39
+ const f = { ...s, auto_analyze: data.auto_analyze };
40
+ for (const l of LISTS)
41
+ f[l.key] = (Array.isArray(s[l.key]) ? s[l.key] : []).join("\n");
42
+ setForm(f);
43
+ }, [data, form]);
44
+ const mut = useMutation({
45
+ mutationFn: async () => {
46
+ const f = form;
47
+ const payload = {
48
+ domain_match: !!f.domain_match,
49
+ analyze_unknown_with_signals: !!f.analyze_unknown_with_signals,
50
+ auto_analyze: !!f.auto_analyze,
51
+ };
52
+ for (const l of LISTS)
53
+ payload[l.key] = String(f[l.key] ?? "").split("\n").map((x) => x.trim()).filter(Boolean);
54
+ for (const n of NUMBERS)
55
+ payload[n.key] = Number(f[n.key]);
56
+ return save({ data: payload });
57
+ },
58
+ onSuccess: () => {
59
+ toast.success("Instellingen opgeslagen");
60
+ void qc.invalidateQueries({ queryKey: ["mailbox-filter-settings"] });
61
+ },
62
+ onError: (e) => toast.error(e?.message ?? "Opslaan mislukt"),
63
+ });
64
+ const set = (k, v) => setForm((p) => ({ ...(p ?? {}), [k]: v }));
65
+ const u = data?.usage;
66
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Voorfilter en kosten bestelmails" }), _jsx(CardDescription, { children: "Bepaalt welke mails de AI leest en hoeveel analyses er maximaal mogen. Mails die geen bestelling zijn, kosten geen AI-analyse." })] }), _jsxs(CardContent, { className: "space-y-5", children: [error && _jsx("p", { className: "text-sm text-destructive", children: "De instellingen konden niet worden geladen." }), u && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm grid gap-1 sm:grid-cols-2", children: [_jsxs("div", { children: ["AI-analyses deze maand: ", _jsx("strong", { children: u.month_analyses }), form ? ` van ${form.max_ai_per_month}` : ""] }), _jsxs("div", { children: ["Vandaag: ", _jsx("strong", { children: u.today_analyses }), form ? ` van ${form.max_ai_per_day}` : ""] }), _jsxs("div", { children: ["Tokens deze maand: ", Number(u.input_tokens).toLocaleString("nl-NL"), " in \u00B7 ", Number(u.output_tokens).toLocaleString("nl-NL"), " uit"] }), _jsxs("div", { children: ["AI-aanroepen volgens het AI-logboek: ", u.ai_calls_month] }), u.test_analyses > 0 && _jsxs("div", { children: ["Waarvan testset: ", u.test_analyses] }), u.waiting > 0 && _jsxs("div", { className: "text-amber-700", children: ["Wachten op analyse: ", u.waiting] })] })), form && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "text-sm", children: "Automatisch analyseren" }), _jsx(Switch, { checked: !!form.auto_analyze, onCheckedChange: (v) => set("auto_analyze", v) })] }), _jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "text-sm", children: "Onbekende afzender met bestel-signalen ook analyseren" }), _jsx(Switch, { checked: !!form.analyze_unknown_with_signals, onCheckedChange: (v) => set("analyze_unknown_with_signals", v) })] }), _jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "text-sm", children: "Klant herkennen op domein van het e-mailadres" }), _jsx(Switch, { checked: !!form.domain_match, onCheckedChange: (v) => set("domain_match", v) })] })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: NUMBERS.map((n) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: n.label }), _jsx(Input, { type: "number", step: n.step ?? "1", value: form[n.key] ?? "", onChange: (e) => set(n.key, e.target.value) })] }, n.key))) }), LISTS.map((l) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: l.label }), _jsx(Textarea, { rows: 4, className: "font-mono text-xs", value: form[l.key] ?? "", onChange: (e) => set(l.key, e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: l.help })] }, l.key))), _jsx(Button, { onClick: () => mut.mutate(), disabled: mut.isPending, children: mut.isPending ? "Opslaan..." : "Opslaan" })] })), _jsxs("div", { className: "border-t pt-4 space-y-2", children: [_jsx(Label, { children: "Bestaande voorstellen tellen" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Telt hoeveel openstaande voorstellen het filter nu als \"geen bestelling\" zou zien. Er wordt niets verborgen of verwijderd." }), _jsx(Button, { variant: "outline", disabled: counting, onClick: async () => {
67
+ setCounting(true);
68
+ try {
69
+ setCount(await fetchCount());
70
+ }
71
+ catch (e) {
72
+ toast.error(e?.message ?? "Tellen mislukt");
73
+ }
74
+ finally {
75
+ setCounting(false);
76
+ }
77
+ }, children: counting ? "Tellen…" : "Tellen" }), count && (_jsxs("div", { className: "text-sm", children: [count.total, " openstaand \u00B7 ", count.known, " van een bekende klant \u00B7 ", count.wouldSkip, " zou het filter overslaan \u00B7 ", count.withSignals, " onbekend met bestel-signalen"] }))] })] })] }));
78
+ }
@@ -0,0 +1,2 @@
1
+ export declare function MailboxTestSetCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxTestSetCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxTestSetCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/MailboxTestSetCard.tsx"],"names":[],"mappings":"AASA,wBAAgB,kBAAkB,gCAkJjC"}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Badge, Input, Label, Textarea, Switch, toast, } from "@flowselections/core";
6
+ import { listTestCases, runTestCase, saveTestCase, deleteTestCase } from "../../lib/mailbox-filter-settings.functions.js";
7
+ export function MailboxTestSetCard() {
8
+ const qc = useQueryClient();
9
+ const fetchCases = useServerFn(listTestCases);
10
+ const runOne = useServerFn(runTestCase);
11
+ const save = useServerFn(saveTestCase);
12
+ const remove = useServerFn(deleteTestCase);
13
+ const { data } = useQuery({ queryKey: ["mailbox-test-cases"], queryFn: () => fetchCases() });
14
+ const cases = Array.isArray(data?.items) ? data.items : [];
15
+ const [running, setRunning] = useState(null);
16
+ const [summary, setSummary] = useState(null);
17
+ const [edit, setEdit] = useState(null);
18
+ const [openId, setOpenId] = useState(null);
19
+ const runAll = async () => {
20
+ let passed = 0, analyses = 0, tokens = 0;
21
+ for (const c of cases) {
22
+ setRunning(c.id);
23
+ try {
24
+ const r = await runOne({ data: { id: c.id } });
25
+ if (r?.passed)
26
+ passed++;
27
+ if (r?.analyzed)
28
+ analyses++;
29
+ tokens += Number(r?.tokens?.input ?? 0) + Number(r?.tokens?.output ?? 0);
30
+ }
31
+ catch (e) {
32
+ toast.error(`${c.name}: ${e?.message ?? "mislukt"}`);
33
+ }
34
+ }
35
+ setRunning(null);
36
+ setSummary({ passed, total: cases.length, analyses, tokens });
37
+ void qc.invalidateQueries({ queryKey: ["mailbox-test-cases"] });
38
+ };
39
+ const saveEdit = async () => {
40
+ try {
41
+ const expected = JSON.parse(edit.expectedText || "{}");
42
+ await save({
43
+ data: {
44
+ id: edit.id,
45
+ name: edit.name,
46
+ subject: edit.subject ?? "",
47
+ from_email: edit.from_email ?? "",
48
+ body_text: edit.body_text ?? "",
49
+ has_attachment: !!edit.has_attachment,
50
+ expected,
51
+ },
52
+ });
53
+ setEdit(null);
54
+ void qc.invalidateQueries({ queryKey: ["mailbox-test-cases"] });
55
+ toast.success("Voorbeeld opgeslagen");
56
+ }
57
+ catch (e) {
58
+ toast.error(e?.message?.includes("JSON") ? "De verwachte uitkomst is geen geldige invoer." : e?.message ?? "Opslaan mislukt");
59
+ }
60
+ };
61
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Testset bestelmails" }), _jsx(CardDescription, { children: "Voorbeeldmails met de verwachte uitkomst. \"Testset draaien\" doorloopt filter en uitlezen, maar maakt geen voorstellen of orders aan." })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: runAll, disabled: !!running || cases.length === 0, children: running ? "Bezig…" : "Testset draaien" }), _jsx(Button, { variant: "outline", onClick: () => setEdit({ name: "", subject: "", from_email: "", body_text: "", has_attachment: false, expectedText: '{"is_order": true}' }), children: "Voorbeeld toevoegen" })] }), summary && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm", children: [summary.passed, " van ", summary.total, " goed \u00B7 ", summary.analyses, " AI-analyses \u00B7 ", summary.tokens.toLocaleString("nl-NL"), " tokens"] })), _jsx("div", { className: "divide-y rounded-md border", children: cases.map((c) => {
62
+ const r = c.last_result;
63
+ return (_jsxs("div", { className: "p-3 text-sm space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("button", { type: "button", className: "text-left font-medium", onClick: () => setOpenId(openId === c.id ? null : c.id), children: c.name }), _jsxs("div", { className: "flex items-center gap-2", children: [running === c.id ? (_jsx(Badge, { variant: "outline", children: "Bezig\u2026" })) : r ? (_jsx(Badge, { variant: "outline", className: r.passed ? "border-emerald-300 bg-emerald-50 text-emerald-800" : "border-destructive/40 text-destructive", children: r.passed ? "Goed" : "Fout" })) : null, _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setEdit({ ...c, expectedText: JSON.stringify(c.expected ?? {}, null, 2) }), children: "Wijzigen" })] })] }), r && _jsxs("div", { className: "text-xs text-muted-foreground", children: [r.filter?.reason, r.analyzed ? ` · ${Number(r.tokens?.input ?? 0) + Number(r.tokens?.output ?? 0)} tokens` : " · geen AI-analyse"] }), openId === c.id && r && Array.isArray(r.checks) && (_jsx("ul", { className: "text-xs space-y-0.5 pt-1", children: r.checks.map((k, i) => (_jsxs("li", { className: k.ok ? "text-emerald-700" : "text-destructive", children: [k.ok ? "Goed" : "Fout", " \u2014 ", k.field, ": verwacht ", JSON.stringify(k.expected), ", gevonden ", JSON.stringify(k.actual)] }, i))) }))] }, c.id));
64
+ }) }), edit && (_jsxs("div", { className: "rounded-md border p-3 space-y-2", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { value: edit.name, onChange: (e) => setEdit({ ...edit, name: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Afzender" }), _jsx(Input, { value: edit.from_email, onChange: (e) => setEdit({ ...edit, from_email: e.target.value }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Onderwerp" }), _jsx(Input, { value: edit.subject, onChange: (e) => setEdit({ ...edit, subject: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Tekst" }), _jsx(Textarea, { rows: 6, value: edit.body_text, onChange: (e) => setEdit({ ...edit, body_text: e.target.value }) })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Switch, { checked: !!edit.has_attachment, onCheckedChange: (v) => setEdit({ ...edit, has_attachment: v }) }), " Met bijlage"] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Verwachte uitkomst" }), _jsx(Textarea, { rows: 6, className: "font-mono text-xs", value: edit.expectedText, onChange: (e) => setEdit({ ...edit, expectedText: e.target.value }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Bijvoorbeeld: is_order, known_sender, delivery_date, reference, en lines met quantity, unit, plants en unit_price." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: saveEdit, children: "Opslaan" }), _jsx(Button, { variant: "outline", onClick: () => setEdit(null), children: "Annuleren" }), edit.id && (_jsx(Button, { variant: "destructive", onClick: async () => {
65
+ await remove({ data: { id: edit.id } });
66
+ setEdit(null);
67
+ void qc.invalidateQueries({ queryKey: ["mailbox-test-cases"] });
68
+ }, children: "Verwijderen" }))] })] }))] })] }));
69
+ }
@@ -3,6 +3,7 @@ export * from "./_core-safelist.js";
3
3
  export { MailboxProposalsPage } from "./components/MailboxProposalsPage.js";
4
4
  export { SearchableSelect } from "./components/SearchableSelect.js";
5
5
  export { MailboxProcessedPage } from "./components/MailboxProcessedPage.js";
6
+ export { MailboxSkippedPage } from "./components/MailboxSkippedPage.js";
6
7
  export { EmailPreview } from "./components/EmailPreview.js";
7
8
  export { TablePagination } from "./components/TablePagination.js";
8
9
  export { MailboxFilterBar } from "./components/MailboxFilterBar.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,eAAO,MAAM,mBAAmB,EAAE,UAmBjC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAMvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,eAAO,MAAM,mBAAmB,EAAE,UAsBjC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -1,20 +1,23 @@
1
1
  // ============================================================================
2
2
  // src/index.ts — MODULE CONTRACT (Mailbox bestellingen)
3
3
  // ============================================================================
4
- import { Mail, ClipboardCheck, CheckCircle2 } from "lucide-react";
4
+ import { Mail, ClipboardCheck, CheckCircle2, MailX } from "lucide-react";
5
5
  import { MailboxOrderTemplateCard } from "./components/settings/MailboxOrderTemplateCard.js";
6
6
  import { MailboxAiPromptCard } from "./components/settings/MailboxAiPromptCard.js";
7
+ import { MailboxFilterSettingsCard } from "./components/settings/MailboxFilterSettingsCard.js";
8
+ import { MailboxTestSetCard } from "./components/settings/MailboxTestSetCard.js";
7
9
  export * from "./_core-safelist.js";
8
10
  export { MailboxProposalsPage } from "./components/MailboxProposalsPage.js";
9
11
  export { SearchableSelect } from "./components/SearchableSelect.js";
10
12
  export { MailboxProcessedPage } from "./components/MailboxProcessedPage.js";
13
+ export { MailboxSkippedPage } from "./components/MailboxSkippedPage.js";
11
14
  export { EmailPreview } from "./components/EmailPreview.js";
12
15
  export { TablePagination } from "./components/TablePagination.js";
13
16
  export { MailboxFilterBar } from "./components/MailboxFilterBar.js";
14
17
  export const mailboxOrdersModule = {
15
18
  id: "mailbox_orders",
16
19
  name: "Mailbox bestellingen",
17
- version: "1.2.0",
20
+ version: "1.3.0",
18
21
  nav: {
19
22
  label: "Mailbox",
20
23
  href: "/mailbox-orders/voorstellen",
@@ -22,10 +25,13 @@ export const mailboxOrdersModule = {
22
25
  children: [
23
26
  { label: "Voorstellen", href: "/mailbox-orders/voorstellen", icon: ClipboardCheck },
24
27
  { label: "Verwerkt", href: "/mailbox-orders/verwerkt", icon: CheckCircle2 },
28
+ { label: "Overgeslagen", href: "/mailbox-orders/overgeslagen", icon: MailX },
25
29
  ],
26
30
  },
27
31
  settingsCards: [
28
32
  { component: MailboxOrderTemplateCard, order: 20 },
29
33
  { component: MailboxAiPromptCard, order: 25 },
34
+ { component: MailboxFilterSettingsCard, order: 26 },
35
+ { component: MailboxTestSetCard, order: 27 },
30
36
  ],
31
37
  };