@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.
Files changed (76) hide show
  1. package/dist/modules/catalog/inbox-actions.js +51 -0
  2. package/dist/modules/catalog/inbox-actions.js.map +7 -0
  3. package/dist/modules/customers/inbox-actions.js +230 -0
  4. package/dist/modules/customers/inbox-actions.js.map +7 -0
  5. package/dist/modules/inbox_ops/api/emails/[id]/route.js +40 -1
  6. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  7. package/dist/modules/inbox_ops/api/extract/route.js +87 -0
  8. package/dist/modules/inbox_ops/api/extract/route.js.map +7 -0
  9. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js +6 -1
  10. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js.map +2 -2
  11. package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
  12. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +40 -14
  13. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
  14. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js +2 -2
  15. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js.map +2 -2
  16. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +161 -79
  17. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  18. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js +2 -2
  19. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js.map +2 -2
  20. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +109 -62
  21. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +3 -3
  22. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js +2 -2
  23. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js.map +2 -2
  24. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +36 -14
  25. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  26. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js +2 -2
  27. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js.map +2 -2
  28. package/dist/modules/inbox_ops/components/proposals/ActionCard.js +65 -10
  29. package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
  30. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +58 -10
  31. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  32. package/dist/modules/inbox_ops/lib/constants.js.map +2 -2
  33. package/dist/modules/inbox_ops/lib/contactValidation.js +40 -0
  34. package/dist/modules/inbox_ops/lib/contactValidation.js.map +7 -0
  35. package/dist/modules/inbox_ops/lib/executionEngine.js +31 -826
  36. package/dist/modules/inbox_ops/lib/executionEngine.js.map +3 -3
  37. package/dist/modules/inbox_ops/lib/executionHelpers.js +368 -0
  38. package/dist/modules/inbox_ops/lib/executionHelpers.js.map +7 -0
  39. package/dist/modules/inbox_ops/lib/extractionPrompt.js +28 -35
  40. package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +3 -3
  41. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js +1 -0
  42. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js.map +7 -0
  43. package/dist/modules/inbox_ops/lib/translationProvider.js +15 -10
  44. package/dist/modules/inbox_ops/lib/translationProvider.js.map +2 -2
  45. package/dist/modules/inbox_ops/subscribers/extractionWorker.js +16 -16
  46. package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
  47. package/dist/modules/sales/inbox-actions.js +278 -0
  48. package/dist/modules/sales/inbox-actions.js.map +7 -0
  49. package/jest.config.cjs +1 -0
  50. package/jest.mocks/inbox-actions.generated.js +5 -0
  51. package/package.json +2 -2
  52. package/src/modules/catalog/inbox-actions.ts +60 -0
  53. package/src/modules/customers/inbox-actions.ts +285 -0
  54. package/src/modules/inbox_ops/api/emails/[id]/route.ts +44 -0
  55. package/src/modules/inbox_ops/api/extract/route.ts +94 -0
  56. package/src/modules/inbox_ops/api/proposals/[id]/translate/route.ts +6 -1
  57. package/src/modules/inbox_ops/api/proposals/counts/route.ts +2 -0
  58. package/src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts +2 -2
  59. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +43 -13
  60. package/src/modules/inbox_ops/backend/inbox-ops/page.meta.ts +2 -2
  61. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +176 -81
  62. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.ts +2 -2
  63. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +122 -68
  64. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.meta.ts +2 -2
  65. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +36 -14
  66. package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +91 -7
  67. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +64 -12
  68. package/src/modules/inbox_ops/lib/constants.ts +9 -0
  69. package/src/modules/inbox_ops/lib/contactValidation.ts +54 -0
  70. package/src/modules/inbox_ops/lib/executionEngine.ts +47 -1060
  71. package/src/modules/inbox_ops/lib/executionHelpers.ts +527 -0
  72. package/src/modules/inbox_ops/lib/extractionPrompt.ts +45 -34
  73. package/src/modules/inbox_ops/lib/inbox-actions-generated.d.ts +11 -0
  74. package/src/modules/inbox_ops/lib/translationProvider.ts +16 -10
  75. package/src/modules/inbox_ops/subscribers/extractionWorker.ts +16 -18
  76. 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
