@flowselections/mailbox-orders 1.0.1

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 (69) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/MailboxInboxPage.d.ts +2 -0
  5. package/dist-lib/components/MailboxInboxPage.d.ts.map +1 -0
  6. package/dist-lib/components/MailboxInboxPage.js +61 -0
  7. package/dist-lib/components/MailboxProcessedPage.d.ts +2 -0
  8. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -0
  9. package/dist-lib/components/MailboxProcessedPage.js +21 -0
  10. package/dist-lib/components/MailboxProposalsPage.d.ts +2 -0
  11. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -0
  12. package/dist-lib/components/MailboxProposalsPage.js +198 -0
  13. package/dist-lib/components/SearchableSelect.d.ts +18 -0
  14. package/dist-lib/components/SearchableSelect.d.ts.map +1 -0
  15. package/dist-lib/components/SearchableSelect.js +20 -0
  16. package/dist-lib/components/settings/ImapAccountsCard.d.ts +2 -0
  17. package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +1 -0
  18. package/dist-lib/components/settings/ImapAccountsCard.js +136 -0
  19. package/dist-lib/components/settings/ImapProfilesCard.d.ts +2 -0
  20. package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +1 -0
  21. package/dist-lib/components/settings/ImapProfilesCard.js +101 -0
  22. package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts +2 -0
  23. package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts.map +1 -0
  24. package/dist-lib/components/settings/MailboxOrderTemplateCard.js +98 -0
  25. package/dist-lib/components/settings/MailboxSettingsCard.d.ts +2 -0
  26. package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +1 -0
  27. package/dist-lib/components/settings/MailboxSettingsCard.js +85 -0
  28. package/dist-lib/index.d.ts +12 -0
  29. package/dist-lib/index.d.ts.map +1 -0
  30. package/dist-lib/index.js +34 -0
  31. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  32. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  33. package/dist-lib/integrations/supabase/auth-attacher.js +15 -0
  34. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2978 -0
  35. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  36. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  37. package/dist-lib/integrations/supabase/client.d.ts +2974 -0
  38. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  39. package/dist-lib/integrations/supabase/client.js +13 -0
  40. package/dist-lib/integrations/supabase/client.server.d.ts +2974 -0
  41. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  42. package/dist-lib/integrations/supabase/client.server.js +30 -0
  43. package/dist-lib/integrations/supabase/types.d.ts +3119 -0
  44. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  45. package/dist-lib/integrations/supabase/types.js +8 -0
  46. package/dist-lib/lib/imap.functions.d.ts +32852 -0
  47. package/dist-lib/lib/imap.functions.d.ts.map +1 -0
  48. package/dist-lib/lib/imap.functions.js +235 -0
  49. package/dist-lib/lib/mailbox-ai.server.d.ts +32 -0
  50. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -0
  51. package/dist-lib/lib/mailbox-ai.server.js +107 -0
  52. package/dist-lib/lib/mailbox-auto.server.d.ts +9 -0
  53. package/dist-lib/lib/mailbox-auto.server.d.ts.map +1 -0
  54. package/dist-lib/lib/mailbox-auto.server.js +198 -0
  55. package/dist-lib/lib/mailbox-template.functions.d.ts +17913 -0
  56. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -0
  57. package/dist-lib/lib/mailbox-template.functions.js +106 -0
  58. package/dist-lib/lib/mailbox.functions.d.ts +32888 -0
  59. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -0
  60. package/dist-lib/lib/mailbox.functions.js +334 -0
  61. package/dist-lib/lib/utils.d.ts +3 -0
  62. package/dist-lib/lib/utils.d.ts.map +1 -0
  63. package/dist-lib/lib/utils.js +5 -0
  64. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  65. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  66. package/dist-lib/lib/validationSchemas.js +25 -0
  67. package/dist-lib/styles.css +1 -0
  68. package/package.json +96 -0
  69. package/public/flowselections-assets/template-module/README.md +15 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_core-safelist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_core-safelist.d.ts","sourceRoot":"","sources":["../src/_core-safelist.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ // Forceert Tailwind classes die uit @flowselections/core komen maar niet
