@open-mercato/core 0.4.5-develop-811deeb983 → 0.4.5-develop-3d8e759e45
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/modules/catalog/inbox-actions.js +51 -0
- package/dist/modules/catalog/inbox-actions.js.map +7 -0
- package/dist/modules/customers/inbox-actions.js +230 -0
- package/dist/modules/customers/inbox-actions.js.map +7 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +40 -1
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/extract/route.js +87 -0
- package/dist/modules/inbox_ops/api/extract/route.js.map +7 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js +6 -1
- package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +40 -14
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +161 -79
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +109 -62
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +3 -3
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +36 -14
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js +65 -10
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +58 -10
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/inbox_ops/lib/constants.js.map +2 -2
- package/dist/modules/inbox_ops/lib/contactValidation.js +40 -0
- package/dist/modules/inbox_ops/lib/contactValidation.js.map +7 -0
- package/dist/modules/inbox_ops/lib/executionEngine.js +31 -826
- package/dist/modules/inbox_ops/lib/executionEngine.js.map +3 -3
- package/dist/modules/inbox_ops/lib/executionHelpers.js +368 -0
- package/dist/modules/inbox_ops/lib/executionHelpers.js.map +7 -0
- package/dist/modules/inbox_ops/lib/extractionPrompt.js +28 -35
- package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +3 -3
- package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js +1 -0
- package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js.map +7 -0
- package/dist/modules/inbox_ops/lib/translationProvider.js +15 -10
- package/dist/modules/inbox_ops/lib/translationProvider.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js +16 -16
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
- package/dist/modules/sales/inbox-actions.js +278 -0
- package/dist/modules/sales/inbox-actions.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.mocks/inbox-actions.generated.js +5 -0
- package/package.json +2 -2
- package/src/modules/catalog/inbox-actions.ts +60 -0
- package/src/modules/customers/inbox-actions.ts +285 -0
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +44 -0
- package/src/modules/inbox_ops/api/extract/route.ts +94 -0
- package/src/modules/inbox_ops/api/proposals/[id]/translate/route.ts +6 -1
- package/src/modules/inbox_ops/api/proposals/counts/route.ts +2 -0
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +43 -13
- package/src/modules/inbox_ops/backend/inbox-ops/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +176 -81
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +122 -68
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +36 -14
- package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +91 -7
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +64 -12
- package/src/modules/inbox_ops/lib/constants.ts +9 -0
- package/src/modules/inbox_ops/lib/contactValidation.ts +54 -0
- package/src/modules/inbox_ops/lib/executionEngine.ts +47 -1060
- package/src/modules/inbox_ops/lib/executionHelpers.ts +527 -0
- package/src/modules/inbox_ops/lib/extractionPrompt.ts +45 -34
- package/src/modules/inbox_ops/lib/inbox-actions-generated.d.ts +11 -0
- package/src/modules/inbox_ops/lib/translationProvider.ts +16 -10
- package/src/modules/inbox_ops/subscribers/extractionWorker.ts +16 -18
- package/src/modules/sales/inbox-actions.ts +359 -0
|
@@ -7,6 +7,8 @@ import { DataTable } from "@open-mercato/ui/backend/DataTable";
|
|
|
7
7
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
8
8
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
9
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
10
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
11
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
10
12
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
13
|
import { ArrowLeft, RefreshCw } from "lucide-react";
|
|
12
14
|
const STATUS_COLORS = {
|
|
@@ -24,37 +26,51 @@ function ProcessingLogPage() {
|
|
|
24
26
|
const [pageSize] = React.useState(25);
|
|
25
27
|
const [statusFilter, setStatusFilter] = React.useState();
|
|
26
28
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
29
|
+
const [error, setError] = React.useState(null);
|
|
27
30
|
const [retryingEmailId, setRetryingEmailId] = React.useState(null);
|
|
31
|
+
const { runMutation } = useGuardedMutation({
|
|
32
|
+
contextId: "inbox-ops-log"
|
|
33
|
+
});
|
|
28
34
|
const loadEmails = React.useCallback(async () => {
|
|
29
35
|
setIsLoading(true);
|
|
36
|
+
setError(null);
|
|
30
37
|
const params = new URLSearchParams();
|
|
31
38
|
params.set("page", String(page));
|
|
32
39
|
params.set("pageSize", String(pageSize));
|
|
33
40
|
if (statusFilter) params.set("status", statusFilter);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
try {
|
|
42
|
+
const result = await apiCall(`/api/inbox_ops/emails?${params}`);
|
|
43
|
+
if (result?.ok && result.result?.items) {
|
|
44
|
+
setItems(result.result.items);
|
|
45
|
+
setTotal(result.result.total || 0);
|
|
46
|
+
} else {
|
|
47
|
+
setError(t("inbox_ops.log.load_failed", "Failed to load processing log"));
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
setError(t("inbox_ops.log.load_failed", "Failed to load processing log"));
|
|
38
51
|
}
|
|
39
52
|
setIsLoading(false);
|
|
40
|
-
}, [page, pageSize, statusFilter]);
|
|
53
|
+
}, [page, pageSize, statusFilter, t]);
|
|
41
54
|
React.useEffect(() => {
|
|
42
55
|
loadEmails();
|
|
43
56
|
}, [loadEmails]);
|
|
44
57
|
const handleRetryEmail = React.useCallback(async (emailId) => {
|
|
45
58
|
setRetryingEmailId(emailId);
|
|
46
|
-
const result = await
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
const result = await runMutation({
|
|
60
|
+
operation: () => apiCall(
|
|
61
|
+
`/api/inbox_ops/emails/${emailId}/reprocess`,
|
|
62
|
+
{ method: "POST" }
|
|
63
|
+
),
|
|
64
|
+
context: {}
|
|
65
|
+
});
|
|
50
66
|
if (result?.ok && result.result?.ok) {
|
|
51
|
-
flash(
|
|
67
|
+
flash(t("inbox_ops.flash.reprocessing_started", "Reprocessing started"), "success");
|
|
52
68
|
await loadEmails();
|
|
53
69
|
} else {
|
|
54
70
|
flash(result?.result?.error || t("inbox_ops.extraction_failed", "Extraction failed"), "error");
|
|
55
71
|
}
|
|
56
72
|
setRetryingEmailId(null);
|
|
57
|
-
}, [loadEmails, t]);
|
|
73
|
+
}, [loadEmails, t, runMutation]);
|
|
58
74
|
const columns = React.useMemo(() => [
|
|
59
75
|
{
|
|
60
76
|
accessorKey: "subject",
|
|
@@ -70,8 +86,16 @@ function ProcessingLogPage() {
|
|
|
70
86
|
accessorKey: "status",
|
|
71
87
|
header: t("inbox_ops.log.status", "Status"),
|
|
72
88
|
cell: ({ row }) => {
|
|
89
|
+
const statusLabels = {
|
|
90
|
+
received: t("inbox_ops.log.tab_received", "Received"),
|
|
91
|
+
processing: t("inbox_ops.log.tab_processing", "Processing"),
|
|
92
|
+
processed: t("inbox_ops.log.tab_processed", "Processed"),
|
|
93
|
+
needs_review: t("inbox_ops.log.tab_needs_review", "Needs Review"),
|
|
94
|
+
failed: t("inbox_ops.log.tab_failed", "Failed")
|
|
95
|
+
};
|
|
73
96
|
const color = STATUS_COLORS[row.original.status] || "bg-gray-100 text-gray-800";
|
|
74
|
-
|
|
97
|
+
const label = statusLabels[row.original.status] || row.original.status;
|
|
98
|
+
return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${color}`, children: label });
|
|
75
99
|
}
|
|
76
100
|
},
|
|
77
101
|
{
|
|
@@ -103,6 +127,7 @@ function ProcessingLogPage() {
|
|
|
103
127
|
return /* @__PURE__ */ jsxs(
|
|
104
128
|
Button,
|
|
105
129
|
{
|
|
130
|
+
type: "button",
|
|
106
131
|
variant: "outline",
|
|
107
132
|
size: "sm",
|
|
108
133
|
className: "h-8",
|
|
@@ -127,13 +152,14 @@ function ProcessingLogPage() {
|
|
|
127
152
|
];
|
|
128
153
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
129
154
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-3 md:px-6 md:py-4", children: [
|
|
130
|
-
/* @__PURE__ */ jsx(Link, { href: "/backend/inbox-ops", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) }) }),
|
|
155
|
+
/* @__PURE__ */ jsx(Link, { href: "/backend/inbox-ops", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) }) }),
|
|
131
156
|
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: t("inbox_ops.processing_log", "Processing Log") })
|
|
132
157
|
] }),
|
|
133
158
|
/* @__PURE__ */ jsxs(PageBody, { children: [
|
|
134
159
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 md:px-0 overflow-x-auto", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
135
160
|
Button,
|
|
136
161
|
{
|
|
162
|
+
type: "button",
|
|
137
163
|
variant: statusFilter === tab.value ? "default" : "outline",
|
|
138
164
|
size: "sm",
|
|
139
165
|
onClick: () => {
|
|
@@ -144,7 +170,7 @@ function ProcessingLogPage() {
|
|
|
144
170
|
},
|
|
145
171
|
tab.value ?? "all"
|
|
146
172
|
)) }),
|
|
147
|
-
/* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "min-w-[640px]", children: /* @__PURE__ */ jsx(
|
|
173
|
+
error ? /* @__PURE__ */ jsx(ErrorMessage, { label: error }) : /* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "min-w-[640px]", children: /* @__PURE__ */ jsx(
|
|
148
174
|
DataTable,
|
|
149
175
|
{
|
|
150
176
|
columns,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/inbox_ops/backend/inbox-ops/log/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ArrowLeft, RefreshCw } from 'lucide-react'\n\ntype EmailRow = {\n id: string\n subject: string\n forwardedByAddress: string\n forwardedByName?: string\n status: string\n processingError?: string\n receivedAt: string\n}\n\ntype EmailListResponse = {\n items?: EmailRow[]\n total?: number\n page?: number\n totalPages?: number\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n received: 'bg-blue-100 text-blue-800',\n processing: 'bg-purple-100 text-purple-800',\n processed: 'bg-green-100 text-green-800',\n needs_review: 'bg-amber-100 text-amber-800',\n failed: 'bg-red-100 text-red-800',\n}\n\nexport default function ProcessingLogPage() {\n const t = useT()\n const [items, setItems] = React.useState<EmailRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(25)\n const [statusFilter, setStatusFilter] = React.useState<string | undefined>()\n const [isLoading, setIsLoading] = React.useState(true)\n const [retryingEmailId, setRetryingEmailId] = React.useState<string | null>(null)\n\n const loadEmails = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (statusFilter) params.set('status', statusFilter)\n\n const result = await apiCall<EmailListResponse>(`/api/inbox_ops/emails?${params}`)\n
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ArrowLeft, RefreshCw } from 'lucide-react'\n\ntype EmailRow = {\n id: string\n subject: string\n forwardedByAddress: string\n forwardedByName?: string\n status: string\n processingError?: string\n receivedAt: string\n}\n\ntype EmailListResponse = {\n items?: EmailRow[]\n total?: number\n page?: number\n totalPages?: number\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n received: 'bg-blue-100 text-blue-800',\n processing: 'bg-purple-100 text-purple-800',\n processed: 'bg-green-100 text-green-800',\n needs_review: 'bg-amber-100 text-amber-800',\n failed: 'bg-red-100 text-red-800',\n}\n\nexport default function ProcessingLogPage() {\n const t = useT()\n const [items, setItems] = React.useState<EmailRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(25)\n const [statusFilter, setStatusFilter] = React.useState<string | undefined>()\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [retryingEmailId, setRetryingEmailId] = React.useState<string | null>(null)\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'inbox-ops-log',\n })\n\n const loadEmails = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (statusFilter) params.set('status', statusFilter)\n\n try {\n const result = await apiCall<EmailListResponse>(`/api/inbox_ops/emails?${params}`)\n if (result?.ok && result.result?.items) {\n setItems(result.result.items)\n setTotal(result.result.total || 0)\n } else {\n setError(t('inbox_ops.log.load_failed', 'Failed to load processing log'))\n }\n } catch {\n setError(t('inbox_ops.log.load_failed', 'Failed to load processing log'))\n }\n setIsLoading(false)\n }, [page, pageSize, statusFilter, t])\n\n React.useEffect(() => { loadEmails() }, [loadEmails])\n\n const handleRetryEmail = React.useCallback(async (emailId: string) => {\n setRetryingEmailId(emailId)\n const result = await runMutation({\n operation: () => apiCall<{ ok: boolean; error?: string }>(\n `/api/inbox_ops/emails/${emailId}/reprocess`,\n { method: 'POST' },\n ),\n context: {},\n })\n\n if (result?.ok && result.result?.ok) {\n flash(t('inbox_ops.flash.reprocessing_started', 'Reprocessing started'), 'success')\n await loadEmails()\n } else {\n flash(result?.result?.error || t('inbox_ops.extraction_failed', 'Extraction failed'), 'error')\n }\n\n setRetryingEmailId(null)\n }, [loadEmails, t, runMutation])\n\n const columns: ColumnDef<EmailRow>[] = React.useMemo(() => [\n {\n accessorKey: 'subject',\n header: t('inbox_ops.log.subject', 'Subject'),\n cell: ({ row }) => (\n <span className=\"text-sm font-medium truncate max-w-[300px] block\">{row.original.subject}</span>\n ),\n },\n {\n accessorKey: 'forwardedByAddress',\n header: t('inbox_ops.log.from', 'From'),\n cell: ({ row }) => (\n <span className=\"text-sm text-muted-foreground truncate max-w-[200px] block\">\n {row.original.forwardedByName || row.original.forwardedByAddress}\n </span>\n ),\n },\n {\n accessorKey: 'status',\n header: t('inbox_ops.log.status', 'Status'),\n cell: ({ row }) => {\n const statusLabels: Record<string, string> = {\n received: t('inbox_ops.log.tab_received', 'Received'),\n processing: t('inbox_ops.log.tab_processing', 'Processing'),\n processed: t('inbox_ops.log.tab_processed', 'Processed'),\n needs_review: t('inbox_ops.log.tab_needs_review', 'Needs Review'),\n failed: t('inbox_ops.log.tab_failed', 'Failed'),\n }\n const color = STATUS_COLORS[row.original.status] || 'bg-gray-100 text-gray-800'\n const label = statusLabels[row.original.status] || row.original.status\n return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${color}`}>{label}</span>\n },\n },\n {\n accessorKey: 'receivedAt',\n header: t('inbox_ops.received_at', 'Received'),\n cell: ({ row }) => {\n const d = new Date(row.original.receivedAt)\n return <span className=\"text-sm text-muted-foreground\">{d.toLocaleDateString()} {d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>\n },\n },\n {\n accessorKey: 'processingError',\n header: t('inbox_ops.extraction_failed', 'Error'),\n cell: ({ row }) => (\n <span className=\"text-xs text-red-600 truncate max-w-[280px] block\">\n {row.original.processingError || '-'}\n </span>\n ),\n },\n {\n id: 'actions',\n header: t('ui.actions.actions', 'Actions'),\n cell: ({ row }) => {\n const canRetry = row.original.status === 'failed' || row.original.status === 'needs_review'\n if (!canRetry) {\n return <span className=\"text-xs text-muted-foreground\">-</span>\n }\n\n const isRetrying = retryingEmailId === row.original.id\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8\"\n disabled={isRetrying}\n onClick={() => handleRetryEmail(row.original.id)}\n >\n <RefreshCw className={`h-3.5 w-3.5 mr-1 ${isRetrying ? 'animate-spin' : ''}`} />\n {t('inbox_ops.action.retry', 'Retry')}\n </Button>\n )\n },\n },\n ], [handleRetryEmail, retryingEmailId, t])\n\n const tabs = [\n { label: t('inbox_ops.log.tab_all', 'All'), value: undefined },\n { label: t('inbox_ops.log.tab_received', 'Received'), value: 'received' },\n { label: t('inbox_ops.log.tab_processing', 'Processing'), value: 'processing' },\n { label: t('inbox_ops.log.tab_processed', 'Processed'), value: 'processed' },\n { label: t('inbox_ops.log.tab_needs_review', 'Needs Review'), value: 'needs_review' },\n { label: t('inbox_ops.log.tab_failed', 'Failed'), value: 'failed' },\n ]\n\n return (\n <Page>\n <div className=\"flex items-center gap-3 px-3 py-3 md:px-6 md:py-4\">\n <Link href=\"/backend/inbox-ops\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\"><ArrowLeft className=\"h-4 w-4\" /></Button>\n </Link>\n <h1 className=\"text-lg font-semibold\">{t('inbox_ops.processing_log', 'Processing Log')}</h1>\n </div>\n\n <PageBody>\n <div className=\"flex items-center gap-2 px-3 py-2 md:px-0 overflow-x-auto\">\n {tabs.map((tab) => (\n <Button\n type=\"button\"\n key={tab.value ?? 'all'}\n variant={statusFilter === tab.value ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => { setStatusFilter(tab.value); setPage(1) }}\n >\n {tab.label}\n </Button>\n ))}\n </div>\n\n {error ? (\n <ErrorMessage label={error} />\n ) : (\n <div className=\"overflow-auto\">\n <div className=\"min-w-[640px]\">\n <DataTable\n columns={columns}\n data={items}\n isLoading={isLoading}\n pagination={{\n page,\n pageSize,\n total,\n totalPages: Math.ceil(total / pageSize),\n onPageChange: setPage,\n }}\n />\n </div>\n </div>\n )}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,12 +3,12 @@ const metadata = {
|
|
|
3
3
|
requireFeatures: ["inbox_ops.log.view"],
|
|
4
4
|
pageTitle: "Processing Log",
|
|
5
5
|
pageTitleKey: "inbox_ops.nav.log",
|
|
6
|
-
pageGroup: "
|
|
6
|
+
pageGroup: "AI Inbox Actions",
|
|
7
7
|
pageGroupKey: "inbox_ops.nav.group",
|
|
8
8
|
pageOrder: 910,
|
|
9
9
|
navHidden: true,
|
|
10
10
|
breadcrumb: [
|
|
11
|
-
{ label: "
|
|
11
|
+
{ label: "AI Inbox Actions", labelKey: "inbox_ops.nav.group", href: "/backend/inbox-ops" },
|
|
12
12
|
{ label: "Processing Log", labelKey: "inbox_ops.nav.log" }
|
|
13
13
|
]
|
|
14
14
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['inbox_ops.log.view'],\n pageTitle: 'Processing Log',\n pageTitleKey: 'inbox_ops.nav.log',\n pageGroup: '
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,oBAAoB;AAAA,EACtC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['inbox_ops.log.view'],\n pageTitle: 'Processing Log',\n pageTitleKey: 'inbox_ops.nav.log',\n pageGroup: 'AI Inbox Actions',\n pageGroupKey: 'inbox_ops.nav.group',\n pageOrder: 910,\n navHidden: true,\n breadcrumb: [\n { label: 'AI Inbox Actions', labelKey: 'inbox_ops.nav.group', href: '/backend/inbox-ops' },\n { label: 'Processing Log', labelKey: 'inbox_ops.nav.log' },\n ],\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,oBAAoB;AAAA,EACtC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,oBAAoB,UAAU,uBAAuB,MAAM,qBAAqB;AAAA,IACzF,EAAE,OAAO,kBAAkB,UAAU,oBAAoB;AAAA,EAC3D;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
7
7
|
import { DataTable } from "@open-mercato/ui/backend/DataTable";
|
|
8
|
+
import { RowActions } from "@open-mercato/ui/backend/RowActions";
|
|
8
9
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
10
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
10
12
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
13
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
14
|
+
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
15
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
16
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
12
17
|
import { Settings, Inbox, Copy } from "lucide-react";
|
|
13
18
|
const STATUS_COLORS = {
|
|
14
19
|
pending: "bg-yellow-100 text-yellow-800",
|
|
@@ -42,30 +47,44 @@ function InboxOpsProposalsPage() {
|
|
|
42
47
|
const t = useT();
|
|
43
48
|
const router = useRouter();
|
|
44
49
|
const scopeVersion = useOrganizationScopeVersion();
|
|
50
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
51
|
+
const { runMutation } = useGuardedMutation({
|
|
52
|
+
contextId: "inbox-ops-proposals"
|
|
53
|
+
});
|
|
45
54
|
const [items, setItems] = React.useState([]);
|
|
46
55
|
const [total, setTotal] = React.useState(0);
|
|
47
56
|
const [page, setPage] = React.useState(1);
|
|
48
57
|
const [pageSize] = React.useState(25);
|
|
49
|
-
const [
|
|
58
|
+
const [filterValues, setFilterValues] = React.useState({});
|
|
50
59
|
const [search, setSearch] = React.useState("");
|
|
51
60
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
61
|
+
const [error, setError] = React.useState(null);
|
|
62
|
+
const [initialLoadComplete, setInitialLoadComplete] = React.useState(false);
|
|
52
63
|
const [counts, setCounts] = React.useState({ pending: 0, partial: 0, accepted: 0, rejected: 0 });
|
|
53
64
|
const [settings, setSettings] = React.useState(null);
|
|
54
65
|
const [copied, setCopied] = React.useState(false);
|
|
66
|
+
const statusFilter = typeof filterValues.status === "string" ? filterValues.status : void 0;
|
|
55
67
|
const loadProposals = React.useCallback(async () => {
|
|
56
68
|
setIsLoading(true);
|
|
69
|
+
setError(null);
|
|
57
70
|
const params = new URLSearchParams();
|
|
58
71
|
params.set("page", String(page));
|
|
59
72
|
params.set("pageSize", String(pageSize));
|
|
60
73
|
if (statusFilter) params.set("status", statusFilter);
|
|
61
|
-
if (search) params.set("search", search);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
if (search.trim()) params.set("search", search.trim());
|
|
75
|
+
try {
|
|
76
|
+
const result = await apiCall(`/api/inbox_ops/proposals?${params}`);
|
|
77
|
+
if (result?.ok && result.result?.items) {
|
|
78
|
+
setItems(result.result.items);
|
|
79
|
+
setTotal(result.result.total || 0);
|
|
80
|
+
} else {
|
|
81
|
+
setError(t("inbox_ops.flash.load_failed", "Failed to load proposals"));
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
setError(t("inbox_ops.flash.load_failed", "Failed to load proposals"));
|
|
66
85
|
}
|
|
67
86
|
setIsLoading(false);
|
|
68
|
-
}, [page, pageSize, statusFilter, search, scopeVersion]);
|
|
87
|
+
}, [page, pageSize, statusFilter, search, scopeVersion, t]);
|
|
69
88
|
const loadCounts = React.useCallback(async () => {
|
|
70
89
|
const result = await apiCall("/api/inbox_ops/proposals/counts");
|
|
71
90
|
if (result?.ok && result.result) setCounts(result.result);
|
|
@@ -75,10 +94,13 @@ function InboxOpsProposalsPage() {
|
|
|
75
94
|
if (result?.ok && result.result?.settings) setSettings(result.result.settings);
|
|
76
95
|
}, [scopeVersion]);
|
|
77
96
|
React.useEffect(() => {
|
|
78
|
-
loadProposals()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}, [
|
|
97
|
+
Promise.all([loadProposals(), loadCounts(), loadSettings()]).then(() => {
|
|
98
|
+
setInitialLoadComplete(true);
|
|
99
|
+
});
|
|
100
|
+
}, []);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
if (initialLoadComplete) loadProposals();
|
|
103
|
+
}, [page, statusFilter, search, scopeVersion]);
|
|
82
104
|
const handleCopyAddress = React.useCallback(() => {
|
|
83
105
|
if (settings?.inboxAddress) {
|
|
84
106
|
navigator.clipboard.writeText(settings.inboxAddress);
|
|
@@ -86,6 +108,52 @@ function InboxOpsProposalsPage() {
|
|
|
86
108
|
setTimeout(() => setCopied(false), 2e3);
|
|
87
109
|
}
|
|
88
110
|
}, [settings]);
|
|
111
|
+
const handleRefresh = React.useCallback(() => {
|
|
112
|
+
loadProposals();
|
|
113
|
+
loadCounts();
|
|
114
|
+
}, [loadProposals, loadCounts]);
|
|
115
|
+
const handleFiltersApply = React.useCallback((values) => {
|
|
116
|
+
setFilterValues(values);
|
|
117
|
+
setPage(1);
|
|
118
|
+
}, []);
|
|
119
|
+
const handleFiltersClear = React.useCallback(() => {
|
|
120
|
+
setFilterValues({});
|
|
121
|
+
setPage(1);
|
|
122
|
+
}, []);
|
|
123
|
+
const handleRejectProposal = React.useCallback(async (proposalId) => {
|
|
124
|
+
const confirmed = await confirm({
|
|
125
|
+
title: t("inbox_ops.action.reject_all", "Reject Proposal"),
|
|
126
|
+
text: t("inbox_ops.action.reject_all_confirm", "Reject all pending actions in this proposal?")
|
|
127
|
+
});
|
|
128
|
+
if (!confirmed) return;
|
|
129
|
+
const result = await runMutation({
|
|
130
|
+
operation: () => apiCall(
|
|
131
|
+
`/api/inbox_ops/proposals/${proposalId}/reject`,
|
|
132
|
+
{ method: "POST" }
|
|
133
|
+
),
|
|
134
|
+
context: {}
|
|
135
|
+
});
|
|
136
|
+
if (result?.ok && result.result?.ok) {
|
|
137
|
+
flash(t("inbox_ops.action.proposal_rejected", "Proposal rejected"), "success");
|
|
138
|
+
loadProposals();
|
|
139
|
+
loadCounts();
|
|
140
|
+
} else {
|
|
141
|
+
flash(t("inbox_ops.flash.action_reject_failed", "Failed to reject"), "error");
|
|
142
|
+
}
|
|
143
|
+
}, [confirm, t, loadProposals, loadCounts, runMutation]);
|
|
144
|
+
const filters = React.useMemo(() => [
|
|
145
|
+
{
|
|
146
|
+
id: "status",
|
|
147
|
+
label: t("inbox_ops.list.filters.status", "Status"),
|
|
148
|
+
type: "select",
|
|
149
|
+
options: [
|
|
150
|
+
{ value: "pending", label: `${t("inbox_ops.status.pending", "Pending")} (${counts.pending})` },
|
|
151
|
+
{ value: "partial", label: `${t("inbox_ops.status.partial", "Partial")} (${counts.partial})` },
|
|
152
|
+
{ value: "accepted", label: `${t("inbox_ops.status.accepted", "Accepted")} (${counts.accepted})` },
|
|
153
|
+
{ value: "rejected", label: `${t("inbox_ops.status.rejected", "Rejected")} (${counts.rejected})` }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
], [t, counts]);
|
|
89
157
|
const columns = React.useMemo(() => [
|
|
90
158
|
{
|
|
91
159
|
accessorKey: "summary",
|
|
@@ -109,7 +177,7 @@ function InboxOpsProposalsPage() {
|
|
|
109
177
|
},
|
|
110
178
|
{
|
|
111
179
|
id: "actions_count",
|
|
112
|
-
header: t("inbox_ops.
|
|
180
|
+
header: t("inbox_ops.list.progress", "Progress"),
|
|
113
181
|
cell: ({ row }) => {
|
|
114
182
|
const pending = row.original.pendingActionCount ?? 0;
|
|
115
183
|
const total2 = row.original.actionCount ?? 0;
|
|
@@ -137,77 +205,91 @@ function InboxOpsProposalsPage() {
|
|
|
137
205
|
}
|
|
138
206
|
], [t]);
|
|
139
207
|
const totalCount = counts.pending + counts.partial + counts.accepted + counts.rejected;
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-3 md:px-6 md:py-4", children: [
|
|
150
|
-
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: t("inbox_ops.title", "InboxOps") }),
|
|
151
|
-
/* @__PURE__ */ jsx(Link, { href: "/backend/inbox-ops/settings", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
|
|
152
|
-
/* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
|
|
153
|
-
/* @__PURE__ */ jsx("span", { className: "hidden md:inline ml-1", children: t("inbox_ops.settings.title", "Settings") })
|
|
154
|
-
] }) })
|
|
208
|
+
const emptyStateContent = initialLoadComplete && totalCount === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
209
|
+
/* @__PURE__ */ jsx(Inbox, { className: "h-12 w-12 text-muted-foreground mb-4" }),
|
|
210
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: t("inbox_ops.empty.title", "Forward emails to start") }),
|
|
211
|
+
settings?.inboxAddress && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3", children: [
|
|
212
|
+
/* @__PURE__ */ jsx("code", { className: "text-sm font-mono", children: settings.inboxAddress }),
|
|
213
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleCopyAddress, children: [
|
|
214
|
+
/* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" }),
|
|
215
|
+
copied ? t("inbox_ops.settings.copied", "Copied") : t("inbox_ops.settings.copy", "Copy")
|
|
216
|
+
] })
|
|
155
217
|
] }),
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
-
/* @__PURE__ */
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/* @__PURE__ */ jsx("code", { className: "text-sm font-mono", children: settings.inboxAddress }),
|
|
161
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleCopyAddress, children: [
|
|
162
|
-
/* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" }),
|
|
163
|
-
copied ? t("inbox_ops.settings.copied", "Copied") : t("inbox_ops.settings.copy", "Copy")
|
|
164
|
-
] })
|
|
218
|
+
/* @__PURE__ */ jsxs("ol", { className: "mt-6 text-sm text-muted-foreground text-left space-y-2", children: [
|
|
219
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
220
|
+
"1. ",
|
|
221
|
+
t("inbox_ops.empty.step1", "Forward any email thread to this address")
|
|
165
222
|
] }),
|
|
166
|
-
/* @__PURE__ */ jsxs("
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
t("inbox_ops.empty.step2", "We'll analyze it and propose actions")
|
|
174
|
-
] }),
|
|
175
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
176
|
-
"3. ",
|
|
177
|
-
t("inbox_ops.empty.step3", "Review and accept with one click")
|
|
178
|
-
] })
|
|
223
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
224
|
+
"2. ",
|
|
225
|
+
t("inbox_ops.empty.step2", "We'll analyze it and propose actions")
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
228
|
+
"3. ",
|
|
229
|
+
t("inbox_ops.empty.step3", "Review and accept with one click")
|
|
179
230
|
] })
|
|
180
|
-
] })
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
231
|
+
] })
|
|
232
|
+
] }) : void 0;
|
|
233
|
+
if (error && !initialLoadComplete) {
|
|
234
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error }) }) });
|
|
235
|
+
}
|
|
236
|
+
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
237
|
+
/* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
238
|
+
DataTable,
|
|
239
|
+
{
|
|
240
|
+
title: t("inbox_ops.title", "AI Inbox Actions"),
|
|
241
|
+
refreshButton: {
|
|
242
|
+
label: t("inbox_ops.list.actions.refresh", "Refresh"),
|
|
243
|
+
onRefresh: handleRefresh
|
|
244
|
+
},
|
|
245
|
+
actions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
246
|
+
settings?.inboxAddress && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleCopyAddress, children: [
|
|
247
|
+
/* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" }),
|
|
248
|
+
/* @__PURE__ */ jsx("span", { className: "hidden md:inline ml-1", children: copied ? t("inbox_ops.settings.copied", "Copied") : t("inbox_ops.settings.copy", "Copy") })
|
|
249
|
+
] }),
|
|
250
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsxs(Link, { href: "/backend/inbox-ops/settings", children: [
|
|
251
|
+
/* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
|
|
252
|
+
/* @__PURE__ */ jsx("span", { className: "hidden md:inline ml-1", children: t("inbox_ops.list.actions.settings", "Settings") })
|
|
253
|
+
] }) })
|
|
254
|
+
] }),
|
|
255
|
+
columns,
|
|
256
|
+
data: items,
|
|
257
|
+
searchValue: search,
|
|
258
|
+
onSearchChange: (value) => {
|
|
259
|
+
setSearch(value);
|
|
260
|
+
setPage(1);
|
|
261
|
+
},
|
|
262
|
+
searchPlaceholder: t("inbox_ops.list.searchPlaceholder", "Search proposals..."),
|
|
263
|
+
filters,
|
|
264
|
+
filterValues,
|
|
265
|
+
onFiltersApply: handleFiltersApply,
|
|
266
|
+
onFiltersClear: handleFiltersClear,
|
|
267
|
+
onRowClick: (row) => router.push(`/backend/inbox-ops/proposals/${row.id}`),
|
|
268
|
+
rowActions: (row) => /* @__PURE__ */ jsx(RowActions, { items: [
|
|
269
|
+
{
|
|
270
|
+
id: "view",
|
|
271
|
+
label: t("inbox_ops.list.actions.view", "View"),
|
|
272
|
+
onSelect: () => router.push(`/backend/inbox-ops/proposals/${row.id}`)
|
|
189
273
|
},
|
|
190
|
-
|
|
274
|
+
...row.status === "pending" || row.status === "partial" ? [{
|
|
275
|
+
id: "reject",
|
|
276
|
+
label: t("inbox_ops.list.actions.reject", "Reject"),
|
|
277
|
+
destructive: true,
|
|
278
|
+
onSelect: () => handleRejectProposal(row.id)
|
|
279
|
+
}] : []
|
|
280
|
+
] }),
|
|
281
|
+
pagination: {
|
|
282
|
+
page,
|
|
283
|
+
pageSize,
|
|
284
|
+
total,
|
|
285
|
+
totalPages: Math.ceil(total / pageSize),
|
|
286
|
+
onPageChange: setPage
|
|
191
287
|
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
columns,
|
|
198
|
-
data: items,
|
|
199
|
-
isLoading,
|
|
200
|
-
onRowClick: (row) => router.push(`/backend/inbox-ops/proposals/${row.id}`),
|
|
201
|
-
pagination: {
|
|
202
|
-
page,
|
|
203
|
-
pageSize,
|
|
204
|
-
total,
|
|
205
|
-
totalPages: Math.ceil(total / pageSize),
|
|
206
|
-
onPageChange: setPage
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
) }) })
|
|
210
|
-
] }) })
|
|
288
|
+
isLoading,
|
|
289
|
+
emptyState: emptyStateContent
|
|
290
|
+
}
|
|
291
|
+
) }),
|
|
292
|
+
ConfirmDialogElement
|
|
211
293
|
] });
|
|
212
294
|
}
|
|
213
295
|
export {
|