@flowselections/mailbox-orders 1.0.13 → 1.0.14
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/MailboxFilterBar.d.ts +31 -0
- package/dist-lib/components/MailboxFilterBar.d.ts.map +1 -0
- package/dist-lib/components/MailboxFilterBar.js +67 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProcessedPage.js +41 -5
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +38 -28
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/client.d.ts +14 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +14 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +14 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +16 -4
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** State die bewaard blijft in localStorage (per pagina-sleutel). */
|
|
2
|
+
export declare function usePersistedState<T>(key: string, initial: T): readonly [T, import("react").Dispatch<import("react").SetStateAction<T>>];
|
|
3
|
+
export type PeriodValue = "all" | "today" | "7d" | "30d";
|
|
4
|
+
export type SortOrderValue = "date-desc" | "date-asc";
|
|
5
|
+
/** Hoofdletter- en accent-ongevoelig normaliseren voor zoeken. */
|
|
6
|
+
export declare function normalizeText(value: unknown): string;
|
|
7
|
+
/** Valt een datum binnen de gekozen periode? */
|
|
8
|
+
export declare function matchesPeriod(date: unknown, period: PeriodValue): boolean;
|
|
9
|
+
interface Props {
|
|
10
|
+
search: string;
|
|
11
|
+
onSearchChange: (value: string) => void;
|
|
12
|
+
period: PeriodValue;
|
|
13
|
+
onPeriodChange: (value: PeriodValue) => void;
|
|
14
|
+
sortOrder: SortOrderValue;
|
|
15
|
+
onSortOrderChange: (value: SortOrderValue) => void;
|
|
16
|
+
status?: string;
|
|
17
|
+
onStatusChange?: (value: string) => void;
|
|
18
|
+
statusOptions?: {
|
|
19
|
+
value: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}[];
|
|
22
|
+
filteredCount: number;
|
|
23
|
+
totalCount: number;
|
|
24
|
+
countLabel?: string;
|
|
25
|
+
onClear: () => void;
|
|
26
|
+
hasActiveFilters: boolean;
|
|
27
|
+
searchPlaceholder?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function MailboxFilterBar({ search, onSearchChange, period, onPeriodChange, sortOrder, onSortOrderChange, status, onStatusChange, statusOptions, filteredCount, totalCount, countLabel, onClear, hasActiveFilters, searchPlaceholder, }: Props): import("react").JSX.Element;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=MailboxFilterBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MailboxFilterBar.d.ts","sourceRoot":"","sources":["../../src/components/MailboxFilterBar.tsx"],"names":[],"mappings":"AAOA,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,6EAkB3D;AAED,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC;AACzD,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtD,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKpD;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAYzE;AAED,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,cAAc,CAAC;IAC1B,iBAAiB,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAoBD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,cAAc,EAAE,aAAa,EACrC,aAAa,EAAE,UAAU,EAAE,UAA0B,EACrD,OAAO,EAAE,gBAAgB,EACzB,iBAAkE,GACnE,EAAE,KAAK,+BA6FP"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
3
|
+
import { Search, X, ArrowDown, ArrowUp } from "lucide-react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
/** State die bewaard blijft in localStorage (per pagina-sleutel). */
|
|
6
|
+
export function usePersistedState(key, initial) {
|
|
7
|
+
const [value, setValue] = useState(initial);
|
|
8
|
+
const [hydrated, setHydrated] = useState(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
try {
|
|
11
|
+
const raw = window.localStorage.getItem(key);
|
|
12
|
+
if (raw !== null)
|
|
13
|
+
setValue(JSON.parse(raw));
|
|
14
|
+
}
|
|
15
|
+
catch { /* negeer */ }
|
|
16
|
+
setHydrated(true);
|
|
17
|
+
}, [key]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!hydrated)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
23
|
+
}
|
|
24
|
+
catch { /* negeer */ }
|
|
25
|
+
}, [key, value, hydrated]);
|
|
26
|
+
return [value, setValue];
|
|
27
|
+
}
|
|
28
|
+
/** Hoofdletter- en accent-ongevoelig normaliseren voor zoeken. */
|
|
29
|
+
export function normalizeText(value) {
|
|
30
|
+
return String(value ?? "")
|
|
31
|
+
.normalize("NFD")
|
|
32
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
33
|
+
.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
/** Valt een datum binnen de gekozen periode? */
|
|
36
|
+
export function matchesPeriod(date, period) {
|
|
37
|
+
if (period === "all")
|
|
38
|
+
return true;
|
|
39
|
+
if (!date)
|
|
40
|
+
return false;
|
|
41
|
+
const d = new Date(date);
|
|
42
|
+
if (Number.isNaN(d.getTime()))
|
|
43
|
+
return false;
|
|
44
|
+
const now = new Date();
|
|
45
|
+
if (period === "today") {
|
|
46
|
+
return d.toDateString() === now.toDateString();
|
|
47
|
+
}
|
|
48
|
+
const days = period === "7d" ? 7 : 30;
|
|
49
|
+
const from = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
50
|
+
return d.getTime() >= from.getTime();
|
|
51
|
+
}
|
|
52
|
+
function Chip({ label, onRemove }) {
|
|
53
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border bg-muted/40 px-2.5 py-1 text-xs", children: [label, _jsx("button", { type: "button", "aria-label": `${label} verwijderen`, onClick: onRemove, className: "text-muted-foreground hover:text-foreground", children: _jsx(X, { className: "h-3 w-3" }) })] }));
|
|
54
|
+
}
|
|
55
|
+
const PERIOD_LABELS = {
|
|
56
|
+
all: "Alle periodes",
|
|
57
|
+
today: "Vandaag",
|
|
58
|
+
"7d": "Laatste 7 dagen",
|
|
59
|
+
"30d": "Laatste 30 dagen",
|
|
60
|
+
};
|
|
61
|
+
export function MailboxFilterBar({ search, onSearchChange, period, onPeriodChange, sortOrder, onSortOrderChange, status, onStatusChange, statusOptions, filteredCount, totalCount, countLabel = "voorstellen", onClear, hasActiveFilters, searchPlaceholder = "Zoek op klant, afzender, onderwerp of tekst…", }) {
|
|
62
|
+
const sortIcon = sortOrder === "date-desc"
|
|
63
|
+
? _jsx(ArrowDown, { className: "h-4 w-4 text-muted-foreground" })
|
|
64
|
+
: _jsx(ArrowUp, { className: "h-4 w-4 text-muted-foreground" });
|
|
65
|
+
const statusLabel = statusOptions?.find((o) => o.value === status)?.label;
|
|
66
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 shrink-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[240px] flex-1 max-w-md", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: searchPlaceholder, className: "h-9 pl-8 pr-8" }), search && (_jsx("button", { type: "button", "aria-label": "Zoekopdracht wissen", className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground", onClick: () => onSearchChange(""), children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsxs(Select, { value: period, onValueChange: (v) => onPeriodChange(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[170px]", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle periodes" }), _jsx(SelectItem, { value: "today", children: "Vandaag" }), _jsx(SelectItem, { value: "7d", children: "Laatste 7 dagen" }), _jsx(SelectItem, { value: "30d", children: "Laatste 30 dagen" })] })] }), _jsxs(Select, { value: sortOrder, onValueChange: (v) => onSortOrderChange(v), children: [_jsxs(SelectTrigger, { className: "h-9 w-[210px] flex items-center gap-2", children: [sortIcon, _jsx(SelectValue, {})] }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "date-desc", children: "Datum: nieuwste eerst" }), _jsx(SelectItem, { value: "date-asc", children: "Datum: oudste eerst" })] })] }), statusOptions && onStatusChange && (_jsxs(Select, { value: status ?? "all", onValueChange: onStatusChange, children: [_jsx(SelectTrigger, { className: "h-9 w-[160px]", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: statusOptions.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] })), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [_jsxs("span", { className: "text-sm text-muted-foreground", children: [filteredCount, " van ", totalCount, " ", countLabel] }), hasActiveFilters && (_jsx(Button, { size: "sm", variant: "ghost", onClick: onClear, children: "Filters wissen" }))] })] }), hasActiveFilters && (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [search && _jsx(Chip, { label: `Zoeken: ${search}`, onRemove: () => onSearchChange("") }), period !== "all" && _jsx(Chip, { label: `Periode: ${PERIOD_LABELS[period]}`, onRemove: () => onPeriodChange("all") }), status && status !== "all" && onStatusChange && (_jsx(Chip, { label: `Status: ${statusLabel ?? status}`, onRemove: () => onStatusChange("all") })), sortOrder !== "date-desc" && (_jsx(Chip, { label: "Sortering: oudste eerst", onRemove: () => onSortOrderChange("date-desc") })), _jsx("button", { type: "button", onClick: onClear, className: "text-xs text-primary underline underline-offset-2", children: "Alles wissen" })] }))] }));
|
|
67
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MailboxProcessedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProcessedPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MailboxProcessedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProcessedPage.tsx"],"names":[],"mappings":"AAaA,wBAAgB,oBAAoB,gCA4KnC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
5
|
import { toast } from "sonner";
|
|
@@ -7,6 +7,7 @@ import { Badge, Button, Card, CardContent, Table, TableBody, TableCell, TableHea
|
|
|
7
7
|
import { listProposals, reopenProposal, getActiveOrdersMailbox } from "../lib/mailbox.functions";
|
|
8
8
|
import { MailboxAuthBanner } from "./MailboxAuthBanner";
|
|
9
9
|
import { TablePagination } from "./TablePagination";
|
|
10
|
+
import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar";
|
|
10
11
|
export function MailboxProcessedPage() {
|
|
11
12
|
const fetchList = useServerFn(listProposals);
|
|
12
13
|
const reopen = useServerFn(reopenProposal);
|
|
@@ -29,13 +30,44 @@ export function MailboxProcessedPage() {
|
|
|
29
30
|
const items = [
|
|
30
31
|
...(Array.isArray(approved.data?.items) ? approved.data.items : []),
|
|
31
32
|
...(Array.isArray(rejected.data?.items) ? rejected.data.items : []),
|
|
32
|
-
]
|
|
33
|
+
];
|
|
33
34
|
const [busyId, setBusyId] = useState(null);
|
|
34
35
|
const [page, setPage] = useState(1);
|
|
35
36
|
const [pageSize, setPageSize] = useState(25);
|
|
36
|
-
const
|
|
37
|
+
const [search, setSearch] = usePersistedState("mailbox-processed:search", "");
|
|
38
|
+
const [period, setPeriod] = usePersistedState("mailbox-processed:period", "all");
|
|
39
|
+
const [sortOrder, setSortOrder] = usePersistedState("mailbox-processed:sort", "date-desc");
|
|
40
|
+
const [statusFilter, setStatusFilter] = usePersistedState("mailbox-processed:status", "all");
|
|
41
|
+
const customerNameFor = (p) => p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
|
|
42
|
+
const filteredItems = useMemo(() => {
|
|
43
|
+
const q = normalizeText(search).trim();
|
|
44
|
+
const filtered = items.filter((p) => {
|
|
45
|
+
if (statusFilter !== "all" && p.status !== statusFilter)
|
|
46
|
+
return false;
|
|
47
|
+
if (!matchesPeriod(p.reviewed_at, period))
|
|
48
|
+
return false;
|
|
49
|
+
if (!q)
|
|
50
|
+
return true;
|
|
51
|
+
const haystack = normalizeText([
|
|
52
|
+
customerNameFor(p),
|
|
53
|
+
p.mailbox_messages?.from_email,
|
|
54
|
+
p.mailbox_messages?.subject,
|
|
55
|
+
p.mailbox_messages?.preview,
|
|
56
|
+
].join(" "));
|
|
57
|
+
return q.split(/\s+/).every((token) => haystack.includes(token));
|
|
58
|
+
});
|
|
59
|
+
return filtered.sort((a, b) => {
|
|
60
|
+
const aDate = a.reviewed_at ?? "";
|
|
61
|
+
const bDate = b.reviewed_at ?? "";
|
|
62
|
+
return sortOrder === "date-desc" ? bDate.localeCompare(aDate) : aDate.localeCompare(bDate);
|
|
63
|
+
});
|
|
64
|
+
}, [items, search, period, sortOrder, statusFilter]);
|
|
65
|
+
const hasActiveFilters = !!search || period !== "all" || sortOrder !== "date-desc" || statusFilter !== "all";
|
|
66
|
+
const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); setStatusFilter("all"); };
|
|
67
|
+
useEffect(() => { setPage(1); }, [search, period, sortOrder, statusFilter]);
|
|
68
|
+
const pageCount = Math.max(1, Math.ceil(filteredItems.length / pageSize));
|
|
37
69
|
const currentPage = Math.min(page, pageCount);
|
|
38
|
-
const pagedItems =
|
|
70
|
+
const pagedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
|
39
71
|
const handleReopen = async (proposalId) => {
|
|
40
72
|
setBusyId(proposalId);
|
|
41
73
|
try {
|
|
@@ -50,5 +82,9 @@ export function MailboxProcessedPage() {
|
|
|
50
82
|
setBusyId(null);
|
|
51
83
|
}
|
|
52
84
|
};
|
|
53
|
-
return (_jsxs("div", { className: "p-6 flex flex-col gap-4 flex-1 min-h-0 overflow-hidden", children: [_jsx(MailboxAuthBanner, { active: activeMailbox }), _jsx("h1", { className: "text-2xl font-semibold shrink-0", children: "Verwerkt" }),
|
|
85
|
+
return (_jsxs("div", { className: "p-6 flex flex-col gap-4 flex-1 min-h-0 overflow-hidden", children: [_jsx(MailboxAuthBanner, { active: activeMailbox }), _jsx("h1", { className: "text-2xl font-semibold shrink-0", children: "Verwerkt" }), items.length > 0 && (_jsx(MailboxFilterBar, { search: search, onSearchChange: setSearch, period: period, onPeriodChange: setPeriod, sortOrder: sortOrder, onSortOrderChange: setSortOrder, status: statusFilter, onStatusChange: setStatusFilter, statusOptions: [
|
|
86
|
+
{ value: "all", label: "Alle statussen" },
|
|
87
|
+
{ value: "approved", label: "Goedgekeurd" },
|
|
88
|
+
{ value: "rejected", label: "Afgewezen" },
|
|
89
|
+
], filteredCount: filteredItems.length, totalCount: items.length, countLabel: "mails", onClear: clearFilters, hasActiveFilters: hasActiveFilters })), _jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain [scrollbar-gutter:stable]", children: _jsx(Card, { children: _jsxs(CardContent, { className: "p-0", children: [items.length === 0 ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Nog geen verwerkte voorstellen." })) : filteredItems.length === 0 ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Geen mails gevonden voor deze zoekopdracht." })) : (_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(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: pagedItems.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 ?? "-" }), _jsxs(TableCell, { className: "max-w-md", children: [_jsx("div", { className: "truncate", children: p.mailbox_messages?.subject ?? "-" }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: p.mailbox_messages?.preview || "" })] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: p.reviewed_at ? new Date(p.reviewed_at).toLocaleString("nl-NL") : "-" }), _jsx(TableCell, { className: "text-right space-x-2", children: p.status === "rejected" && (_jsx(Button, { size: "sm", variant: "outline", disabled: busyId === p.id, onClick: () => handleReopen(p.id), children: busyId === p.id ? "Bezig..." : "Terugzetten" })) })] }, p.id))) })] })), items.length > 0 && (_jsx(TablePagination, { total: filteredItems.length, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }))] }) }) })] }));
|
|
54
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AA6FA,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AA2CD,wBAAgB,oBAAoB,gCA8bnC"}
|
|
@@ -5,13 +5,14 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
5
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
6
|
import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw } from "lucide-react";
|
|
7
7
|
import { computeEarliestDelivery, isValidDeliveryDate } from "../lib/delivery-date";
|
|
8
|
-
import { listProposals, getProposalDetail, approveProposal, rejectProposal, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getOrderPickDays, getActiveOrdersMailbox, syncMailboxNow,
|
|
8
|
+
import { listProposals, getProposalDetail, approveProposal, rejectProposal, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getOrderPickDays, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, } from "../lib/mailbox.functions";
|
|
9
9
|
import { enabledLoadCarriers, normalizeAllowedLoadCarriers, EMPTY_LOAD_CARRIERS } from "../lib/load-carriers";
|
|
10
10
|
import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions";
|
|
11
11
|
import { SearchableSelect } from "./SearchableSelect";
|
|
12
12
|
import { MailboxAuthBanner } from "./MailboxAuthBanner";
|
|
13
13
|
import { EmailPreview } from "./EmailPreview";
|
|
14
14
|
import { TablePagination } from "./TablePagination";
|
|
15
|
+
import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar";
|
|
15
16
|
import { supabase } from "../integrations/supabase/client";
|
|
16
17
|
const beladingKey = (b) => `${b.plants_per_crate}x${b.crates_per_layer}x${b.layers_per_cart}|${b.crate_code ?? ""}|${b.carrier_code ?? ""}`;
|
|
17
18
|
const beladingLabel = (b) => `${b.plants_per_crate} planten/fust · ${b.crates_per_layer} fusten/laag · ${b.layers_per_cart} lagen`;
|
|
@@ -88,7 +89,6 @@ export function MailboxProposalsPage() {
|
|
|
88
89
|
const fetchPickupLocations = useServerFn(listPickupLocations);
|
|
89
90
|
const fetchActive = useServerFn(getActiveOrdersMailbox);
|
|
90
91
|
const fetchSync = useServerFn(syncMailboxNow);
|
|
91
|
-
const fetchLastRun = useServerFn(getLastIngestRun);
|
|
92
92
|
// Server fns met requireSupabaseAuth gooien een raw Response bij een stale
|
|
93
93
|
// bearer-token. Die bubbelt op als "[object Response]" en blankt het scherm.
|
|
94
94
|
// Vang elke fout en geef een lege fallback terug — UI blijft bruikbaar.
|
|
@@ -105,6 +105,9 @@ export function MailboxProposalsPage() {
|
|
|
105
105
|
const [activeId, setActiveId] = useState(null);
|
|
106
106
|
const [page, setPage] = useState(1);
|
|
107
107
|
const [pageSize, setPageSize] = useState(25);
|
|
108
|
+
const [search, setSearch] = usePersistedState("mailbox-proposals:search", "");
|
|
109
|
+
const [period, setPeriod] = usePersistedState("mailbox-proposals:period", "all");
|
|
110
|
+
const [sortOrder, setSortOrder] = usePersistedState("mailbox-proposals:sort", "date-desc");
|
|
108
111
|
const [refsEnabled, setRefsEnabled] = useState(false);
|
|
109
112
|
useEffect(() => { if (activeId)
|
|
110
113
|
setRefsEnabled(true); }, [activeId]);
|
|
@@ -168,22 +171,9 @@ export function MailboxProposalsPage() {
|
|
|
168
171
|
onSuccess: (res) => {
|
|
169
172
|
if (res?.ok && ((res.inserted ?? 0) > 0 || (res.processed ?? 0) > 0)) {
|
|
170
173
|
qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
|
|
171
|
-
qc.invalidateQueries({ queryKey: ["mailbox-last-ingest-run"] });
|
|
172
174
|
}
|
|
173
175
|
},
|
|
174
176
|
});
|
|
175
|
-
const handleManualSync = () => {
|
|
176
|
-
const p = syncMut.mutateAsync("manual");
|
|
177
|
-
toast.promise(p, {
|
|
178
|
-
loading: "Mailbox synchroniseren...",
|
|
179
|
-
success: (res) => res?.ok
|
|
180
|
-
? (res.inserted ?? 0) > 0
|
|
181
|
-
? `${res.inserted} nieuwe bestelling(en) opgehaald`
|
|
182
|
-
: "Geen nieuwe bestellingen"
|
|
183
|
-
: `Sync mislukt: ${res?.error ?? "onbekende fout"}`,
|
|
184
|
-
error: "Sync mislukt",
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
177
|
// Realtime hint: zodra er een nieuw event binnenkomt vragen we de server om
|
|
188
178
|
// het meteen te verwerken (zelfde pipeline). Cron is de garantiestrook.
|
|
189
179
|
useEffect(() => {
|
|
@@ -212,13 +202,6 @@ export function MailboxProposalsPage() {
|
|
|
212
202
|
return () => clearInterval(t);
|
|
213
203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
214
204
|
}, [activeMailboxId]);
|
|
215
|
-
// Laatste ingest-run tonen voor observability.
|
|
216
|
-
const { data: lastRunData } = useQuery({
|
|
217
|
-
queryKey: ["mailbox-last-ingest-run"],
|
|
218
|
-
queryFn: () => safe(() => fetchLastRun()),
|
|
219
|
-
refetchInterval: 60000,
|
|
220
|
-
});
|
|
221
|
-
const lastRun = lastRunData?.run ?? null;
|
|
222
205
|
// AI-agent gewijzigd? Dan worden openstaande voorstellen automatisch opnieuw
|
|
223
206
|
// geanalyseerd met de nieuwste agent. Server-side draait dit ook elke
|
|
224
207
|
// cron-run; hier reageren we direct op wijzigingen in de AI-module.
|
|
@@ -283,9 +266,6 @@ export function MailboxProposalsPage() {
|
|
|
283
266
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
284
267
|
}, []);
|
|
285
268
|
const items = Array.isArray(data?.items) ? data.items : [];
|
|
286
|
-
const pageCount = Math.max(1, Math.ceil(items.length / pageSize));
|
|
287
|
-
const currentPage = Math.min(page, pageCount);
|
|
288
|
-
const pagedItems = items.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
|
289
269
|
const template = Array.isArray(templateData?.items) ? templateData.items : [];
|
|
290
270
|
const customers = Array.isArray(customersData?.items) ? customersData.items : [];
|
|
291
271
|
const products = Array.isArray(productsData?.items) ? productsData.items : [];
|
|
@@ -310,12 +290,42 @@ export function MailboxProposalsPage() {
|
|
|
310
290
|
}
|
|
311
291
|
return p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
|
|
312
292
|
};
|
|
313
|
-
|
|
293
|
+
// --- Zoeken, filteren en sorteren --------------------------------------
|
|
294
|
+
const filteredItems = useMemo(() => {
|
|
295
|
+
const q = normalizeText(search).trim();
|
|
296
|
+
const filtered = items.filter((p) => {
|
|
297
|
+
const name = customerNameFor(p);
|
|
298
|
+
if (!matchesPeriod(p.mailbox_messages?.received_at ?? p.created_at, period))
|
|
299
|
+
return false;
|
|
300
|
+
if (!q)
|
|
301
|
+
return true;
|
|
302
|
+
const haystack = normalizeText([
|
|
303
|
+
name,
|
|
304
|
+
p.mailbox_messages?.from_email,
|
|
305
|
+
p.mailbox_messages?.from_name,
|
|
306
|
+
p.mailbox_messages?.subject,
|
|
307
|
+
p.mailbox_messages?.preview,
|
|
308
|
+
].join(" "));
|
|
309
|
+
return q.split(/\s+/).every((token) => haystack.includes(token));
|
|
310
|
+
});
|
|
311
|
+
return filtered.sort((a, b) => {
|
|
312
|
+
const aDate = a.mailbox_messages?.received_at ?? a.created_at ?? "";
|
|
313
|
+
const bDate = b.mailbox_messages?.received_at ?? b.created_at ?? "";
|
|
314
|
+
return sortOrder === "date-desc" ? bDate.localeCompare(aDate) : aDate.localeCompare(bDate);
|
|
315
|
+
});
|
|
316
|
+
}, [items, customers, search, sortOrder, period]);
|
|
317
|
+
const hasActiveFilters = !!search || period !== "all" || sortOrder !== "date-desc";
|
|
318
|
+
const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); };
|
|
319
|
+
useEffect(() => { setPage(1); }, [search, period, sortOrder]);
|
|
320
|
+
const pageCount = Math.max(1, Math.ceil(filteredItems.length / pageSize));
|
|
321
|
+
const currentPage = Math.min(page, pageCount);
|
|
322
|
+
const pagedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
|
323
|
+
return (_jsxs("div", { className: "p-6 flex flex-col gap-4 flex-1 min-h-0 overflow-hidden", children: [_jsx(MailboxAuthBanner, { active: activeMailbox }), _jsx("div", { className: "flex items-center justify-between shrink-0", children: _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Bestelvoorstellen" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Nieuw binnengekomen bestellingen die nog handmatig gecontroleerd moeten worden." })] }) }), items.length > 0 && (_jsx(MailboxFilterBar, { search: search, onSearchChange: setSearch, period: period, onPeriodChange: setPeriod, sortOrder: sortOrder, onSortOrderChange: setSortOrder, filteredCount: filteredItems.length, totalCount: items.length, countLabel: "voorstellen", onClear: clearFilters, hasActiveFilters: hasActiveFilters })), _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..." })) : listError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm", children: [_jsx("p", { className: "font-medium text-destructive", children: "Bestellingen konden niet worden opgehaald." }), _jsxs("p", { className: "text-muted-foreground mt-1", children: [listError?.message ?? "Onbekende fout", " \u2014 de lijst is niet leeg, alleen niet geladen."] }), _jsx(Button, { size: "sm", variant: "outline", className: "mt-3", onClick: () => void refetchList(), children: "Opnieuw proberen" })] })) : items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen bestellingen te controleren." })) : filteredItems.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen voorstellen gevonden voor deze zoekopdracht." })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "p-0", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Klantnaam" }), _jsx(TableHead, { children: "Bericht" }), _jsx(TableHead, { children: "Orderdatum" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: pagedItems.map((p) => {
|
|
314
324
|
const fvs = Array.isArray(p.mailbox_proposal_field_values) ? p.mailbox_proposal_field_values : [];
|
|
315
325
|
const needsReview = fvs.some((v) => v.needs_review);
|
|
316
326
|
const date = p.mailbox_messages?.received_at ?? p.created_at;
|
|
317
|
-
return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => setActiveId(p.id), children: [_jsx(TableCell, { className: "font-medium", children: customerNameFor(p) }),
|
|
318
|
-
}) })] }), _jsx(TablePagination, { total:
|
|
327
|
+
return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => setActiveId(p.id), children: [_jsx(TableCell, { className: "font-medium", children: customerNameFor(p) }), _jsxs(TableCell, { className: "max-w-sm", children: [_jsx("div", { className: "truncate text-sm font-medium", children: p.mailbox_messages?.subject || "(geen onderwerp)" }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: p.mailbox_messages?.preview || "Geen voorbeeldtekst" })] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: date ? new Date(date).toLocaleDateString("nl-NL") : "-" }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: "Te controleren" }) })] }, p.id));
|
|
328
|
+
}) })] }), _jsx(TablePagination, { total: filteredItems.length, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }) })) }), _jsx(Dialog, { open: !!activeId, onOpenChange: (o) => { if (!o) {
|
|
319
329
|
setActiveId(null);
|
|
320
330
|
setReparseError(null);
|
|
321
331
|
} }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: !active ? (_jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [_jsx(DialogTitle, { className: "text-base", children: "Bestelling" }), detailLoading ? "Bestelling laden…" : "Bestelling niet gevonden."] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx(DialogTitle, { children: customerNameFor(activeSummary ?? active) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "mr-8", disabled: reparseMutation.isPending, onClick: () => reparseMutation.mutate(active.id), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${reparseMutation.isPending ? "animate-spin" : ""}` }), reparseMutation.isPending ? "Analyseren…" : "Mail opnieuw analyseren"] })] }), reparseError && (_jsxs("div", { className: "mt-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: reparseError })] }))] }), _jsx("div", { className: "flex-1 min-h-0 overflow-hidden p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, allowBackorder: allowBackorder, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { SearchableSelect } from "./components/SearchableSelect";
|
|
|
5
5
|
export { MailboxProcessedPage } from "./components/MailboxProcessedPage";
|
|
6
6
|
export { EmailPreview } from "./components/EmailPreview";
|
|
7
7
|
export { TablePagination } from "./components/TablePagination";
|
|
8
|
+
export { MailboxFilterBar } from "./components/MailboxFilterBar";
|
|
8
9
|
export declare const mailboxOrdersModule: FlowModule;
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist-lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { SearchableSelect } from "./components/SearchableSelect";
|
|
|
10
10
|
export { MailboxProcessedPage } from "./components/MailboxProcessedPage";
|
|
11
11
|
export { EmailPreview } from "./components/EmailPreview";
|
|
12
12
|
export { TablePagination } from "./components/TablePagination";
|
|
13
|
+
export { MailboxFilterBar } from "./components/MailboxFilterBar";
|
|
13
14
|
export const mailboxOrdersModule = {
|
|
14
15
|
id: "mailbox_orders",
|
|
15
16
|
name: "Mailbox bestellingen",
|
|
@@ -18588,6 +18588,10 @@ export declare const supabase: import("@supabase/supabase-js").SupabaseClient<Da
|
|
|
18588
18588
|
Args: never;
|
|
18589
18589
|
Returns: undefined;
|
|
18590
18590
|
};
|
|
18591
|
+
cleanup_voorraad_module_history: {
|
|
18592
|
+
Args: never;
|
|
18593
|
+
Returns: undefined;
|
|
18594
|
+
};
|
|
18591
18595
|
complete_floriday_stock_queue: {
|
|
18592
18596
|
Args: {
|
|
18593
18597
|
p_attempt_id?: string;
|
|
@@ -20312,6 +20316,12 @@ export declare const supabase: import("@supabase/supabase-js").SupabaseClient<Da
|
|
|
20312
20316
|
};
|
|
20313
20317
|
Returns: undefined;
|
|
20314
20318
|
};
|
|
20319
|
+
reset_stalled_floriday_stock_queue: {
|
|
20320
|
+
Args: {
|
|
20321
|
+
p_include_failed?: boolean;
|
|
20322
|
+
};
|
|
20323
|
+
Returns: number;
|
|
20324
|
+
};
|
|
20315
20325
|
resolve_group_allocation: {
|
|
20316
20326
|
Args: {
|
|
20317
20327
|
_needed: number;
|
|
@@ -20366,6 +20376,10 @@ export declare const supabase: import("@supabase/supabase-js").SupabaseClient<Da
|
|
|
20366
20376
|
Args: never;
|
|
20367
20377
|
Returns: undefined;
|
|
20368
20378
|
};
|
|
20379
|
+
run_floriday_stock_queue_process: {
|
|
20380
|
+
Args: never;
|
|
20381
|
+
Returns: undefined;
|
|
20382
|
+
};
|
|
20369
20383
|
run_logistics_tasks_dispatch: {
|
|
20370
20384
|
Args: never;
|
|
20371
20385
|
Returns: undefined;
|