@open-mercato/core 0.6.4-develop.4305.1.efaf0ebab1 → 0.6.4-develop.4322.1.7bf54b8070
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/currencies/backend/exchange-rates/[id]/page.js +16 -3
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +4 -0
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +21 -5
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +21 -5
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +48 -32
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +18 -3
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/entities/api/entities.js +4 -1
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +14 -9
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +21 -3
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +18 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -3
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/jest.config.cjs +3 -0
- package/package.json +7 -7
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -3
- package/src/modules/customers/api/companies/[id]/route.ts +4 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +25 -5
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +25 -5
- package/src/modules/customers/commands/companies.ts +51 -34
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +21 -3
- package/src/modules/entities/api/entities.ts +4 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +22 -11
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +36 -3
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +21 -2
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +21 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/integrations/backend/integrations/bundle/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\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 type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\ntype BundleConfigPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const bundleId = parts.at(-1)\n if (!bundleId || bundleId === 'bundle' || bundleId === 'integrations') return undefined\n return decodeURIComponent(bundleId)\n}\n\nexport default function BundleConfigPage({ params }: BundleConfigPageProps) {\n const pathname = usePathname()\n const bundleId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const resolveCurrentBundleId = React.useCallback(() => {\n return bundleId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [bundleId])\n\n const load = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n setError(null)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n const next = { ...credCall.result.credentials }\n if (currentBundleId === 'storage_s3') {\n const authMode = next.authMode\n if (authMode !== 'access_keys' && authMode !== 'ambient') {\n const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)\n next.authMode = hasKeys ? 'access_keys' : 'ambient'\n }\n }\n setCredValues(next)\n }\n setIsLoading(false)\n }, [resolveCurrentBundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) return\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [resolveCurrentBundleId, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)\n\n function isFieldVisible(field: CredentialField): boolean {\n if (!field.visibleWhen) return true\n return credValues[field.visibleWhen.field] === field.visibleWhen.equals\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.filter(isFieldVisible).map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <Select\n value={(credValues[field.key] as string) || undefined}\n onValueChange={(value) => setCredValues((prev) => ({ ...prev, [field.key]: value ?? '' }))}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"\u2014\" />\n </SelectTrigger>\n <SelectContent>\n {field.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\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 type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\ntype BundleConfigPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const bundleId = parts.at(-1)\n if (!bundleId || bundleId === 'bundle' || bundleId === 'integrations') return undefined\n return decodeURIComponent(bundleId)\n}\n\nexport default function BundleConfigPage({ params }: BundleConfigPageProps) {\n const pathname = usePathname()\n const bundleId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const resolveCurrentBundleId = React.useCallback(() => {\n return bundleId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [bundleId])\n\n const load = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n if (call.status === 404) {\n setIsNotFound(true)\n } else {\n setError(t('integrations.detail.loadError'))\n }\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n const next = { ...credCall.result.credentials }\n if (currentBundleId === 'storage_s3') {\n const authMode = next.authMode\n if (authMode !== 'access_keys' && authMode !== 'ambient') {\n const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)\n next.authMode = hasKeys ? 'access_keys' : 'ambient'\n }\n }\n setCredValues(next)\n }\n setIsLoading(false)\n }, [resolveCurrentBundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) return\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [resolveCurrentBundleId, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('integrations.detail.notFound', 'Integration not found.')}\n backHref=\"/backend/integrations\"\n backLabel={t('integrations.detail.backToList', 'Back to integrations')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)\n\n function isFieldVisible(field: CredentialField): boolean {\n if (!field.visibleWhen) return true\n return credValues[field.visibleWhen.field] === field.visibleWhen.equals\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.filter(isFieldVisible).map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <Select\n value={(credValues[field.key] as string) || undefined}\n onValueChange={(value) => setCredValues((prev) => ({ ...prev, [field.key]: value ?? '' }))}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"\u2014\" />\n </SelectTrigger>\n <SelectContent>\n {field.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/jest.config.cjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.4-develop.
|
|
3
|
+
"version": "0.6.4-develop.4322.1.7bf54b8070",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -243,16 +243,16 @@
|
|
|
243
243
|
"zod": "^4.4.3"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
247
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
248
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
246
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4322.1.7bf54b8070",
|
|
247
|
+
"@open-mercato/shared": "0.6.4-develop.4322.1.7bf54b8070",
|
|
248
|
+
"@open-mercato/ui": "0.6.4-develop.4322.1.7bf54b8070",
|
|
249
249
|
"react": "^19.0.0",
|
|
250
250
|
"react-dom": "^19.0.0"
|
|
251
251
|
},
|
|
252
252
|
"devDependencies": {
|
|
253
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
254
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
255
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
253
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4322.1.7bf54b8070",
|
|
254
|
+
"@open-mercato/shared": "0.6.4-develop.4322.1.7bf54b8070",
|
|
255
|
+
"@open-mercato/ui": "0.6.4-develop.4322.1.7bf54b8070",
|
|
256
256
|
"@testing-library/dom": "^10.4.1",
|
|
257
257
|
"@testing-library/jest-dom": "^6.9.1",
|
|
258
258
|
"@testing-library/react": "^16.3.1",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
6
|
-
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
6
|
+
import { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
|
|
7
7
|
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
8
8
|
import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
9
9
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
@@ -49,6 +49,7 @@ export default function EditExchangeRatePage({ params }: { params?: { id?: strin
|
|
|
49
49
|
const [exchangeRate, setExchangeRate] = React.useState<ExchangeRateData | null>(null)
|
|
50
50
|
const [loading, setLoading] = React.useState(true)
|
|
51
51
|
const [error, setError] = React.useState<string | null>(null)
|
|
52
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
52
53
|
|
|
53
54
|
const loadOptions = React.useCallback(
|
|
54
55
|
(query?: string) => loadCurrencyOptions(apiCall, query),
|
|
@@ -62,8 +63,10 @@ export default function EditExchangeRatePage({ params }: { params?: { id?: strin
|
|
|
62
63
|
const response = await apiCall<{ items: ExchangeRateData[] }>(`/api/currencies/exchange-rates?id=${params?.id}`)
|
|
63
64
|
if (response.ok && response.result && response.result.items.length > 0) {
|
|
64
65
|
setExchangeRate(response.result.items[0])
|
|
66
|
+
} else if (response.ok) {
|
|
67
|
+
setIsNotFound(true)
|
|
65
68
|
} else {
|
|
66
|
-
setError(t('exchangeRates.form.errors.
|
|
69
|
+
setError(t('exchangeRates.form.errors.load'))
|
|
67
70
|
}
|
|
68
71
|
} catch (err) {
|
|
69
72
|
setError(t('exchangeRates.form.errors.load'))
|
|
@@ -89,11 +92,25 @@ export default function EditExchangeRatePage({ params }: { params?: { id?: strin
|
|
|
89
92
|
)
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
if (isNotFound) {
|
|
96
|
+
return (
|
|
97
|
+
<Page>
|
|
98
|
+
<PageBody>
|
|
99
|
+
<RecordNotFoundState
|
|
100
|
+
label={t('exchangeRates.form.errors.notFound')}
|
|
101
|
+
backHref="/backend/exchange-rates"
|
|
102
|
+
backLabel={t('exchangeRates.form.actions.backToList', 'Back to exchange rates')}
|
|
103
|
+
/>
|
|
104
|
+
</PageBody>
|
|
105
|
+
</Page>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
if (error || !exchangeRate) {
|
|
93
110
|
return (
|
|
94
111
|
<Page>
|
|
95
112
|
<PageBody>
|
|
96
|
-
<ErrorMessage label={error
|
|
113
|
+
<ErrorMessage label={error ?? t('exchangeRates.form.errors.load')} />
|
|
97
114
|
</PageBody>
|
|
98
115
|
</Page>
|
|
99
116
|
)
|
|
@@ -933,6 +933,8 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
933
933
|
organizationId: company.organizationId,
|
|
934
934
|
tenantId: company.tenantId,
|
|
935
935
|
isActive: company.isActive,
|
|
936
|
+
temperature: company.temperature ?? null,
|
|
937
|
+
renewalQuarter: company.renewalQuarter ?? null,
|
|
936
938
|
createdAt: company.createdAt.toISOString(),
|
|
937
939
|
updatedAt: company.updatedAt.toISOString(),
|
|
938
940
|
},
|
|
@@ -1150,6 +1152,8 @@ const companyDetailResponseSchema = z.object({
|
|
|
1150
1152
|
organizationId: z.string().uuid().nullable().optional(),
|
|
1151
1153
|
tenantId: z.string().uuid().nullable().optional(),
|
|
1152
1154
|
isActive: z.boolean().optional(),
|
|
1155
|
+
temperature: z.string().nullable().optional(),
|
|
1156
|
+
renewalQuarter: z.string().nullable().optional(),
|
|
1153
1157
|
createdAt: z.string(),
|
|
1154
1158
|
updatedAt: z.string(),
|
|
1155
1159
|
}),
|
|
@@ -10,7 +10,7 @@ import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/u
|
|
|
10
10
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
11
11
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
12
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
13
|
-
import { AttachmentsSection, ErrorMessage, LoadingMessage, type SectionAction } from '@open-mercato/ui/backend/detail'
|
|
13
|
+
import { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState, type SectionAction } from '@open-mercato/ui/backend/detail'
|
|
14
14
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
15
15
|
import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
|
|
16
16
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
@@ -54,6 +54,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
54
54
|
const [data, setData] = React.useState<CompanyOverview | null>(null)
|
|
55
55
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
56
56
|
const [error, setError] = React.useState<string | null>(null)
|
|
57
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
57
58
|
|
|
58
59
|
// Tab state
|
|
59
60
|
const initialTab = React.useMemo(() => {
|
|
@@ -113,7 +114,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
113
114
|
const initialLoadDoneRef = React.useRef(false)
|
|
114
115
|
const loadData = React.useCallback(async () => {
|
|
115
116
|
if (!id) {
|
|
116
|
-
|
|
117
|
+
setIsNotFound(true)
|
|
117
118
|
setIsLoading(false)
|
|
118
119
|
return
|
|
119
120
|
}
|
|
@@ -121,6 +122,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
121
122
|
setIsLoading(true)
|
|
122
123
|
}
|
|
123
124
|
setError(null)
|
|
125
|
+
setIsNotFound(false)
|
|
124
126
|
try {
|
|
125
127
|
const payload = await readApiResultOrThrow<CompanyOverview>(
|
|
126
128
|
`/api/customers/companies/${encodeURIComponent(id)}`,
|
|
@@ -129,8 +131,12 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
129
131
|
)
|
|
130
132
|
setData(payload as CompanyOverview)
|
|
131
133
|
} catch (err) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
if ((err as { status?: number }).status === 404) {
|
|
135
|
+
setIsNotFound(true)
|
|
136
|
+
} else {
|
|
137
|
+
const message = err instanceof Error ? err.message : t('customers.companies.detail.error.load', 'Failed to load company.')
|
|
138
|
+
setError(message)
|
|
139
|
+
}
|
|
134
140
|
if (!initialLoadDoneRef.current) setData(null)
|
|
135
141
|
} finally {
|
|
136
142
|
setIsLoading(false)
|
|
@@ -373,12 +379,26 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
373
379
|
)
|
|
374
380
|
}
|
|
375
381
|
|
|
382
|
+
if (isNotFound) {
|
|
383
|
+
return (
|
|
384
|
+
<Page>
|
|
385
|
+
<PageBody>
|
|
386
|
+
<RecordNotFoundState
|
|
387
|
+
label={t('customers.companies.detail.error.notFound', 'Company not found.')}
|
|
388
|
+
backHref="/backend/customers/companies"
|
|
389
|
+
backLabel={t('customers.companies.detail.actions.backToList', 'Back to companies')}
|
|
390
|
+
/>
|
|
391
|
+
</PageBody>
|
|
392
|
+
</Page>
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
|
|
376
396
|
if (error || !data?.company?.id) {
|
|
377
397
|
return (
|
|
378
398
|
<Page>
|
|
379
399
|
<PageBody>
|
|
380
400
|
<ErrorMessage
|
|
381
|
-
label={error
|
|
401
|
+
label={error ?? t('customers.companies.detail.error.load', 'Failed to load company.')}
|
|
382
402
|
action={(
|
|
383
403
|
<Button asChild variant="outline">
|
|
384
404
|
<Link href="/backend/customers/companies">
|
|
@@ -16,7 +16,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
16
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
17
|
import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
18
18
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
19
|
-
import { AttachmentsSection, ErrorMessage, LoadingMessage, type SectionAction } from '@open-mercato/ui/backend/detail'
|
|
19
|
+
import { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState, type SectionAction } from '@open-mercato/ui/backend/detail'
|
|
20
20
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
21
21
|
import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
|
|
22
22
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
@@ -63,6 +63,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
63
63
|
const [data, setData] = React.useState<PersonOverview | null>(null)
|
|
64
64
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
65
65
|
const [error, setError] = React.useState<string | null>(null)
|
|
66
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
66
67
|
|
|
67
68
|
// Form state lifted for header Save button
|
|
68
69
|
const [isDirty, setIsDirty] = React.useState(false)
|
|
@@ -124,7 +125,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
124
125
|
const initialLoadDoneRef = React.useRef(false)
|
|
125
126
|
const loadData = React.useCallback(async () => {
|
|
126
127
|
if (!id) {
|
|
127
|
-
|
|
128
|
+
setIsNotFound(true)
|
|
128
129
|
setIsLoading(false)
|
|
129
130
|
return
|
|
130
131
|
}
|
|
@@ -132,6 +133,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
132
133
|
setIsLoading(true)
|
|
133
134
|
}
|
|
134
135
|
setError(null)
|
|
136
|
+
setIsNotFound(false)
|
|
135
137
|
try {
|
|
136
138
|
const payload = await readApiResultOrThrow<PersonOverview>(
|
|
137
139
|
`/api/customers/people/${encodeURIComponent(id)}`,
|
|
@@ -140,8 +142,12 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
140
142
|
)
|
|
141
143
|
setData(payload as PersonOverview)
|
|
142
144
|
} catch (err) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
if ((err as { status?: number }).status === 404) {
|
|
146
|
+
setIsNotFound(true)
|
|
147
|
+
} else {
|
|
148
|
+
const message = err instanceof Error ? err.message : t('customers.people.detail.error.load', 'Failed to load person.')
|
|
149
|
+
setError(message)
|
|
150
|
+
}
|
|
145
151
|
if (!initialLoadDoneRef.current) setData(null)
|
|
146
152
|
} finally {
|
|
147
153
|
setIsLoading(false)
|
|
@@ -375,12 +381,26 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
375
381
|
)
|
|
376
382
|
}
|
|
377
383
|
|
|
384
|
+
if (isNotFound) {
|
|
385
|
+
return (
|
|
386
|
+
<Page>
|
|
387
|
+
<PageBody>
|
|
388
|
+
<RecordNotFoundState
|
|
389
|
+
label={t('customers.people.detail.error.notFound', 'Person not found.')}
|
|
390
|
+
backHref="/backend/customers/people"
|
|
391
|
+
backLabel={t('customers.people.detail.actions.backToList', 'Back to people')}
|
|
392
|
+
/>
|
|
393
|
+
</PageBody>
|
|
394
|
+
</Page>
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
378
398
|
if (error || !data?.person?.id || !initialValues) {
|
|
379
399
|
return (
|
|
380
400
|
<Page>
|
|
381
401
|
<PageBody>
|
|
382
402
|
<ErrorMessage
|
|
383
|
-
label={error
|
|
403
|
+
label={error ?? t('customers.people.detail.error.load', 'Failed to load person.')}
|
|
384
404
|
action={(
|
|
385
405
|
<Button asChild variant="outline">
|
|
386
406
|
<Link href="/backend/customers/people">
|
|
@@ -201,6 +201,8 @@ type CompanySnapshot = {
|
|
|
201
201
|
status: string | null
|
|
202
202
|
lifecycleStage: string | null
|
|
203
203
|
source: string | null
|
|
204
|
+
temperature: string | null
|
|
205
|
+
renewalQuarter: string | null
|
|
204
206
|
nextInteractionAt: Date | null
|
|
205
207
|
nextInteractionName: string | null
|
|
206
208
|
nextInteractionRefId: string | null
|
|
@@ -297,6 +299,8 @@ async function loadCompanySnapshot(em: EntityManager, id: string): Promise<Compa
|
|
|
297
299
|
status: entity.status ?? null,
|
|
298
300
|
lifecycleStage: entity.lifecycleStage ?? null,
|
|
299
301
|
source: entity.source ?? null,
|
|
302
|
+
temperature: entity.temperature ?? null,
|
|
303
|
+
renewalQuarter: entity.renewalQuarter ?? null,
|
|
300
304
|
nextInteractionAt: entity.nextInteractionAt ?? null,
|
|
301
305
|
nextInteractionName: entity.nextInteractionName ?? null,
|
|
302
306
|
nextInteractionRefId: entity.nextInteractionRefId ?? null,
|
|
@@ -499,6 +503,8 @@ const createCompanyCommand: CommandHandler<CompanyCreateInput, { entityId: strin
|
|
|
499
503
|
nextInteractionIcon,
|
|
500
504
|
nextInteractionColor,
|
|
501
505
|
isActive: parsed.isActive ?? true,
|
|
506
|
+
temperature: parsed.temperature ?? null,
|
|
507
|
+
renewalQuarter: parsed.renewalQuarter ?? null,
|
|
502
508
|
})
|
|
503
509
|
|
|
504
510
|
const profile = em.create(CustomerCompanyProfile, {
|
|
@@ -613,41 +619,44 @@ const updateCompanyCommand: CommandHandler<CompanyUpdateInput, { entityId: strin
|
|
|
613
619
|
const profile = await em.findOne(CustomerCompanyProfile, { entity: record })
|
|
614
620
|
if (!profile) throw new CrudHttpError(404, { error: 'Company profile not found' })
|
|
615
621
|
|
|
616
|
-
if (parsed.displayName !== undefined) record.displayName = parsed.displayName
|
|
617
|
-
if (parsed.description !== undefined) record.description = parsed.description ?? null
|
|
618
|
-
if (parsed.ownerUserId !== undefined) record.ownerUserId = parsed.ownerUserId ?? null
|
|
619
|
-
if (parsed.primaryEmail !== undefined) record.primaryEmail = parsed.primaryEmail ?? null
|
|
620
|
-
if (parsed.primaryPhone !== undefined) record.primaryPhone = normalizeOptionalString(parsed.primaryPhone)
|
|
621
|
-
if (parsed.status !== undefined) record.status = parsed.status ?? null
|
|
622
|
-
if (parsed.lifecycleStage !== undefined) record.lifecycleStage = parsed.lifecycleStage ?? null
|
|
623
|
-
if (parsed.source !== undefined) record.source = parsed.source ?? null
|
|
624
|
-
if (parsed.isActive !== undefined) record.isActive = parsed.isActive
|
|
625
|
-
|
|
626
|
-
if (parsed.nextInteraction) {
|
|
627
|
-
record.nextInteractionAt = parsed.nextInteraction.at
|
|
628
|
-
record.nextInteractionName = parsed.nextInteraction.name.trim()
|
|
629
|
-
record.nextInteractionRefId = normalizeOptionalString(parsed.nextInteraction.refId) ?? null
|
|
630
|
-
record.nextInteractionIcon = normalizeOptionalString(parsed.nextInteraction.icon)
|
|
631
|
-
record.nextInteractionColor = normalizeHexColor(parsed.nextInteraction.color)
|
|
632
|
-
} else if (parsed.nextInteraction === null) {
|
|
633
|
-
record.nextInteractionAt = null
|
|
634
|
-
record.nextInteractionName = null
|
|
635
|
-
record.nextInteractionRefId = null
|
|
636
|
-
record.nextInteractionIcon = null
|
|
637
|
-
record.nextInteractionColor = null
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (parsed.legalName !== undefined) profile.legalName = parsed.legalName ?? null
|
|
641
|
-
if (parsed.brandName !== undefined) profile.brandName = parsed.brandName ?? null
|
|
642
|
-
if (parsed.domain !== undefined) profile.domain = parsed.domain ?? null
|
|
643
|
-
if (parsed.websiteUrl !== undefined) profile.websiteUrl = parsed.websiteUrl ?? null
|
|
644
|
-
if (parsed.industry !== undefined) profile.industry = parsed.industry ?? null
|
|
645
|
-
if (parsed.sizeBucket !== undefined) profile.sizeBucket = parsed.sizeBucket ?? null
|
|
646
|
-
if (parsed.annualRevenue !== undefined) {
|
|
647
|
-
profile.annualRevenue = parsed.annualRevenue !== null && parsed.annualRevenue !== undefined ? String(parsed.annualRevenue) : null
|
|
648
|
-
}
|
|
649
|
-
|
|
650
622
|
await withAtomicFlush(em, [
|
|
623
|
+
() => {
|
|
624
|
+
if (parsed.displayName !== undefined) record.displayName = parsed.displayName
|
|
625
|
+
if (parsed.description !== undefined) record.description = parsed.description ?? null
|
|
626
|
+
if (parsed.ownerUserId !== undefined) record.ownerUserId = parsed.ownerUserId ?? null
|
|
627
|
+
if (parsed.primaryEmail !== undefined) record.primaryEmail = parsed.primaryEmail ?? null
|
|
628
|
+
if (parsed.primaryPhone !== undefined) record.primaryPhone = normalizeOptionalString(parsed.primaryPhone)
|
|
629
|
+
if (parsed.status !== undefined) record.status = parsed.status ?? null
|
|
630
|
+
if (parsed.lifecycleStage !== undefined) record.lifecycleStage = parsed.lifecycleStage ?? null
|
|
631
|
+
if (parsed.source !== undefined) record.source = parsed.source ?? null
|
|
632
|
+
if (parsed.isActive !== undefined) record.isActive = parsed.isActive
|
|
633
|
+
if (parsed.temperature !== undefined) record.temperature = parsed.temperature ?? null
|
|
634
|
+
if (parsed.renewalQuarter !== undefined) record.renewalQuarter = parsed.renewalQuarter ?? null
|
|
635
|
+
|
|
636
|
+
if (parsed.nextInteraction) {
|
|
637
|
+
record.nextInteractionAt = parsed.nextInteraction.at
|
|
638
|
+
record.nextInteractionName = parsed.nextInteraction.name.trim()
|
|
639
|
+
record.nextInteractionRefId = normalizeOptionalString(parsed.nextInteraction.refId) ?? null
|
|
640
|
+
record.nextInteractionIcon = normalizeOptionalString(parsed.nextInteraction.icon)
|
|
641
|
+
record.nextInteractionColor = normalizeHexColor(parsed.nextInteraction.color)
|
|
642
|
+
} else if (parsed.nextInteraction === null) {
|
|
643
|
+
record.nextInteractionAt = null
|
|
644
|
+
record.nextInteractionName = null
|
|
645
|
+
record.nextInteractionRefId = null
|
|
646
|
+
record.nextInteractionIcon = null
|
|
647
|
+
record.nextInteractionColor = null
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (parsed.legalName !== undefined) profile.legalName = parsed.legalName ?? null
|
|
651
|
+
if (parsed.brandName !== undefined) profile.brandName = parsed.brandName ?? null
|
|
652
|
+
if (parsed.domain !== undefined) profile.domain = parsed.domain ?? null
|
|
653
|
+
if (parsed.websiteUrl !== undefined) profile.websiteUrl = parsed.websiteUrl ?? null
|
|
654
|
+
if (parsed.industry !== undefined) profile.industry = parsed.industry ?? null
|
|
655
|
+
if (parsed.sizeBucket !== undefined) profile.sizeBucket = parsed.sizeBucket ?? null
|
|
656
|
+
if (parsed.annualRevenue !== undefined) {
|
|
657
|
+
profile.annualRevenue = parsed.annualRevenue !== null && parsed.annualRevenue !== undefined ? String(parsed.annualRevenue) : null
|
|
658
|
+
}
|
|
659
|
+
},
|
|
651
660
|
() => syncEntityTags(em, record, parsed.tags),
|
|
652
661
|
], { transaction: true })
|
|
653
662
|
|
|
@@ -718,6 +727,8 @@ const updateCompanyCommand: CommandHandler<CompanyUpdateInput, { entityId: strin
|
|
|
718
727
|
status: before.entity.status,
|
|
719
728
|
lifecycleStage: before.entity.lifecycleStage,
|
|
720
729
|
source: before.entity.source,
|
|
730
|
+
temperature: before.entity.temperature,
|
|
731
|
+
renewalQuarter: before.entity.renewalQuarter,
|
|
721
732
|
nextInteractionAt: before.entity.nextInteractionAt,
|
|
722
733
|
nextInteractionName: before.entity.nextInteractionName,
|
|
723
734
|
nextInteractionRefId: before.entity.nextInteractionRefId,
|
|
@@ -735,6 +746,8 @@ const updateCompanyCommand: CommandHandler<CompanyUpdateInput, { entityId: strin
|
|
|
735
746
|
entity.status = before.entity.status
|
|
736
747
|
entity.lifecycleStage = before.entity.lifecycleStage
|
|
737
748
|
entity.source = before.entity.source
|
|
749
|
+
entity.temperature = before.entity.temperature
|
|
750
|
+
entity.renewalQuarter = before.entity.renewalQuarter
|
|
738
751
|
entity.nextInteractionAt = before.entity.nextInteractionAt
|
|
739
752
|
entity.nextInteractionName = before.entity.nextInteractionName
|
|
740
753
|
entity.nextInteractionRefId = before.entity.nextInteractionRefId
|
|
@@ -1045,6 +1058,8 @@ const deleteCompanyCommand: CommandHandler<{ body?: Record<string, unknown>; que
|
|
|
1045
1058
|
status: before.entity.status,
|
|
1046
1059
|
lifecycleStage: before.entity.lifecycleStage,
|
|
1047
1060
|
source: before.entity.source,
|
|
1061
|
+
temperature: before.entity.temperature,
|
|
1062
|
+
renewalQuarter: before.entity.renewalQuarter,
|
|
1048
1063
|
nextInteractionAt: before.entity.nextInteractionAt,
|
|
1049
1064
|
nextInteractionName: before.entity.nextInteractionName,
|
|
1050
1065
|
nextInteractionRefId: before.entity.nextInteractionRefId,
|
|
@@ -1063,6 +1078,8 @@ const deleteCompanyCommand: CommandHandler<{ body?: Record<string, unknown>; que
|
|
|
1063
1078
|
entity.status = before.entity.status
|
|
1064
1079
|
entity.lifecycleStage = before.entity.lifecycleStage
|
|
1065
1080
|
entity.source = before.entity.source
|
|
1081
|
+
entity.temperature = before.entity.temperature
|
|
1082
|
+
entity.renewalQuarter = before.entity.renewalQuarter
|
|
1066
1083
|
entity.nextInteractionAt = before.entity.nextInteractionAt
|
|
1067
1084
|
entity.nextInteractionName = before.entity.nextInteractionName
|
|
1068
1085
|
entity.nextInteractionRefId = before.entity.nextInteractionRefId
|
|
@@ -12,8 +12,7 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
|
12
12
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
13
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
14
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
|
-
import { LoadingMessage } from '@open-mercato/ui/backend/detail'
|
|
16
|
-
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
15
|
+
import { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
|
|
17
16
|
import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
|
|
18
17
|
import { RotateCcw, XCircle } from 'lucide-react'
|
|
19
18
|
|
|
@@ -106,6 +105,7 @@ export default function SyncRunDetailPage({ params }: SyncRunDetailPageProps) {
|
|
|
106
105
|
const [run, setRun] = React.useState<SyncRunDetail | null>(null)
|
|
107
106
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
108
107
|
const [error, setError] = React.useState<string | null>(null)
|
|
108
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
109
109
|
const [logs, setLogs] = React.useState<LogEntry[]>([])
|
|
110
110
|
const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)
|
|
111
111
|
|
|
@@ -124,13 +124,18 @@ export default function SyncRunDetailPage({ params }: SyncRunDetailPageProps) {
|
|
|
124
124
|
setIsLoading(false)
|
|
125
125
|
return
|
|
126
126
|
}
|
|
127
|
+
setIsNotFound(false)
|
|
127
128
|
const call = await apiCall<SyncRunDetail>(
|
|
128
129
|
`/api/data_sync/runs/${encodeURIComponent(currentRunId)}`,
|
|
129
130
|
undefined,
|
|
130
131
|
{ fallback: null },
|
|
131
132
|
)
|
|
132
133
|
if (!call.ok || !call.result) {
|
|
133
|
-
|
|
134
|
+
if (call.status === 404) {
|
|
135
|
+
setIsNotFound(true)
|
|
136
|
+
} else {
|
|
137
|
+
setError(t('data_sync.runs.detail.loadError'))
|
|
138
|
+
}
|
|
134
139
|
setIsLoading(false)
|
|
135
140
|
return
|
|
136
141
|
}
|
|
@@ -243,6 +248,19 @@ export default function SyncRunDetailPage({ params }: SyncRunDetailPageProps) {
|
|
|
243
248
|
}, [resolveCurrentRunId, router, t])
|
|
244
249
|
|
|
245
250
|
if (isLoading) return <Page><PageBody><LoadingMessage label={t('data_sync.runs.detail.title')} /></PageBody></Page>
|
|
251
|
+
if (isNotFound) {
|
|
252
|
+
return (
|
|
253
|
+
<Page>
|
|
254
|
+
<PageBody>
|
|
255
|
+
<RecordNotFoundState
|
|
256
|
+
label={t('data_sync.runs.detail.notFound', 'Sync run not found.')}
|
|
257
|
+
backHref="/backend/data-sync"
|
|
258
|
+
backLabel={t('data_sync.runs.detail.back')}
|
|
259
|
+
/>
|
|
260
|
+
</PageBody>
|
|
261
|
+
</Page>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
246
264
|
if (error || !run) return <Page><PageBody><ErrorMessage label={error ?? t('data_sync.runs.detail.loadError')} /></PageBody></Page>
|
|
247
265
|
|
|
248
266
|
const totalProcessed = run.createdCount + run.updatedCount + run.skippedCount + run.failedCount
|
|
@@ -63,7 +63,10 @@ export async function GET(req: Request) {
|
|
|
63
63
|
|
|
64
64
|
const byId = new Map<string, any>()
|
|
65
65
|
for (const g of generated) byId.set(g.entityId, g)
|
|
66
|
-
for (const cu of custom)
|
|
66
|
+
for (const cu of custom) {
|
|
67
|
+
const existing = byId.get(cu.entityId)
|
|
68
|
+
byId.set(cu.entityId, { ...existing, ...cu, source: existing?.source ?? cu.source })
|
|
69
|
+
}
|
|
67
70
|
|
|
68
71
|
// Count field definitions scoped to current tenant/org (same scoping as custom entities)
|
|
69
72
|
const defsWhere: any = { isActive: true }
|
|
@@ -473,20 +473,13 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
473
473
|
flash('Please fix validation errors in field definitions', 'error')
|
|
474
474
|
throw createCrudFormError('Please fix validation errors in field definitions')
|
|
475
475
|
}
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
.extend({ showInSidebar: z.boolean().optional() }) as unknown as z.ZodTypeAny
|
|
480
|
-
const normalized = {
|
|
481
|
-
...(vals as any),
|
|
482
|
-
defaultEditor: (vals as any)?.defaultEditor || undefined,
|
|
483
|
-
}
|
|
484
|
-
const parsed = partial.safeParse(normalized)
|
|
485
|
-
if (!parsed.success) throw createCrudFormError('Validation failed')
|
|
476
|
+
{
|
|
477
|
+
const entityPayload = buildEntityMetadataPayload(entitySource, vals)
|
|
478
|
+
if (!entityPayload) throw createCrudFormError('Validation failed')
|
|
486
479
|
const callEntity = await apiCall('/api/entities/entities', {
|
|
487
480
|
method: 'POST',
|
|
488
481
|
headers: { 'content-type': 'application/json' },
|
|
489
|
-
body: JSON.stringify({ entityId, ...
|
|
482
|
+
body: JSON.stringify({ entityId, ...entityPayload }),
|
|
490
483
|
})
|
|
491
484
|
if (!callEntity.ok) {
|
|
492
485
|
await raiseCrudError(callEntity.response, 'Failed to save entity')
|
|
@@ -603,3 +596,21 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
603
596
|
</Page>
|
|
604
597
|
)
|
|
605
598
|
}
|
|
599
|
+
|
|
600
|
+
export function buildEntityMetadataPayload(
|
|
601
|
+
entitySource: 'code' | 'custom',
|
|
602
|
+
vals: Record<string, unknown>,
|
|
603
|
+
): Record<string, unknown> | null {
|
|
604
|
+
const partial = entitySource === 'custom'
|
|
605
|
+
? upsertCustomEntitySchema
|
|
606
|
+
.pick({ label: true, description: true, labelField: true as any, defaultEditor: true as any })
|
|
607
|
+
.extend({ showInSidebar: z.boolean().optional() }) as unknown as z.ZodTypeAny
|
|
608
|
+
: upsertCustomEntitySchema
|
|
609
|
+
.pick({ label: true, description: true, defaultEditor: true as any }) as unknown as z.ZodTypeAny
|
|
610
|
+
const normalized = {
|
|
611
|
+
...vals,
|
|
612
|
+
defaultEditor: typeof vals.defaultEditor === 'string' && vals.defaultEditor ? vals.defaultEditor : undefined,
|
|
613
|
+
}
|
|
614
|
+
const parsed = partial.safeParse(normalized)
|
|
615
|
+
return parsed.success ? (parsed.data as Record<string, unknown>) : null
|
|
616
|
+
}
|