@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/inbox_ops/backend/inbox-ops/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\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 { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Settings, Inbox, Copy } from 'lucide-react'\n\ntype ProposalRow = {\n id: string\n summary: string\n confidence: string\n status: string\n inboxEmailId: string\n createdAt: string\n participants?: { name: string; email: string }[]\n actionCount?: number\n pendingActionCount?: number\n discrepancyCount?: number\n emailSubject?: string | null\n emailFrom?: string | null\n receivedAt?: string | null\n}\n\ntype ProposalListResponse = {\n items?: ProposalRow[]\n total?: number\n page?: number\n totalPages?: number\n}\n\ntype StatusCounts = {\n pending: number\n partial: number\n accepted: number\n rejected: number\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n pending: 'bg-yellow-100 text-yellow-800',\n partial: 'bg-blue-100 text-blue-800',\n accepted: 'bg-green-100 text-green-800',\n rejected: 'bg-red-100 text-red-800',\n processing: 'bg-purple-100 text-purple-800',\n}\n\nfunction ConfidenceBadge({ value }: { value: string }) {\n const num = parseFloat(value)\n const pct = Math.round(num * 100)\n const color = num >= 0.8 ? 'bg-green-100 text-green-800' : num >= 0.6 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'\n return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${color}`}>{pct}%</span>\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n const t = useT()\n const statusLabels: Record<string, string> = {\n pending: t('inbox_ops.status.pending', 'Pending'),\n partial: t('inbox_ops.status.partial', 'Partial'),\n accepted: t('inbox_ops.status.accepted', 'Accepted'),\n rejected: t('inbox_ops.status.rejected', 'Rejected'),\n processing: t('inbox_ops.status.processing', 'Processing'),\n }\n const color = STATUS_COLORS[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 ${color}`}>{statusLabels[status] || status}</span>\n}\n\nexport default function InboxOpsProposalsPage() {\n const t = useT()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [items, setItems] = React.useState<ProposalRow[]>([])\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 [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [counts, setCounts] = React.useState<StatusCounts>({ pending: 0, partial: 0, accepted: 0, rejected: 0 })\n const [settings, setSettings] = React.useState<{ inboxAddress?: string } | null>(null)\n const [copied, setCopied] = React.useState(false)\n\n const loadProposals = 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 if (search) params.set('search', search)\n\n const result = await apiCall<ProposalListResponse>(`/api/inbox_ops/proposals?${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, search, scopeVersion])\n\n const loadCounts = React.useCallback(async () => {\n const result = await apiCall<StatusCounts>('/api/inbox_ops/proposals/counts')\n if (result?.ok && result.result) setCounts(result.result)\n }, [scopeVersion])\n\n const loadSettings = React.useCallback(async () => {\n const result = await apiCall<{ settings: { inboxAddress?: string } | null }>('/api/inbox_ops/settings')\n if (result?.ok && result.result?.settings) setSettings(result.result.settings)\n }, [scopeVersion])\n\n React.useEffect(() => {\n loadProposals()\n loadCounts()\n loadSettings()\n }, [loadProposals, loadCounts, loadSettings])\n\n const handleCopyAddress = React.useCallback(() => {\n if (settings?.inboxAddress) {\n navigator.clipboard.writeText(settings.inboxAddress)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n }\n }, [settings])\n\n const columns: ColumnDef<ProposalRow>[] = React.useMemo(() => [\n {\n accessorKey: 'summary',\n header: t('inbox_ops.summary', 'Summary'),\n cell: ({ row }) => (\n <div className=\"min-w-0\">\n <Link\n href={`/backend/inbox-ops/proposals/${row.original.id}`}\n className=\"text-sm font-medium text-primary hover:underline truncate max-w-[300px] block\"\n >\n {row.original.emailSubject || row.original.summary?.slice(0, 80) || t('inbox_ops.untitled_proposal', 'Untitled proposal')}\n </Link>\n {row.original.emailFrom && (\n <span className=\"text-xs text-muted-foreground truncate block\">{row.original.emailFrom}</span>\n )}\n </div>\n ),\n },\n {\n accessorKey: 'status',\n header: t('inbox_ops.list.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n id: 'actions_count',\n header: t('inbox_ops.actions_count', 'Actions'),\n cell: ({ row }) => {\n const pending = row.original.pendingActionCount ?? 0\n const total = row.original.actionCount ?? 0\n if (total === 0) return <span className=\"text-sm text-muted-foreground\">\u2014</span>\n return (\n <span className=\"text-sm text-muted-foreground\">\n {t('inbox_ops.list.action_summary', '{pending}/{total} actions')\n .replace('{pending}', String(pending))\n .replace('{total}', String(total))}\n </span>\n )\n },\n },\n {\n accessorKey: 'confidence',\n header: t('inbox_ops.confidence', 'Confidence'),\n cell: ({ row }) => <ConfidenceBadge value={row.original.confidence} />,\n },\n {\n accessorKey: 'receivedAt',\n header: t('inbox_ops.received_at', 'Received'),\n cell: ({ row }) => {\n const dateStr = row.original.receivedAt || row.original.createdAt\n const d = new Date(dateStr)\n return <span className=\"text-sm text-muted-foreground\">{d.toLocaleDateString()} {d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>\n },\n },\n ], [t])\n\n const totalCount = counts.pending + counts.partial + counts.accepted + counts.rejected\n const isEmpty = totalCount === 0 && !isLoading\n\n const tabs = [\n { label: `${t('common.all', 'All')} (${totalCount})`, value: undefined },\n { label: `${t('inbox_ops.status.pending', 'Pending')} (${counts.pending})`, value: 'pending' },\n { label: `${t('inbox_ops.status.partial', 'Partial')} (${counts.partial})`, value: 'partial' },\n { label: `${t('inbox_ops.status.accepted', 'Accepted')} (${counts.accepted})`, value: 'accepted' },\n { label: `${t('inbox_ops.status.rejected', 'Rejected')} (${counts.rejected})`, value: 'rejected' },\n ]\n\n return (\n <Page>\n <div className=\"flex items-center justify-between px-3 py-3 md:px-6 md:py-4\">\n <h1 className=\"text-lg font-semibold\">{t('inbox_ops.title', 'InboxOps')}</h1>\n <Link href=\"/backend/inbox-ops/settings\">\n <Button variant=\"outline\" size=\"sm\">\n <Settings className=\"h-4 w-4\" />\n <span className=\"hidden md:inline ml-1\">{t('inbox_ops.settings.title', 'Settings')}</span>\n </Button>\n </Link>\n </div>\n\n <PageBody>\n {isEmpty ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <Inbox className=\"h-12 w-12 text-muted-foreground mb-4\" />\n <h2 className=\"text-lg font-semibold mb-2\">{t('inbox_ops.empty.title', 'Forward emails to start')}</h2>\n {settings?.inboxAddress && (\n <div className=\"mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3\">\n <code className=\"text-sm font-mono\">{settings.inboxAddress}</code>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCopyAddress}>\n <Copy className=\"h-4 w-4\" />\n {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}\n </Button>\n </div>\n )}\n <ol className=\"mt-6 text-sm text-muted-foreground text-left space-y-2\">\n <li>1. {t('inbox_ops.empty.step1', 'Forward any email thread to this address')}</li>\n <li>2. {t('inbox_ops.empty.step2', \"We'll analyze it and propose actions\")}</li>\n <li>3. {t('inbox_ops.empty.step3', 'Review and accept with one click')}</li>\n </ol>\n </div>\n ) : (\n <>\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 onRowClick={(row) => router.push(`/backend/inbox-ops/proposals/${row.id}`)}\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 )}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\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 { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { Settings, Inbox, Copy } from 'lucide-react'\n\ntype ProposalRow = {\n id: string\n summary: string\n confidence: string\n status: string\n inboxEmailId: string\n createdAt: string\n participants?: { name: string; email: string }[]\n actionCount?: number\n pendingActionCount?: number\n discrepancyCount?: number\n emailSubject?: string | null\n emailFrom?: string | null\n receivedAt?: string | null\n}\n\ntype ProposalListResponse = {\n items?: ProposalRow[]\n total?: number\n page?: number\n totalPages?: number\n}\n\ntype StatusCounts = {\n pending: number\n partial: number\n accepted: number\n rejected: number\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n pending: 'bg-yellow-100 text-yellow-800',\n partial: 'bg-blue-100 text-blue-800',\n accepted: 'bg-green-100 text-green-800',\n rejected: 'bg-red-100 text-red-800',\n processing: 'bg-purple-100 text-purple-800',\n}\n\nfunction ConfidenceBadge({ value }: { value: string }) {\n const num = parseFloat(value)\n const pct = Math.round(num * 100)\n const color = num >= 0.8 ? 'bg-green-100 text-green-800' : num >= 0.6 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'\n return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${color}`}>{pct}%</span>\n}\n\nfunction StatusBadge({ status }: { status: string }) {\n const t = useT()\n const statusLabels: Record<string, string> = {\n pending: t('inbox_ops.status.pending', 'Pending'),\n partial: t('inbox_ops.status.partial', 'Partial'),\n accepted: t('inbox_ops.status.accepted', 'Accepted'),\n rejected: t('inbox_ops.status.rejected', 'Rejected'),\n processing: t('inbox_ops.status.processing', 'Processing'),\n }\n const color = STATUS_COLORS[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 ${color}`}>{statusLabels[status] || status}</span>\n}\n\nexport default function InboxOpsProposalsPage() {\n const t = useT()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'inbox-ops-proposals',\n })\n\n const [items, setItems] = React.useState<ProposalRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(25)\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [initialLoadComplete, setInitialLoadComplete] = React.useState(false)\n const [counts, setCounts] = React.useState<StatusCounts>({ pending: 0, partial: 0, accepted: 0, rejected: 0 })\n const [settings, setSettings] = React.useState<{ inboxAddress?: string } | null>(null)\n const [copied, setCopied] = React.useState(false)\n\n const statusFilter = typeof filterValues.status === 'string' ? filterValues.status : undefined\n\n const loadProposals = 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 if (search.trim()) params.set('search', search.trim())\n\n try {\n const result = await apiCall<ProposalListResponse>(`/api/inbox_ops/proposals?${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.flash.load_failed', 'Failed to load proposals'))\n }\n } catch {\n setError(t('inbox_ops.flash.load_failed', 'Failed to load proposals'))\n }\n setIsLoading(false)\n }, [page, pageSize, statusFilter, search, scopeVersion, t])\n\n const loadCounts = React.useCallback(async () => {\n const result = await apiCall<StatusCounts>('/api/inbox_ops/proposals/counts')\n if (result?.ok && result.result) setCounts(result.result)\n }, [scopeVersion])\n\n const loadSettings = React.useCallback(async () => {\n const result = await apiCall<{ settings: { inboxAddress?: string } | null }>('/api/inbox_ops/settings')\n if (result?.ok && result.result?.settings) setSettings(result.result.settings)\n }, [scopeVersion])\n\n React.useEffect(() => {\n Promise.all([loadProposals(), loadCounts(), loadSettings()]).then(() => {\n setInitialLoadComplete(true)\n })\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n React.useEffect(() => {\n if (initialLoadComplete) loadProposals()\n }, [page, statusFilter, search, scopeVersion]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleCopyAddress = React.useCallback(() => {\n if (settings?.inboxAddress) {\n navigator.clipboard.writeText(settings.inboxAddress)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n }\n }, [settings])\n\n const handleRefresh = React.useCallback(() => {\n loadProposals()\n loadCounts()\n }, [loadProposals, loadCounts])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n setFilterValues(values)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleRejectProposal = React.useCallback(async (proposalId: string) => {\n const confirmed = await confirm({\n title: t('inbox_ops.action.reject_all', 'Reject Proposal'),\n text: t('inbox_ops.action.reject_all_confirm', 'Reject all pending actions in this proposal?'),\n })\n if (!confirmed) return\n\n const result = await runMutation({\n operation: () => apiCall<{ ok: boolean }>(\n `/api/inbox_ops/proposals/${proposalId}/reject`,\n { method: 'POST' },\n ),\n context: {},\n })\n if (result?.ok && result.result?.ok) {\n flash(t('inbox_ops.action.proposal_rejected', 'Proposal rejected'), 'success')\n loadProposals()\n loadCounts()\n } else {\n flash(t('inbox_ops.flash.action_reject_failed', 'Failed to reject'), 'error')\n }\n }, [confirm, t, loadProposals, loadCounts, runMutation])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'status',\n label: t('inbox_ops.list.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: 'pending', label: `${t('inbox_ops.status.pending', 'Pending')} (${counts.pending})` },\n { value: 'partial', label: `${t('inbox_ops.status.partial', 'Partial')} (${counts.partial})` },\n { value: 'accepted', label: `${t('inbox_ops.status.accepted', 'Accepted')} (${counts.accepted})` },\n { value: 'rejected', label: `${t('inbox_ops.status.rejected', 'Rejected')} (${counts.rejected})` },\n ],\n },\n ], [t, counts])\n\n const columns: ColumnDef<ProposalRow>[] = React.useMemo(() => [\n {\n accessorKey: 'summary',\n header: t('inbox_ops.summary', 'Summary'),\n cell: ({ row }) => (\n <div className=\"min-w-0\">\n <Link\n href={`/backend/inbox-ops/proposals/${row.original.id}`}\n className=\"text-sm font-medium text-primary hover:underline truncate max-w-[300px] block\"\n >\n {row.original.emailSubject || row.original.summary?.slice(0, 80) || t('inbox_ops.untitled_proposal', 'Untitled proposal')}\n </Link>\n {row.original.emailFrom && (\n <span className=\"text-xs text-muted-foreground truncate block\">{row.original.emailFrom}</span>\n )}\n </div>\n ),\n },\n {\n accessorKey: 'status',\n header: t('inbox_ops.list.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n id: 'actions_count',\n header: t('inbox_ops.list.progress', 'Progress'),\n cell: ({ row }) => {\n const pending = row.original.pendingActionCount ?? 0\n const total = row.original.actionCount ?? 0\n if (total === 0) return <span className=\"text-sm text-muted-foreground\">\u2014</span>\n return (\n <span className=\"text-sm text-muted-foreground\">\n {t('inbox_ops.list.action_summary', '{pending}/{total} actions')\n .replace('{pending}', String(pending))\n .replace('{total}', String(total))}\n </span>\n )\n },\n },\n {\n accessorKey: 'confidence',\n header: t('inbox_ops.confidence', 'Confidence'),\n cell: ({ row }) => <ConfidenceBadge value={row.original.confidence} />,\n },\n {\n accessorKey: 'receivedAt',\n header: t('inbox_ops.received_at', 'Received'),\n cell: ({ row }) => {\n const dateStr = row.original.receivedAt || row.original.createdAt\n const d = new Date(dateStr)\n return <span className=\"text-sm text-muted-foreground\">{d.toLocaleDateString()} {d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>\n },\n },\n ], [t])\n\n const totalCount = counts.pending + counts.partial + counts.accepted + counts.rejected\n\n const emptyStateContent = initialLoadComplete && totalCount === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <Inbox className=\"h-12 w-12 text-muted-foreground mb-4\" />\n <h2 className=\"text-lg font-semibold mb-2\">{t('inbox_ops.empty.title', 'Forward emails to start')}</h2>\n {settings?.inboxAddress && (\n <div className=\"mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3\">\n <code className=\"text-sm font-mono\">{settings.inboxAddress}</code>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCopyAddress}>\n <Copy className=\"h-4 w-4\" />\n {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}\n </Button>\n </div>\n )}\n <ol className=\"mt-6 text-sm text-muted-foreground text-left space-y-2\">\n <li>1. {t('inbox_ops.empty.step1', 'Forward any email thread to this address')}</li>\n <li>2. {t('inbox_ops.empty.step2', \"We'll analyze it and propose actions\")}</li>\n <li>3. {t('inbox_ops.empty.step3', 'Review and accept with one click')}</li>\n </ol>\n </div>\n ) : undefined\n\n if (error && !initialLoadComplete) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <DataTable<ProposalRow>\n title={t('inbox_ops.title', 'AI Inbox Actions')}\n refreshButton={{\n label: t('inbox_ops.list.actions.refresh', 'Refresh'),\n onRefresh: handleRefresh,\n }}\n actions={(\n <div className=\"flex items-center gap-2\">\n {settings?.inboxAddress && (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCopyAddress}>\n <Copy className=\"h-4 w-4\" />\n <span className=\"hidden md:inline ml-1\">\n {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}\n </span>\n </Button>\n )}\n <Button variant=\"outline\" size=\"sm\" asChild>\n <Link href=\"/backend/inbox-ops/settings\">\n <Settings className=\"h-4 w-4\" />\n <span className=\"hidden md:inline ml-1\">{t('inbox_ops.list.actions.settings', 'Settings')}</span>\n </Link>\n </Button>\n </div>\n )}\n columns={columns}\n data={items}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('inbox_ops.list.searchPlaceholder', 'Search proposals...')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n onRowClick={(row) => router.push(`/backend/inbox-ops/proposals/${row.id}`)}\n rowActions={(row) => (\n <RowActions items={[\n {\n id: 'view',\n label: t('inbox_ops.list.actions.view', 'View'),\n onSelect: () => router.push(`/backend/inbox-ops/proposals/${row.id}`),\n },\n ...(row.status === 'pending' || row.status === 'partial' ? [{\n id: 'reject',\n label: t('inbox_ops.list.actions.reject', 'Reject'),\n destructive: true,\n onSelect: () => handleRejectProposal(row.id),\n }] : []),\n ]} />\n )}\n pagination={{\n page,\n pageSize,\n total,\n totalPages: Math.ceil(total / pageSize),\n onPageChange: setPage,\n }}\n isLoading={isLoading}\n emptyState={emptyStateContent}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["total"]
|
|
7
7
|
}
|
|
@@ -10,12 +10,12 @@ const metadata = {
|
|
|
10
10
|
requireFeatures: ["inbox_ops.proposals.view"],
|
|
11
11
|
pageTitle: "Proposals",
|
|
12
12
|
pageTitleKey: "inbox_ops.nav.proposals",
|
|
13
|
-
pageGroup: "
|
|
13
|
+
pageGroup: "AI Inbox Actions",
|
|
14
14
|
pageGroupKey: "inbox_ops.nav.group",
|
|
15
15
|
pagePriority: 45,
|
|
16
16
|
pageOrder: 100,
|
|
17
17
|
icon: inboxIcon,
|
|
18
|
-
breadcrumb: [{ label: "
|
|
18
|
+
breadcrumb: [{ label: "AI Inbox Actions", labelKey: "inbox_ops.nav.group" }]
|
|
19
19
|
};
|
|
20
20
|
export {
|
|
21
21
|
metadata
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/inbox_ops/backend/inbox-ops/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["import React from 'react'\n\nconst inboxIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },\n React.createElement('polyline', { points: '22 12 16 12 14 15 10 15 8 12 2 12' }),\n React.createElement('path', { d: 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['inbox_ops.proposals.view'],\n pageTitle: 'Proposals',\n pageTitleKey: 'inbox_ops.nav.proposals',\n pageGroup: '
|
|
5
|
-
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,YAAY,MAAM;AAAA,EACtB;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,EAAE;AAAA,EACpG,MAAM,cAAc,YAAY,EAAE,QAAQ,oCAAoC,CAAC;AAAA,EAC/E,MAAM,cAAc,QAAQ,EAAE,GAAG,6GAA6G,CAAC;AACjJ;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,0BAA0B;AAAA,EAC5C,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,
|
|
4
|
+
"sourcesContent": ["import React from 'react'\n\nconst inboxIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },\n React.createElement('polyline', { points: '22 12 16 12 14 15 10 15 8 12 2 12' }),\n React.createElement('path', { d: 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['inbox_ops.proposals.view'],\n pageTitle: 'Proposals',\n pageTitleKey: 'inbox_ops.nav.proposals',\n pageGroup: 'AI Inbox Actions',\n pageGroupKey: 'inbox_ops.nav.group',\n pagePriority: 45,\n pageOrder: 100,\n icon: inboxIcon,\n breadcrumb: [{ label: 'AI Inbox Actions', labelKey: 'inbox_ops.nav.group' }],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,YAAY,MAAM;AAAA,EACtB;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,EAAE;AAAA,EACpG,MAAM,cAAc,YAAY,EAAE,QAAQ,oCAAoC,CAAC;AAAA,EAC/E,MAAM,cAAc,QAAQ,EAAE,GAAG,6GAA6G,CAAC;AACjJ;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,0BAA0B;AAAA,EAC5C,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,oBAAoB,UAAU,sBAAsB,CAAC;AAC7E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,9 +7,10 @@ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
|
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 { LoadingMessage } from "@open-mercato/ui/backend/detail";
|
|
10
|
+
import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
11
11
|
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
12
12
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
13
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
13
14
|
import {
|
|
14
15
|
ArrowLeft,
|
|
15
16
|
CheckCircle,
|
|
@@ -22,7 +23,8 @@ import {
|
|
|
22
23
|
Users,
|
|
23
24
|
Languages
|
|
24
25
|
} from "lucide-react";
|
|
25
|
-
import { ActionCard, ConfidenceBadge, useActionTypeLabels } from "../../../../components/proposals/ActionCard.js";
|
|
26
|
+
import { ActionCard, ConfidenceBadge, useActionTypeLabels, useDiscrepancyDescriptions } from "../../../../components/proposals/ActionCard.js";
|
|
27
|
+
import { hasContactNameIssue } from "../../../../lib/contactValidation.js";
|
|
26
28
|
import { EditActionDialog } from "../../../../components/proposals/EditActionDialog.js";
|
|
27
29
|
function EmailThreadViewer({ email }) {
|
|
28
30
|
const t = useT();
|
|
@@ -52,9 +54,14 @@ function ProposalDetailPage({ params }) {
|
|
|
52
54
|
const [discrepancies, setDiscrepancies] = React.useState([]);
|
|
53
55
|
const [email, setEmail] = React.useState(null);
|
|
54
56
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
57
|
+
const [error, setError] = React.useState(null);
|
|
55
58
|
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
56
59
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
60
|
+
const { runMutation } = useGuardedMutation({
|
|
61
|
+
contextId: "inbox-ops-proposal-detail"
|
|
62
|
+
});
|
|
57
63
|
const actionTypeLabels = useActionTypeLabels();
|
|
64
|
+
const resolveDiscrepancyDescription = useDiscrepancyDescriptions();
|
|
58
65
|
const [editingAction, setEditingAction] = React.useState(null);
|
|
59
66
|
const [sendingReplyId, setSendingReplyId] = React.useState(null);
|
|
60
67
|
const [translation, setTranslation] = React.useState(null);
|
|
@@ -97,39 +104,53 @@ function ProposalDetailPage({ params }) {
|
|
|
97
104
|
const handleTranslate = React.useCallback(async () => {
|
|
98
105
|
if (!proposalId) return;
|
|
99
106
|
setIsTranslating(true);
|
|
100
|
-
const result = await
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
const result = await runMutation({
|
|
108
|
+
operation: () => apiCall(
|
|
109
|
+
`/api/inbox_ops/proposals/${proposalId}/translate`,
|
|
110
|
+
{ method: "POST", body: JSON.stringify({ targetLocale: locale }) }
|
|
111
|
+
),
|
|
112
|
+
context: {}
|
|
113
|
+
});
|
|
104
114
|
if (result?.ok && result.result?.translation) {
|
|
105
115
|
setTranslation(result.result.translation);
|
|
106
116
|
setShowTranslation(true);
|
|
107
117
|
} else {
|
|
108
|
-
|
|
118
|
+
const detail = result?.result?.error;
|
|
119
|
+
flash(detail ? `${t("inbox_ops.translate.failed", "Translation failed")}: ${detail}` : t("inbox_ops.translate.failed", "Translation failed"), "error");
|
|
109
120
|
}
|
|
110
121
|
setIsTranslating(false);
|
|
111
|
-
}, [proposalId, locale, t]);
|
|
122
|
+
}, [proposalId, locale, t, runMutation]);
|
|
112
123
|
const loadData = React.useCallback(async () => {
|
|
113
124
|
if (!proposalId) return;
|
|
114
125
|
setIsLoading(true);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
setError(null);
|
|
127
|
+
try {
|
|
128
|
+
const result = await apiCall(`/api/inbox_ops/proposals/${proposalId}`);
|
|
129
|
+
if (result?.ok && result.result) {
|
|
130
|
+
setProposal(result.result.proposal);
|
|
131
|
+
setActions(result.result.actions || []);
|
|
132
|
+
setDiscrepancies(result.result.discrepancies || []);
|
|
133
|
+
setEmail(result.result.email);
|
|
134
|
+
} else {
|
|
135
|
+
setError(t("inbox_ops.flash.load_failed", "Failed to load proposal"));
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
setError(t("inbox_ops.flash.load_failed", "Failed to load proposal"));
|
|
121
139
|
}
|
|
122
140
|
setIsLoading(false);
|
|
123
|
-
}, [proposalId]);
|
|
141
|
+
}, [proposalId, t]);
|
|
124
142
|
React.useEffect(() => {
|
|
125
143
|
loadData();
|
|
126
144
|
}, [loadData]);
|
|
127
145
|
const handleAcceptAction = React.useCallback(async (actionId) => {
|
|
128
146
|
setIsProcessing(true);
|
|
129
|
-
const result = await
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
const result = await runMutation({
|
|
148
|
+
operation: () => apiCall(
|
|
149
|
+
`/api/inbox_ops/proposals/${proposalId}/actions/${actionId}/accept`,
|
|
150
|
+
{ method: "POST" }
|
|
151
|
+
),
|
|
152
|
+
context: {}
|
|
153
|
+
});
|
|
133
154
|
if (result?.ok && result.result?.ok) {
|
|
134
155
|
flash(t("inbox_ops.flash.action_executed", "Action executed"), "success");
|
|
135
156
|
await loadData();
|
|
@@ -137,13 +158,16 @@ function ProposalDetailPage({ params }) {
|
|
|
137
158
|
flash(result?.result?.error || t("inbox_ops.flash.action_execute_failed", "Failed to execute action"), "error");
|
|
138
159
|
}
|
|
139
160
|
setIsProcessing(false);
|
|
140
|
-
}, [proposalId, loadData, t]);
|
|
161
|
+
}, [proposalId, loadData, t, runMutation]);
|
|
141
162
|
const handleRejectAction = React.useCallback(async (actionId) => {
|
|
142
163
|
setIsProcessing(true);
|
|
143
|
-
const result = await
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
const result = await runMutation({
|
|
165
|
+
operation: () => apiCall(
|
|
166
|
+
`/api/inbox_ops/proposals/${proposalId}/actions/${actionId}/reject`,
|
|
167
|
+
{ method: "POST" }
|
|
168
|
+
),
|
|
169
|
+
context: {}
|
|
170
|
+
});
|
|
147
171
|
if (result?.ok && result.result?.ok) {
|
|
148
172
|
flash(t("inbox_ops.flash.action_rejected", "Action rejected"), "success");
|
|
149
173
|
await loadData();
|
|
@@ -151,27 +175,34 @@ function ProposalDetailPage({ params }) {
|
|
|
151
175
|
flash(t("inbox_ops.flash.action_reject_failed", "Failed to reject action"), "error");
|
|
152
176
|
}
|
|
153
177
|
setIsProcessing(false);
|
|
154
|
-
}, [proposalId, loadData]);
|
|
178
|
+
}, [proposalId, loadData, runMutation]);
|
|
155
179
|
const handleAcceptAll = React.useCallback(async () => {
|
|
156
|
-
const
|
|
180
|
+
const pendingActions2 = actions.filter((a) => a.status === "pending");
|
|
181
|
+
const pendingCount = pendingActions2.length;
|
|
182
|
+
const nameIssueCount = pendingActions2.filter((a) => hasContactNameIssue(a)).length;
|
|
183
|
+
const confirmText = nameIssueCount > 0 ? t("inbox_ops.action.accept_all_confirm_with_skip", "Execute {count} pending actions? {skipCount} contact actions will be skipped due to missing names.").replace("{count}", String(pendingCount)).replace("{skipCount}", String(nameIssueCount)) : t("inbox_ops.action.accept_all_confirm", "Execute {count} pending actions?").replace("{count}", String(pendingCount));
|
|
157
184
|
const confirmed = await confirm({
|
|
158
185
|
title: t("inbox_ops.action.accept_all", "Accept All"),
|
|
159
|
-
text:
|
|
186
|
+
text: confirmText
|
|
160
187
|
});
|
|
161
188
|
if (!confirmed) return;
|
|
162
189
|
setIsProcessing(true);
|
|
163
|
-
const result = await
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
190
|
+
const result = await runMutation({
|
|
191
|
+
operation: () => apiCall(
|
|
192
|
+
`/api/inbox_ops/proposals/${proposalId}/accept-all`,
|
|
193
|
+
{ method: "POST" }
|
|
194
|
+
),
|
|
195
|
+
context: {}
|
|
196
|
+
});
|
|
167
197
|
if (result?.ok && result.result?.ok) {
|
|
168
|
-
|
|
198
|
+
const msg = result.result.failed > 0 ? t("inbox_ops.flash.accept_all_partial", "{succeeded} actions executed, {failed} failed").replace("{succeeded}", String(result.result.succeeded)).replace("{failed}", String(result.result.failed)) : t("inbox_ops.flash.accept_all_success", "{succeeded} actions executed").replace("{succeeded}", String(result.result.succeeded));
|
|
199
|
+
flash(msg, "success");
|
|
169
200
|
await loadData();
|
|
170
201
|
} else {
|
|
171
202
|
flash(t("inbox_ops.flash.accept_all_failed", "Failed to accept all actions"), "error");
|
|
172
203
|
}
|
|
173
204
|
setIsProcessing(false);
|
|
174
|
-
}, [proposalId, actions, confirm, t, loadData]);
|
|
205
|
+
}, [proposalId, actions, confirm, t, loadData, runMutation]);
|
|
175
206
|
const handleRejectAll = React.useCallback(async () => {
|
|
176
207
|
const confirmed = await confirm({
|
|
177
208
|
title: t("inbox_ops.action.reject_all", "Reject Proposal"),
|
|
@@ -179,35 +210,44 @@ function ProposalDetailPage({ params }) {
|
|
|
179
210
|
});
|
|
180
211
|
if (!confirmed) return;
|
|
181
212
|
setIsProcessing(true);
|
|
182
|
-
const result = await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
213
|
+
const result = await runMutation({
|
|
214
|
+
operation: () => apiCall(
|
|
215
|
+
`/api/inbox_ops/proposals/${proposalId}/reject`,
|
|
216
|
+
{ method: "POST" }
|
|
217
|
+
),
|
|
218
|
+
context: {}
|
|
219
|
+
});
|
|
186
220
|
if (result?.ok && result.result?.ok) {
|
|
187
221
|
flash(t("inbox_ops.action.proposal_rejected", "Proposal rejected"), "success");
|
|
188
222
|
await loadData();
|
|
189
223
|
}
|
|
190
224
|
setIsProcessing(false);
|
|
191
|
-
}, [proposalId, confirm, t, loadData]);
|
|
225
|
+
}, [proposalId, confirm, t, loadData, runMutation]);
|
|
192
226
|
const handleRetryExtraction = React.useCallback(async () => {
|
|
193
227
|
if (!email) return;
|
|
194
228
|
setIsProcessing(true);
|
|
195
|
-
const result = await
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
229
|
+
const result = await runMutation({
|
|
230
|
+
operation: () => apiCall(
|
|
231
|
+
`/api/inbox_ops/emails/${email.id}/reprocess`,
|
|
232
|
+
{ method: "POST" }
|
|
233
|
+
),
|
|
234
|
+
context: {}
|
|
235
|
+
});
|
|
199
236
|
if (result?.ok && result.result?.ok) {
|
|
200
237
|
flash(t("inbox_ops.flash.reprocessing_started", "Reprocessing started"), "success");
|
|
201
238
|
await loadData();
|
|
202
239
|
}
|
|
203
240
|
setIsProcessing(false);
|
|
204
|
-
}, [email, loadData]);
|
|
241
|
+
}, [email, loadData, runMutation]);
|
|
205
242
|
const handleSendReply = React.useCallback(async (actionId) => {
|
|
206
243
|
setSendingReplyId(actionId);
|
|
207
|
-
const result = await
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
244
|
+
const result = await runMutation({
|
|
245
|
+
operation: () => apiCall(
|
|
246
|
+
`/api/inbox_ops/proposals/${proposalId}/replies/${actionId}/send`,
|
|
247
|
+
{ method: "POST" }
|
|
248
|
+
),
|
|
249
|
+
context: {}
|
|
250
|
+
});
|
|
211
251
|
if (result?.ok && result.result?.ok) {
|
|
212
252
|
flash(t("inbox_ops.reply.sent_success", "Reply sent successfully"), "success");
|
|
213
253
|
await loadData();
|
|
@@ -215,8 +255,9 @@ function ProposalDetailPage({ params }) {
|
|
|
215
255
|
flash(result?.result?.error || t("inbox_ops.flash.send_reply_failed", "Failed to send reply"), "error");
|
|
216
256
|
}
|
|
217
257
|
setSendingReplyId(null);
|
|
218
|
-
}, [proposalId, t, loadData]);
|
|
258
|
+
}, [proposalId, t, loadData, runMutation]);
|
|
219
259
|
if (isLoading) return /* @__PURE__ */ jsx(LoadingMessage, { label: t("inbox_ops.loading_proposal", "Loading proposal...") });
|
|
260
|
+
if (error) return /* @__PURE__ */ jsx(ErrorMessage, { label: error });
|
|
220
261
|
const pendingActions = actions.filter((a) => a.status === "pending");
|
|
221
262
|
const emailIsProcessing = email?.status === "processing";
|
|
222
263
|
const emailFailed = email?.status === "failed";
|
|
@@ -231,22 +272,23 @@ function ProposalDetailPage({ params }) {
|
|
|
231
272
|
onSaved: loadData
|
|
232
273
|
}
|
|
233
274
|
),
|
|
234
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-
|
|
235
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
236
|
-
/* @__PURE__ */ jsx(Link, { href: "/backend/inbox-ops", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) }) }),
|
|
237
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
238
|
-
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold truncate", children: email?.subject || t("inbox_ops.proposal", "Proposal") }),
|
|
239
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 md:px-6 md:py-4 border-b bg-background", children: [
|
|
276
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 md:gap-3 min-w-0", children: [
|
|
277
|
+
/* @__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" }) }) }),
|
|
278
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
279
|
+
/* @__PURE__ */ jsx("h1", { className: "text-base md:text-lg font-semibold truncate", children: email?.subject || t("inbox_ops.proposal", "Proposal") }),
|
|
280
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground truncate", children: [
|
|
240
281
|
email?.forwardedByName || email?.forwardedByAddress,
|
|
241
282
|
" \xB7 ",
|
|
242
283
|
email?.receivedAt && new Date(email.receivedAt).toLocaleString()
|
|
243
284
|
] })
|
|
244
285
|
] })
|
|
245
286
|
] }),
|
|
246
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
287
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
247
288
|
pendingActions.length > 0 && /* @__PURE__ */ jsxs(
|
|
248
289
|
Button,
|
|
249
290
|
{
|
|
291
|
+
type: "button",
|
|
250
292
|
variant: "outline",
|
|
251
293
|
size: "sm",
|
|
252
294
|
className: "h-11 md:h-9 text-destructive border-destructive/30 hover:bg-destructive/10",
|
|
@@ -258,7 +300,7 @@ function ProposalDetailPage({ params }) {
|
|
|
258
300
|
]
|
|
259
301
|
}
|
|
260
302
|
),
|
|
261
|
-
pendingActions.length > 1 && /* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-11 md:h-9", onClick: handleAcceptAll, disabled: isProcessing, children: [
|
|
303
|
+
pendingActions.length > 1 && /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", className: "h-11 md:h-9", onClick: handleAcceptAll, disabled: isProcessing, children: [
|
|
262
304
|
isProcessing ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin mr-1" }) : /* @__PURE__ */ jsx(CheckCheck, { className: "h-4 w-4 mr-1" }),
|
|
263
305
|
/* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: t("inbox_ops.action.accept_all", "Accept All") })
|
|
264
306
|
] })
|
|
@@ -275,7 +317,7 @@ function ProposalDetailPage({ params }) {
|
|
|
275
317
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-red-700", children: t("inbox_ops.extraction_failed", "Extraction failed") })
|
|
276
318
|
] }),
|
|
277
319
|
email?.processingError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 mb-3", children: email.processingError }),
|
|
278
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: handleRetryExtraction, disabled: isProcessing, children: [
|
|
320
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleRetryExtraction, disabled: isProcessing, children: [
|
|
279
321
|
/* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 mr-1" }),
|
|
280
322
|
t("inbox_ops.action.retry", "Retry")
|
|
281
323
|
] })
|
|
@@ -283,9 +325,10 @@ function ProposalDetailPage({ params }) {
|
|
|
283
325
|
/* @__PURE__ */ jsxs("div", { className: "border rounded-lg p-3 md:p-4", children: [
|
|
284
326
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
285
327
|
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm", children: t("inbox_ops.summary", "Summary") }),
|
|
286
|
-
proposal.workingLanguage
|
|
328
|
+
(proposal.workingLanguage || "en") !== locale && /* @__PURE__ */ jsxs(
|
|
287
329
|
Button,
|
|
288
330
|
{
|
|
331
|
+
type: "button",
|
|
289
332
|
variant: "ghost",
|
|
290
333
|
size: "sm",
|
|
291
334
|
className: "h-8 text-xs",
|
|
@@ -333,15 +376,17 @@ function ProposalDetailPage({ params }) {
|
|
|
333
376
|
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: general.map((d) => /* @__PURE__ */ jsxs("div", { className: `flex items-start gap-2 text-xs rounded px-2 py-1.5 ${d.severity === "error" ? "bg-red-100 text-red-700 dark:bg-red-950/30" : "bg-yellow-100 text-yellow-700 dark:bg-yellow-950/30"}`, children: [
|
|
334
377
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3 mt-0.5 flex-shrink-0" }),
|
|
335
378
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
336
|
-
/* @__PURE__ */ jsx("span", { children: d.description }),
|
|
379
|
+
/* @__PURE__ */ jsx("span", { children: resolveDiscrepancyDescription(d.description, d.foundValue) }),
|
|
337
380
|
(d.expectedValue || d.foundValue) && /* @__PURE__ */ jsxs("div", { className: "mt-0.5 text-[11px] opacity-80", children: [
|
|
338
381
|
d.expectedValue && /* @__PURE__ */ jsxs("span", { children: [
|
|
339
|
-
"
|
|
382
|
+
t("inbox_ops.discrepancy.expected", "Expected"),
|
|
383
|
+
": ",
|
|
340
384
|
d.expectedValue
|
|
341
385
|
] }),
|
|
342
386
|
d.expectedValue && d.foundValue && /* @__PURE__ */ jsx("span", { children: " \xB7 " }),
|
|
343
387
|
d.foundValue && /* @__PURE__ */ jsxs("span", { children: [
|
|
344
|
-
"
|
|
388
|
+
t("inbox_ops.discrepancy.found", "Found"),
|
|
389
|
+
": ",
|
|
345
390
|
d.foundValue
|
|
346
391
|
] })
|
|
347
392
|
] })
|
|
@@ -362,12 +407,14 @@ function ProposalDetailPage({ params }) {
|
|
|
362
407
|
onReject: handleRejectAction,
|
|
363
408
|
onRetry: handleAcceptAction,
|
|
364
409
|
onEdit: handleEditAction,
|
|
365
|
-
translatedDescription: showTranslation ? translation?.actions[action.id] : void 0
|
|
410
|
+
translatedDescription: showTranslation ? translation?.actions[action.id] : void 0,
|
|
411
|
+
resolveDiscrepancyDescription
|
|
366
412
|
}
|
|
367
413
|
),
|
|
368
414
|
action.actionType === "draft_reply" && (action.status === "executed" || action.status === "accepted") && /* @__PURE__ */ jsx("div", { className: "mt-2 pl-7", children: /* @__PURE__ */ jsxs(
|
|
369
415
|
Button,
|
|
370
416
|
{
|
|
417
|
+
type: "button",
|
|
371
418
|
size: "sm",
|
|
372
419
|
variant: "outline",
|
|
373
420
|
className: "h-11 md:h-9",
|