- const result = await apiCall(`/api/inbox_ops/emails?${params}`);
35
- if (result?.ok && result.result?.items) {
36
- setItems(result.result.items);
37
- setTotal(result.result.total || 0);
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 apiCall(
47
- `/api/inbox_ops/emails/${emailId}/reprocess`,
48
- { method: "POST" }
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(`${t("inbox_ops.action.retry", "Retry")} ${t("inbox_ops.status.processing", "Processing")}`, "success");
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
- return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center px-2 py-0.5 rounded text-xs font-medium capitalize ${color}`, children: row.original.status });
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 if (result?.ok && result.result?.items) {\n setItems(result.result.items)\n setTotal(result.result.total || 0)\n }\n setIsLoading(false)\n }, [page, pageSize, statusFilter])\n\n React.useEffect(() => { loadEmails() }, [loadEmails])\n\n const handleRetryEmail = React.useCallback(async (emailId: string) => {\n setRetryingEmailId(emailId)\n const result = await apiCall<{ ok: boolean; error?: string }>(\n `/api/inbox_ops/emails/${emailId}/reprocess`,\n { method: 'POST' },\n )\n\n if (result?.ok && result.result?.ok) {\n flash(`${t('inbox_ops.action.retry', 'Retry')} ${t('inbox_ops.status.processing', 'Processing')}`, '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])\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 color = STATUS_COLORS[row.original.status] || 'bg-gray-100 text-gray-800'\n return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium capitalize ${color}`}>{row.original.status}</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 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 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 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 <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 </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
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: "InboxOps",
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: "InboxOps", labelKey: "inbox_ops.nav.group", href: "/backend/inbox-ops" },
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: 'InboxOps',\n pageGroupKey: 'inbox_ops.nav.group',\n pageOrder: 910,\n navHidden: true,\n breadcrumb: [\n { label: 'InboxOps', 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,YAAY,UAAU,uBAAuB,MAAM,qBAAqB;AAAA,IACjF,EAAE,OAAO,kBAAkB,UAAU,oBAAoB;AAAA,EAC3D;AACF;",
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 { Fragment, jsx, jsxs } from "react/jsx-runtime";
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 [statusFilter, setStatusFilter] = React.useState();
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
- const result = await apiCall(`/api/inbox_ops/proposals?${params}`);
63
- if (result?.ok && result.result?.items) {
64
- setItems(result.result.items);
65
- setTotal(result.result.total || 0);
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
- loadCounts();
80
- loadSettings();
81
- }, [loadProposals, loadCounts, loadSettings]);
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.actions_count", "Actions"),
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 isEmpty = totalCount === 0 && !isLoading;
141
- const tabs = [
142
- { label: `${t("common.all", "All")} (${totalCount})`, value: void 0 },
143
- { label: `${t("inbox_ops.status.pending", "Pending")} (${counts.pending})`, value: "pending" },
144
- { label: `${t("inbox_ops.status.partial", "Partial")} (${counts.partial})`, value: "partial" },
145
- { label: `${t("inbox_ops.status.accepted", "Accepted")} (${counts.accepted})`, value: "accepted" },
146
- { label: `${t("inbox_ops.status.rejected", "Rejected")} (${counts.rejected})`, value: "rejected" }
147
- ];
148
- return /* @__PURE__ */ jsxs(Page, { children: [
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__ */ jsx(PageBody, { children: isEmpty ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
157
- /* @__PURE__ */ jsx(Inbox, { className: "h-12 w-12 text-muted-foreground mb-4" }),
158
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: t("inbox_ops.empty.title", "Forward emails to start") }),
159
- settings?.inboxAddress && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3", children: [
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("ol", { className: "mt-6 text-sm text-muted-foreground text-left space-y-2", children: [
167
- /* @__PURE__ */ jsxs("li", { children: [
168
- "1. ",
169
- t("inbox_ops.empty.step1", "Forward any email thread to this address")
170
- ] }),
171
- /* @__PURE__ */ jsxs("li", { children: [
172
- "2. ",
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
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
181
- /* @__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(
182
- Button,
183
- {
184
- variant: statusFilter === tab.value ? "default" : "outline",
185
- size: "sm",
186
- onClick: () => {
187
- setStatusFilter(tab.value);
188
- setPage(1);
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
- children: tab.label
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
- tab.value ?? "all"
193
- )) }),
194
- /* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "min-w-[640px]", children: /* @__PURE__ */ jsx(
195
- DataTable,
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 {