@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.
@@ -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":"AAYA,wBAAgB,oBAAoB,gCA6GnC"}
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
- ].sort((a, b) => (b.reviewed_at ?? "").localeCompare(a.reviewed_at ?? ""));
33
+ ];
33
34
  const [busyId, setBusyId] = useState(null);
34
35
  const [page, setPage] = useState(1);
35
36
  const [pageSize, setPageSize] = useState(25);
36
- const pageCount = Math.max(1, Math.ceil(items.length / pageSize));
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 = items.slice((currentPage - 1) * pageSize, currentPage * pageSize);
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" }), _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." })) : (_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 ?? "-" }), _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") : "-" }), _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: items.length, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }))] }) }) })] }));
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":"AA2FA,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,gCAqbnC"}
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, getLastIngestRun, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, } from "../lib/mailbox.functions";
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
- return (_jsxs("div", { className: "p-6 flex flex-col gap-4 flex-1 min-h-0 overflow-hidden", children: [_jsx(MailboxAuthBanner, { active: activeMailbox }), _jsxs("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." })] }), _jsxs("div", { className: "flex items-center gap-3", children: [lastRun?.finished_at && (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Laatste sync: ", new Date(lastRun.finished_at).toLocaleTimeString("nl-NL"), " \u2014 ", lastRun.processed, " verwerkt", lastRun.errors > 0 ? `, ${lastRun.errors} fouten` : ""] })), _jsxs(Button, { onClick: handleManualSync, disabled: syncMut.isPending, variant: "outline", children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${syncMut.isPending ? "animate-spin" : ""}` }), "Sync nu"] })] })] }), _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." })) : (_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: "Orderdatum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Orderwaarde" })] }) }), _jsx(TableBody, { children: pagedItems.map((p) => {
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) }), _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) }), String(p.ai_ok) === "true" && (_jsx(Badge, { variant: "outline", className: "text-xs border-sky-500 text-sky-700", children: "AI" })), 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));
318
- }) })] }), _jsx(TablePagination, { total: items.length, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }) })) }), _jsx(Dialog, { open: !!activeId, onOpenChange: (o) => { if (!o) {
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) => {
@@ -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
@@ -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;AAE/D,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;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;