2
+ // voorkomen in de eigen broncode van deze module.
3
+ // Zonder dit bestand worden deze classes weggeoptimaliseerd op de
4
+ // Lovable productie build.
5
+ const _safelist = [
6
+ // Layout — Sidebar en shell structuur
7
+ 'h-full', 'flex-col', 'flex-1', 'min-h-screen', 'ml-64', 'pt-16',
8
+ 'items-start', 'items-center', 'justify-between', 'border-t',
9
+ 'w-64', 'h-screen', 'h-20', 'overflow-y-auto',
10
+ // Grid — InstellingenPage
11
+ 'grid', 'lg:grid-cols-2', 'gap-6', 'p-6', 'space-y-6',
12
+ // Toggle/Switch — aan/uit zichtbaarheid
13
+ 'data-[state=checked]:bg-primary', 'data-[state=unchecked]:bg-input',
14
+ ];
15
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function MailboxInboxPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxInboxPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxInboxPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxInboxPage.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,gBAAgB,gCAoH/B"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { useServerFn } from "@tanstack/react-start";
4
+ import { Button, Badge, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
5
+ import { Mail, RefreshCw, Sparkles } from "lucide-react";
6
+ import { listMessages, pollMailbox, parseMessage } from "../lib/mailbox.functions";
7
+ const STATUS_VARIANT = {
8
+ new: "secondary",
9
+ parsing: "outline",
10
+ parsed: "default",
11
+ failed: "destructive",
12
+ ignored: "outline",
13
+ converted: "default",
14
+ };
15
+ export function MailboxInboxPage() {
16
+ const qc = useQueryClient();
17
+ const fetchList = useServerFn(listMessages);
18
+ const fetchPoll = useServerFn(pollMailbox);
19
+ const fetchParse = useServerFn(parseMessage);
20
+ const { data, isLoading } = useQuery({
21
+ queryKey: ["mailbox-messages"],
22
+ queryFn: () => fetchList(),
23
+ });
24
+ const pollMut = useMutation({
25
+ mutationFn: () => fetchPoll(),
26
+ onSuccess: (res) => {
27
+ if (res?.ok) {
28
+ const ins = res.inserted ?? 0;
29
+ const app = res.autoApproved ?? 0;
30
+ if (ins === 0)
31
+ toast.success("Geen nieuwe e-mails");
32
+ else if (app > 0)
33
+ toast.success(`${ins} nieuwe e-mail(s) — ${app} automatisch omgezet in bestelling`);
34
+ else
35
+ toast.success(`${ins} nieuwe e-mail(s) opgehaald`);
36
+ qc.invalidateQueries({ queryKey: ["mailbox-messages"] });
37
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
38
+ }
39
+ else {
40
+ toast.error(res?.error ?? "Ophalen mislukt");
41
+ }
42
+ },
43
+ onError: (e) => toast.error(e?.message ?? "Ophalen mislukt"),
44
+ });
45
+ const parseMut = useMutation({
46
+ mutationFn: (messageId) => fetchParse({ data: { messageId } }),
47
+ onSuccess: (res) => {
48
+ if (res?.ok) {
49
+ toast.success("E-mail geparsed naar voorstel");
50
+ qc.invalidateQueries({ queryKey: ["mailbox-messages"] });
51
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
52
+ }
53
+ else {
54
+ toast.error(res?.error ?? "Parsen mislukt");
55
+ }
56
+ },
57
+ onError: (e) => toast.error(e?.message ?? "Parsen mislukt"),
58
+ });
59
+ const items = Array.isArray(data?.items) ? data.items : [];
60
+ return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h1", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Mail, { className: "h-6 w-6 text-primary" }), " Inbox"] }), _jsx("p", { className: "text-muted-foreground text-sm mt-1", children: "Binnenkomende bestel-e-mails uit de mailbox" })] }), _jsxs(Button, { onClick: () => pollMut.mutate(), disabled: pollMut.isPending, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${pollMut.isPending ? "animate-spin" : ""}` }), "Nu ophalen"] })] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: isLoading ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Nog geen e-mails. Klik \"Nu ophalen\" om de mailbox te controleren." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Afzender" }), _jsx(TableHead, { children: "Onderwerp" }), _jsx(TableHead, { children: "Ontvangen" }), _jsx(TableHead, { className: "text-right", children: "Actie" })] }) }), _jsx(TableBody, { children: items.map((m) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Badge, { variant: STATUS_VARIANT[m.status] ?? "secondary", children: m.status }) }), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm font-medium", children: m.from_name || m.from_email }), m.from_name && _jsx("div", { className: "text-xs text-muted-foreground", children: m.from_email })] }), _jsx(TableCell, { className: "max-w-md truncate", children: m.subject || "(geen onderwerp)" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: m.received_at ? new Date(m.received_at).toLocaleString("nl-NL") : "-" }), _jsx(TableCell, { className: "text-right", children: m.status !== "converted" && (_jsxs(Button, { size: "sm", variant: "outline", disabled: parseMut.isPending, onClick: () => parseMut.mutate(m.id), children: [_jsx(Sparkles, { className: "h-3 w-3 mr-1" }), m.status === "parsed" ? "Opnieuw parsen" : "Parse"] })) })] }, m.id))) })] })) }) })] }));
61
+ }
@@ -0,0 +1,2 @@
1
+ export declare function MailboxProcessedPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxProcessedPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxProcessedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProcessedPage.tsx"],"names":[],"mappings":"AAQA,wBAAgB,oBAAoB,gCAuDnC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { useServerFn } from "@tanstack/react-start";
4
+ import { Badge, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
5
+ import { listProposals } from "../lib/mailbox.functions";
6
+ export function MailboxProcessedPage() {
7
+ const fetchList = useServerFn(listProposals);
8
+ const approved = useQuery({
9
+ queryKey: ["mailbox-proposals", "approved"],
10
+ queryFn: () => fetchList({ data: { status: "approved" } }),
11
+ });
12
+ const rejected = useQuery({
13
+ queryKey: ["mailbox-proposals", "rejected"],
14
+ queryFn: () => fetchList({ data: { status: "rejected" } }),
15
+ });
16
+ const items = [
17
+ ...(Array.isArray(approved.data?.items) ? approved.data.items : []),
18
+ ...(Array.isArray(rejected.data?.items) ? rejected.data.items : []),
19
+ ].sort((a, b) => (b.reviewed_at ?? "").localeCompare(a.reviewed_at ?? ""));
20
+ return (_jsxs("div", { className: "p-6 space-y-4", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Verwerkt" }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: items.length === 0 ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Nog geen verwerkte voorstellen." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Afzender" }), _jsx(TableHead, { children: "Onderwerp" }), _jsx(TableHead, { children: "Verwerkt op" })] }) }), _jsx(TableBody, { children: items.map((p) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Badge, { variant: p.status === "approved" ? "default" : "destructive", children: p.status === "approved" ? "Goedgekeurd" : "Afgewezen" }) }), _jsx(TableCell, { children: p.mailbox_messages?.from_email ?? "-" }), _jsx(TableCell, { className: "max-w-md truncate", children: p.mailbox_messages?.subject ?? "-" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: p.reviewed_at ? new Date(p.reviewed_at).toLocaleString("nl-NL") : "-" })] }, p.id))) })] })) }) })] }));
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare function MailboxProposalsPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxProposalsPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,oBAAoB,gCAqKnC"}
@@ -0,0 +1,198 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Dialog, DialogContent, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
+ import { Check, X, Trash2, Plus, AlertTriangle } from "lucide-react";
7
+ import { listProposals, approveProposal, rejectProposal, listCustomersForPicker, listProductsForPicker, } from "../lib/mailbox.functions";
8
+ import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions";
9
+ import { SearchableSelect } from "./SearchableSelect";
10
+ export function MailboxProposalsPage() {
11
+ const qc = useQueryClient();
12
+ const fetchList = useServerFn(listProposals);
13
+ const fetchTemplate = useServerFn(listTemplateFields);
14
+ const fetchApprove = useServerFn(approveProposal);
15
+ const fetchReject = useServerFn(rejectProposal);
16
+ const fetchCustomers = useServerFn(listCustomersForPicker);
17
+ const fetchProducts = useServerFn(listProductsForPicker);
18
+ const fetchPlantTypes = useServerFn(listPlantTypesForPicker);
19
+ const fetchSuppliers = useServerFn(listSuppliersForPicker);
20
+ const [status, setStatus] = useState("pending");
21
+ const { data, isLoading } = useQuery({
22
+ queryKey: ["mailbox-proposals", status],
23
+ queryFn: () => fetchList({ data: { status } }),
24
+ });
25
+ const { data: templateData } = useQuery({
26
+ queryKey: ["mailbox-template-fields"],
27
+ queryFn: () => fetchTemplate(),
28
+ });
29
+ const { data: customersData } = useQuery({ queryKey: ["mailbox-customers"], queryFn: () => fetchCustomers() });
30
+ const { data: productsData } = useQuery({ queryKey: ["mailbox-products"], queryFn: () => fetchProducts() });
31
+ const { data: plantData } = useQuery({ queryKey: ["mailbox-plant-types"], queryFn: () => fetchPlantTypes() });
32
+ const { data: supplierData } = useQuery({ queryKey: ["mailbox-suppliers"], queryFn: () => fetchSuppliers() });
33
+ const items = Array.isArray(data?.items) ? data.items : [];
34
+ const template = Array.isArray(templateData?.items) ? templateData.items : [];
35
+ const customers = Array.isArray(customersData?.items) ? customersData.items : [];
36
+ const products = Array.isArray(productsData?.items) ? productsData.items : [];
37
+ const plantTypes = Array.isArray(plantData?.items) ? plantData.items : [];
38
+ const suppliers = Array.isArray(supplierData?.items) ? supplierData.items : [];
39
+ const [activeId, setActiveId] = useState(null);
40
+ const active = items.find((p) => p.id === activeId) ?? null;
41
+ const statusLabel = (s) => s === "approved" ? "Goedgekeurd" : s === "rejected" ? "Afgewezen" : "Nieuw";
42
+ const statusVariant = (s) => s === "approved" ? "default" : s === "rejected" ? "destructive" : "secondary";
43
+ const customerNameFor = (p) => {
44
+ const fvs = Array.isArray(p.mailbox_proposal_field_values) ? p.mailbox_proposal_field_values : [];
45
+ const fv = fvs.find((v) => v.field_key === "customer");
46
+ const id = fv?.value ?? p.matched_customer_id ?? null;
47
+ if (id) {
48
+ const c = customers.find((x) => x.id === id);
49
+ if (c?.company_name)
50
+ return c.company_name;
51
+ }
52
+ return p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
53
+ };
54
+ const orderNumberFor = (p) => `MB-${String(p.id).slice(0, 8).toUpperCase()}`;
55
+ return (_jsxs("div", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Voorstellen" }), _jsxs(Select, { value: status, onValueChange: (v) => setStatus(v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "pending", children: "Nieuw / In behandeling" }), _jsx(SelectItem, { value: "approved", children: "Goedgekeurd" }), _jsx(SelectItem, { value: "rejected", children: "Afgekeurd" })] })] })] }), isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen voorstellen in deze categorie." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Klantnaam" }), _jsx(TableHead, { children: "Orderdatum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Orderwaarde" })] }) }), _jsx(TableBody, { children: items.map((p) => {
56
+ const fvs = Array.isArray(p.mailbox_proposal_field_values) ? p.mailbox_proposal_field_values : [];
57
+ const needsReview = fvs.some((v) => v.needs_review);
58
+ const date = p.mailbox_messages?.received_at ?? p.created_at;
59
+ return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => setActiveId(p.id), children: [_jsx(TableCell, { className: "font-mono text-xs", children: orderNumberFor(p) }), _jsx(TableCell, { className: "font-medium", children: customerNameFor(p) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: date ? new Date(date).toLocaleDateString("nl-NL") : "-" }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Badge, { variant: statusVariant(p.status), children: statusLabel(p.status) }), needsReview && (_jsxs(Badge, { variant: "outline", className: "text-xs border-yellow-500 text-yellow-700", children: [_jsx(AlertTriangle, { className: "h-3 w-3 mr-1" }), "Controleren"] }))] }) }), _jsx(TableCell, { className: "text-right text-sm text-muted-foreground", children: "\u2014" })] }, p.id));
60
+ }) })] }) }) })), _jsx(Dialog, { open: !!active, onOpenChange: (o) => { if (!o)
61
+ setActiveId(null); }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] overflow-y-auto p-6", children: active && (_jsxs(_Fragment, { children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { children: [orderNumberFor(active), " \u00B7 ", customerNameFor(active)] }) }), _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers, onApprove: async (payload) => {
62
+ try {
63
+ await fetchApprove({ data: payload });
64
+ toast.success("Bestelling aangemaakt");
65
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
66
+ setActiveId(null);
67
+ }
68
+ catch (e) {
69
+ toast.error(e?.message ?? "Goedkeuren mislukt");
70
+ }
71
+ }, onReject: async (reason) => {
72
+ try {
73
+ await fetchReject({ data: { proposalId: active.id, reason } });
74
+ toast.success("Voorstel afgewezen");
75
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
76
+ setActiveId(null);
77
+ }
78
+ catch (e) {
79
+ toast.error(e?.message ?? "Afwijzen mislukt");
80
+ }
81
+ } }, active.id)] })) }) })] }));
82
+ }
83
+ function ProposalEditor({ proposal, template, customers, products, plantTypes, suppliers, onApprove, onReject, }) {
84
+ const msg = proposal.mailbox_messages ?? {};
85
+ const fieldValues = Array.isArray(proposal.mailbox_proposal_field_values)
86
+ ? proposal.mailbox_proposal_field_values : [];
87
+ // Map field_key -> current row
88
+ const fvMap = useMemo(() => {
89
+ const m = {};
90
+ fieldValues.forEach((v) => { m[v.field_key] = v; });
91
+ return m;
92
+ }, [fieldValues]);
93
+ const visibleFields = template.filter((f) => f.visible);
94
+ const [values, setValues] = useState(() => {
95
+ const init = {};
96
+ visibleFields.forEach((f) => {
97
+ init[f.key] = fvMap[f.key]?.value ?? f.default_value ?? null;
98
+ });
99
+ return init;
100
+ });
101
+ const initialLines = Array.isArray(proposal.mailbox_proposal_lines)
102
+ ? proposal.mailbox_proposal_lines.map((l) => ({
103
+ id: l.id,
104
+ product_id: l.product_id,
105
+ raw_product_text: l.raw_product_text ?? "",
106
+ quantity: l.quantity,
107
+ unit: l.unit,
108
+ }))
109
+ : [];
110
+ const [lines, setLines] = useState(initialLines.length ? initialLines : [
111
+ { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" },
112
+ ]);
113
+ const customerVal = values["customer"];
114
+ const deliveryVal = values["delivery_date"];
115
+ const notesVal = values["notes"];
116
+ const setVal = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
117
+ const handleApprove = () => {
118
+ const missing = template.filter((f) => f.required && (values[f.key] === null || values[f.key] === undefined || values[f.key] === ""));
119
+ if (missing.length > 0) {
120
+ toast.error(`Verplichte velden ontbreken: ${missing.map((m) => m.label).join(", ")}`);
121
+ return;
122
+ }
123
+ const cust = customers.find((c) => c.id === customerVal);
124
+ onApprove({
125
+ proposalId: proposal.id,
126
+ customerId: customerVal ?? null,
127
+ customerName: cust?.company_name ?? msg.from_name ?? msg.from_email ?? "Onbekend",
128
+ deliveryDate: deliveryVal || new Date(Date.now() + 86400000).toISOString().slice(0, 10),
129
+ notes: notesVal || null,
130
+ lines: lines.filter((l) => l.raw_product_text.trim() !== ""),
131
+ fieldValues: Object.entries(values).map(([field_key, value]) => ({ field_key, value })),
132
+ });
133
+ };
134
+ return (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: msg.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [msg.from_name, " <", msg.from_email, "> \u00B7", " ", msg.received_at ? new Date(msg.received_at).toLocaleString("nl-NL") : ""] })] }), _jsx(CardContent, { children: _jsx("pre", { className: "text-sm whitespace-pre-wrap font-sans max-h-[60vh] overflow-y-auto", children: msg.body_text || "(geen tekst)" }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4", children: [visibleFields.map((field) => (_jsx(DynamicField, { field: field, value: values[field.key], meta: fvMap[field.key], onChange: (v) => setVal(field.key, v), customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers }, field.key))), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bestelregels" }), lines.map((line, idx) => (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-start", children: [_jsxs("div", { className: "col-span-6", children: [_jsx(SearchableSelect, { value: line.product_id, placeholder: "Kies product...", clearLabel: "\u2014 Handmatig \u2014", options: products.map((p) => ({ value: p.id, label: p.product, hint: p.unit })), onChange: (v) => {
135
+ const next = [...lines];
136
+ if (v === null)
137
+ next[idx] = { ...next[idx], product_id: null };
138
+ else {
139
+ const p = products.find((x) => x.id === v);
140
+ next[idx] = {
141
+ ...next[idx], product_id: v,
142
+ raw_product_text: p?.product ?? next[idx].raw_product_text,
143
+ unit: next[idx].unit ?? p?.unit ?? "stuks",
144
+ };
145
+ }
146
+ setLines(next);
147
+ } }), _jsx(Input, { className: "mt-1", value: line.raw_product_text, onChange: (e) => {
148
+ const next = [...lines];
149
+ next[idx] = { ...next[idx], raw_product_text: e.target.value };
150
+ setLines(next);
151
+ }, placeholder: "Productomschrijving" })] }), _jsx(Input, { className: "col-span-2", type: "number", value: line.quantity ?? "", onChange: (e) => {
152
+ const next = [...lines];
153
+ next[idx] = { ...next[idx], quantity: e.target.value ? Number(e.target.value) : null };
154
+ setLines(next);
155
+ }, placeholder: "Aantal" }), _jsx(Input, { className: "col-span-3", value: line.unit ?? "", onChange: (e) => {
156
+ const next = [...lines];
157
+ next[idx] = { ...next[idx], unit: e.target.value };
158
+ setLines(next);
159
+ }, placeholder: "Eenheid" }), _jsx(Button, { variant: "ghost", size: "icon", className: "col-span-1", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, idx))), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", onClick: handleApprove, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Goedkeuren"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] })] })] })] }));
160
+ }
161
+ function DynamicField({ field, value, meta, onChange, customers, products, plantTypes, suppliers, }) {
162
+ const reviewClass = meta?.needs_review ? "border-yellow-500 ring-1 ring-yellow-500/40 rounded-md p-2" : "";
163
+ const label = (_jsxs(Label, { className: "flex items-center gap-2", children: [field.label, field.required && _jsx("span", { className: "text-destructive", children: "*" }), meta?.needs_review && (_jsx(Badge, { variant: "outline", className: "text-xs border-yellow-500 text-yellow-700", children: "Controleren" })), typeof meta?.ai_confidence === "number" && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [Math.round(meta.ai_confidence * 100), "%"] }))] }));
164
+ const renderControl = () => {
165
+ switch (field.field_type) {
166
+ case "text":
167
+ return _jsx(Input, { value: value ?? "", onChange: (e) => onChange(e.target.value) });
168
+ case "number":
169
+ return _jsx(Input, { type: "number", value: value ?? "", onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null) });
170
+ case "date":
171
+ return _jsx(Input, { type: "date", value: value ?? "", onChange: (e) => onChange(e.target.value || null) });
172
+ case "boolean":
173
+ return _jsx("div", { children: _jsx(Switch, { checked: !!value, onCheckedChange: onChange }) });
174
+ case "note":
175
+ return _jsx(Textarea, { rows: 2, value: value ?? "", onChange: (e) => onChange(e.target.value) });
176
+ case "select":
177
+ return (_jsxs(Select, { value: value ?? "__none", onValueChange: (v) => onChange(v === "__none" ? null : v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none", children: "\u2014" }), (field.options ?? []).map((o) => _jsx(SelectItem, { value: o.value, children: o.label }, o.value))] })] }));
178
+ case "multiselect": {
179
+ const arr = Array.isArray(value) ? value : [];
180
+ return (_jsx("div", { className: "flex flex-wrap gap-1", children: (field.options ?? []).map((o) => {
181
+ const on = arr.includes(o.value);
182
+ return (_jsx(Badge, { variant: on ? "default" : "outline", className: "cursor-pointer", onClick: () => onChange(on ? arr.filter((x) => x !== o.value) : [...arr, o.value]), children: o.label }, o.value));
183
+ }) }));
184
+ }
185
+ case "customer":
186
+ return (_jsx(SearchableSelect, { value: value ?? null, placeholder: "Kies klant...", options: customers.map((c) => ({ value: c.id, label: c.company_name, hint: c.email })), onChange: onChange }));
187
+ case "product":
188
+ return (_jsx(SearchableSelect, { value: value ?? null, placeholder: "Kies product...", options: products.map((p) => ({ value: p.id, label: p.product })), onChange: onChange }));
189
+ case "plant_type":
190
+ return (_jsx(SearchableSelect, { value: value ?? null, placeholder: "Kies plantsoort...", options: plantTypes.map((p) => ({ value: p.id, label: p.name })), onChange: onChange }));
191
+ case "supplier":
192
+ return (_jsx(SearchableSelect, { value: value ?? null, placeholder: "Kies leverancier...", options: suppliers.map((s) => ({ value: s.id, label: s.name })), onChange: onChange }));
193
+ default:
194
+ return _jsx(Input, { value: value ?? "", onChange: (e) => onChange(e.target.value) });
195
+ }
196
+ };
197
+ return (_jsxs("div", { className: "space-y-1", children: [label, _jsx("div", { className: reviewClass, children: renderControl() })] }));
198
+ }
@@ -0,0 +1,18 @@
1
+ export interface SearchableOption {
2
+ value: string;
3
+ label: string;
4
+ hint?: string | null;
5
+ }
6
+ interface SearchableSelectProps {
7
+ value: string | null;
8
+ options: SearchableOption[];
9
+ placeholder?: string;
10
+ emptyLabel?: string;
11
+ allowClear?: boolean;
12
+ clearLabel?: string;
13
+ onChange: (value: string | null) => void;
14
+ className?: string;
15
+ }
16
+ export declare function SearchableSelect({ value, options, placeholder, emptyLabel, allowClear, clearLabel, onChange, className, }: SearchableSelectProps): import("react").JSX.Element;
17
+ export {};
18
+ //# sourceMappingURL=SearchableSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../src/components/SearchableSelect.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EAAE,OAAO,EAAE,WAAuB,EAAE,UAA6B,EACtE,UAAiB,EAAE,UAAiC,EACpD,QAAQ,EAAE,SAAS,GACpB,EAAE,qBAAqB,+BAsFvB"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Button, Input, Popover, PopoverContent, PopoverTrigger, } from "@flowselections/core";
4
+ import { Check, ChevronsUpDown, Search } from "lucide-react";
5
+ export function SearchableSelect({ value, options, placeholder = "Kies...", emptyLabel = "Geen resultaat", allowClear = true, clearLabel = "— Geen koppeling —", onChange, className, }) {
6
+ const [open, setOpen] = useState(false);
7
+ const [q, setQ] = useState("");
8
+ const selected = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value]);
9
+ const filtered = useMemo(() => {
10
+ const needle = q.trim().toLowerCase();
11
+ if (!needle)
12
+ return options.slice(0, 200);
13
+ return options
14
+ .filter((o) => o.label.toLowerCase().includes(needle) ||
15
+ (o.hint ?? "").toLowerCase().includes(needle))
16
+ .slice(0, 200);
17
+ }, [options, q]);
18
+ return (_jsxs(Popover, { open: open, onOpenChange: (o) => { setOpen(o); if (!o)
19
+ setQ(""); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, className: `w-full justify-between font-normal ${className ?? ""}`, children: [_jsx("span", { className: `truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.label ?? placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: [_jsx("div", { className: "p-2 border-b", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: q, onChange: (e) => setQ(e.target.value), placeholder: `Zoek in ${options.length} items...`, className: "pl-8 h-9" })] }) }), _jsxs("div", { className: "max-h-64 overflow-y-auto py-1", children: [allowClear && (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-center gap-2", onClick: () => { onChange(null); setOpen(false); }, children: [value === null && _jsx(Check, { className: "h-3 w-3" }), _jsx("span", { className: "text-muted-foreground italic", children: clearLabel })] })), filtered.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-sm text-muted-foreground text-center", children: emptyLabel })) : (filtered.map((o) => (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-start gap-2", onClick: () => { onChange(o.value); setOpen(false); }, children: [_jsx(Check, { className: `h-3 w-3 mt-1 shrink-0 ${value === o.value ? "" : "invisible"}` }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "truncate", children: o.label }), o.hint && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.hint })] })] }, o.value)))), filtered.length === 200 && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground text-center border-t", children: "Toon eerste 200 resultaten \u2014 verfijn je zoekopdracht" }))] })] })] }));
20
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ImapAccountsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=ImapAccountsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImapAccountsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ImapAccountsCard.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,gBAAgB,gCAwP/B"}
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label, Switch, Badge, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
4
+ import { Server, Plus, Pencil, Trash2, TestTube2, RefreshCw, CheckCircle2, PlayCircle } from "lucide-react";
5
+ import { listImapAccounts, upsertImapAccount, deleteImapAccount, testImapAccount, listImapProfiles, activateImapAccount, pollImapProfile, } from "../../lib/imap.functions";
6
+ const emptyAccount = {
7
+ id: undefined,
8
+ name: "",
9
+ email: "",
10
+ host: "",
11
+ port: 993,
12
+ username: "",
13
+ use_tls: true,
14
+ password: "",
15
+ is_active: true,
16
+ };
17
+ export function ImapAccountsCard() {
18
+ const [items, setItems] = useState([]);
19
+ const [activeAccountId, setActiveAccountId] = useState(null);
20
+ const [activeProfileId, setActiveProfileId] = useState(null);
21
+ const [loading, setLoading] = useState(true);
22
+ const [editing, setEditing] = useState(null);
23
+ const [saving, setSaving] = useState(false);
24
+ const [testingId, setTestingId] = useState(null);
25
+ const [pollingId, setPollingId] = useState(null);
26
+ const reload = async () => {
27
+ setLoading(true);
28
+ try {
29
+ const [accRes, profRes] = await Promise.all([listImapAccounts(), listImapProfiles()]);
30
+ setItems(Array.isArray(accRes?.items) ? accRes.items : []);
31
+ const profs = Array.isArray(profRes?.items) ? profRes.items : [];
32
+ const orders = profs.find((p) => p.profile_key === "mailbox_orders") ?? profs[0] ?? null;
33
+ setActiveAccountId(orders?.account_id ?? null);
34
+ setActiveProfileId(orders?.id ?? null);
35
+ }
36
+ finally {
37
+ setLoading(false);
38
+ }
39
+ };
40
+ useEffect(() => { reload(); }, []);
41
+ const handleSave = async () => {
42
+ if (!editing)
43
+ return;
44
+ setSaving(true);
45
+ try {
46
+ const saved = await upsertImapAccount({
47
+ data: {
48
+ id: editing.id,
49
+ name: editing.name,
50
+ email: editing.email || null,
51
+ host: editing.host,
52
+ port: Number(editing.port),
53
+ username: editing.username,
54
+ use_tls: editing.use_tls,
55
+ password: editing.password || undefined,
56
+ is_active: editing.is_active,
57
+ },
58
+ });
59
+ toast.success("Account opgeslagen");
60
+ // Nieuw account direct als actief markeren als er nog geen is
61
+ if (!editing.id && saved?.id && !activeAccountId) {
62
+ await activateImapAccount({ data: { account_id: saved.id } });
63
+ }
64
+ setEditing(null);
65
+ reload();
66
+ }
67
+ catch (e) {
68
+ toast.error(e?.message ?? "Opslaan mislukt");
69
+ }
70
+ finally {
71
+ setSaving(false);
72
+ }
73
+ };
74
+ const handleDelete = async (id) => {
75
+ if (!confirm("Account verwijderen?"))
76
+ return;
77
+ try {
78
+ await deleteImapAccount({ data: { id } });
79
+ toast.success("Verwijderd");
80
+ reload();
81
+ }
82
+ catch (e) {
83
+ toast.error(e?.message ?? "Verwijderen mislukt");
84
+ }
85
+ };
86
+ const handleTest = async (id) => {
87
+ setTestingId(id);
88
+ try {
89
+ const res = await testImapAccount({ data: { id } });
90
+ if (res?.ok)
91
+ toast.success("Verbinding OK");
92
+ else
93
+ toast.error(res?.error ?? "Verbinding mislukt");
94
+ reload();
95
+ }
96
+ finally {
97
+ setTestingId(null);
98
+ }
99
+ };
100
+ const handleActivate = async (id) => {
101
+ try {
102
+ await activateImapAccount({ data: { account_id: id } });
103
+ toast.success("Account geactiveerd");
104
+ reload();
105
+ }
106
+ catch (e) {
107
+ toast.error(e?.message ?? "Activeren mislukt");
108
+ }
109
+ };
110
+ const handlePoll = async () => {
111
+ if (!activeProfileId)
112
+ return;
113
+ setPollingId(activeProfileId);
114
+ try {
115
+ const res = await pollImapProfile({ data: { profile_id: activeProfileId } });
116
+ if (res?.ok)
117
+ toast.success(`${res.inserted ?? 0} nieuwe e-mail(s) opgehaald`);
118
+ else
119
+ toast.error(res?.error ?? "Ophalen mislukt");
120
+ }
121
+ finally {
122
+ setPollingId(null);
123
+ }
124
+ };
125
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Server, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "IMAP Account" })] }), _jsx(CardDescription, { children: "Beheer je e-mailaccounts en kies eenvoudig welke nu actief is voor de mailbox-bestellingen." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: handlePoll, disabled: !activeProfileId || !!pollingId, children: [_jsx(PlayCircle, { className: `h-4 w-4 mr-2 ${pollingId ? "animate-pulse" : ""}` }), "Nu ophalen"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: reload, disabled: loading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Vernieuwen"] }), _jsxs(Button, { size: "sm", onClick: () => setEditing({ ...emptyAccount }), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Account toevoegen"] })] })] }), _jsx(CardContent, { children: loading ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen accounts. Klik op \"Account toevoegen\"." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[80px]", children: "Actief" }), _jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Server" }), _jsx(TableHead, { children: "Laatste test" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: items.map((a) => {
126
+ const isActive = a.id === activeAccountId;
127
+ return (_jsxs(TableRow, { className: isActive ? "bg-primary/5" : "", children: [_jsx(TableCell, { children: isActive ? (_jsxs(Badge, { variant: "default", className: "gap-1", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Actief"] })) : (_jsx(Button, { variant: "outline", size: "sm", onClick: () => handleActivate(a.id), children: "Activeer" })) }), _jsx(TableCell, { className: "font-medium", children: a.name }), _jsx(TableCell, { className: "text-muted-foreground", children: a.email ?? a.username }), _jsxs(TableCell, { className: "text-muted-foreground", children: [a.host, ":", a.port, a.use_tls ? " · TLS" : ""] }), _jsx(TableCell, { children: a.last_tested_at ? (a.last_test_ok
128
+ ? _jsx(Badge, { variant: "default", children: "OK" })
129
+ : _jsx(Badge, { variant: "destructive", title: a.last_test_error ?? "", children: "Fout" })) : _jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex gap-1 justify-end", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => handleTest(a.id), disabled: testingId === a.id, title: "Test verbinding", children: _jsx(TestTube2, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setEditing({
130
+ id: a.id, name: a.name, email: a.email ?? "", host: a.host, port: a.port,
131
+ username: a.username, use_tls: a.use_tls,
132
+ password: "",
133
+ is_active: a.is_active,
134
+ }), title: "Bewerken", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => handleDelete(a.id), title: "Verwijderen", children: _jsx(Trash2, { className: "h-4 w-4 text-destructive" }) })] }) })] }, a.id));
135
+ }) })] })) }), _jsx(Dialog, { open: !!editing, onOpenChange: (o) => !o && setEditing(null), children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editing?.id ? "Account bewerken" : "Nieuw account" }) }), editing && (_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1 col-span-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { value: editing.name, onChange: (e) => setEditing({ ...editing, name: e.target.value }), placeholder: "bv. Orders Mailbox" })] }), _jsxs("div", { className: "space-y-1 col-span-2", children: [_jsx(Label, { children: "E-mailadres" }), _jsx(Input, { type: "email", value: editing.email, onChange: (e) => setEditing({ ...editing, email: e.target.value }), placeholder: "bestellingen@example.com" })] }), _jsxs("div", { className: "space-y-1 col-span-2", children: [_jsx(Label, { children: "IMAP-server" }), _jsx(Input, { value: editing.host, onChange: (e) => setEditing({ ...editing, host: e.target.value }), placeholder: "imap.example.com" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Poort" }), _jsx(Input, { type: "number", value: editing.port, onChange: (e) => setEditing({ ...editing, port: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-1 flex flex-col", children: [_jsx(Label, { children: "TLS / SSL" }), _jsx(Switch, { checked: editing.use_tls, onCheckedChange: (v) => setEditing({ ...editing, use_tls: v }) })] }), _jsxs("div", { className: "space-y-1 col-span-2", children: [_jsx(Label, { children: "Gebruikersnaam" }), _jsx(Input, { value: editing.username, onChange: (e) => setEditing({ ...editing, username: e.target.value }) })] }), _jsxs("div", { className: "space-y-1 col-span-2", children: [_jsx(Label, { children: "Wachtwoord" }), _jsx(Input, { type: "password", value: editing.password, onChange: (e) => setEditing({ ...editing, password: e.target.value }), placeholder: editing.id ? "Laat leeg om huidig wachtwoord te behouden" : "Voer wachtwoord in", autoComplete: "new-password" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wachtwoord wordt versleuteld opgeslagen en alleen gebruikt voor de IMAP-verbinding." })] }), _jsxs("div", { className: "col-span-2 flex items-center justify-between", children: [_jsx(Label, { children: "Actief" }), _jsx(Switch, { checked: editing.is_active, onCheckedChange: (v) => setEditing({ ...editing, is_active: v }) })] })] })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setEditing(null), children: "Annuleren" }), _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Opslaan..." : "Opslaan" })] })] }) })] }));
136
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ImapProfilesCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=ImapProfilesCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImapProfilesCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ImapProfilesCard.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,gBAAgB,gCAgN/B"}