@open-mercato/core 0.6.8-develop.7046.1.153faed87a → 0.6.8-develop.7048.1.b011912b63
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/helpers/integration/api.js +3 -1
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/api.ts +13 -9
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
|
@@ -91,7 +91,9 @@ async function postForm(request, path, data, options) {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
async function withCredentialIsolatedRequest(use) {
|
|
94
|
-
const context = await playwrightRequest.newContext(
|
|
94
|
+
const context = await playwrightRequest.newContext({
|
|
95
|
+
baseURL: BASE_URL ?? "http://localhost:3000"
|
|
96
|
+
});
|
|
95
97
|
try {
|
|
96
98
|
return await use(context);
|
|
97
99
|
} finally {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/api.ts"],
|
|
4
|
-
"sourcesContent": ["import { request as playwrightRequest, type APIRequestContext } from '@playwright/test';\nimport { DEFAULT_CREDENTIALS, type Role } from './auth';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || null;\n\nfunction resolveUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path;\n}\n\n// Cached tokens per credential to dodge the login rate limit\n// (5 attempts/60s per email). Tokens are reused across tests in the same\n// Playwright worker; each worker still mints its own.\nconst tokenCache = new Map<string, { token: string; mintedAt: number }>();\nconst TOKEN_TTL_MS = 45 * 60 * 1000; // 45 min; well under the default 2h session TTL.\n\n/**\n * Drops cached tokens so the next getAuthToken() mints a fresh session.\n *\n * A cached token points at a `sessions` row. Anything that deletes those rows \u2014\n * deactivating the user, a password change, an explicit session purge \u2014 leaves the\n * cached token resolving to 401 for the rest of the TTL, and `apiRequest` neither\n * retries nor re-mints. A spec that revokes sessions for an account other specs also\n * use MUST call this afterwards.\n */\nexport function clearAuthTokenCache(): void {\n tokenCache.clear();\n}\n\nexport async function getAuthToken(\n request: APIRequestContext,\n roleOrEmail: Role | string = 'admin',\n password?: string,\n): Promise<string> {\n const role = roleOrEmail in DEFAULT_CREDENTIALS ? (roleOrEmail as Role) : null;\n const credentialAttempts: Array<{ email: string; password: string }> = [];\n\n if (role) {\n const configured = DEFAULT_CREDENTIALS[role];\n credentialAttempts.push({ email: configured.email, password: password ?? configured.password });\n if (!password) {\n credentialAttempts.push({ email: `${role}@acme.com`, password: 'secret' });\n }\n } else {\n credentialAttempts.push({ email: roleOrEmail, password: password ?? 'secret' });\n }\n\n const cacheKey = credentialAttempts\n .map((entry) => `${entry.email}:${entry.password}`)\n .join('|');\n const cached = tokenCache.get(cacheKey);\n if (cached && Date.now() - cached.mintedAt < TOKEN_TTL_MS) {\n return cached.token;\n }\n\n let lastStatus = 0;\n\n for (const attempt of credentialAttempts) {\n const form = new URLSearchParams();\n form.set('email', attempt.email);\n form.set('password', attempt.password);\n\n // Retry on 429 (auth rate limit kicks in after ~25-30 rapid attempts from\n // the same test run). Capped exponential backoff: 1s, 2s, 4s; 3 retries.\n for (let retry = 0; retry < 4; retry += 1) {\n const response = await request.post(resolveUrl('/api/auth/login'), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: form.toString(),\n });\n\n const raw = await response.text();\n let body: Record<string, unknown> | null = null;\n try {\n body = raw ? (JSON.parse(raw) as Record<string, unknown>) : null;\n } catch {\n body = null;\n }\n\n lastStatus = response.status();\n if (response.ok() && body && typeof body.token === 'string' && body.token) {\n tokenCache.set(cacheKey, { token: body.token, mintedAt: Date.now() });\n return body.token;\n }\n if (response.status() !== 429) break;\n const backoffMs = 1000 * 2 ** retry;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n }\n\n throw new Error(`Failed to obtain auth token (status ${lastStatus})`);\n}\n\nexport async function apiRequest(\n request: APIRequestContext,\n method: string,\n path: string,\n options: {\n token: string;\n data?: unknown;\n timeout?: number;\n retryTransport?: boolean;\n headers?: Record<string, string>;\n },\n) {\n const headers = {\n Authorization: `Bearer ${options.token}`,\n 'Content-Type': 'application/json',\n ...(options.headers ?? {}),\n };\n const timeout = options.timeout ?? 30_000;\n let lastError: unknown = null;\n const maxAttempts = options.retryTransport === false ? 1 : 2;\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n try {\n return await request.fetch(resolveUrl(path), { method, headers, data: options.data, timeout });\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : '';\n const retryable = /timeout|idle-session|socket|ECONNRESET|Target page, context or browser has been closed/i.test(message);\n if (!retryable || attempt === maxAttempts - 1) throw error;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n throw lastError;\n}\n\nexport async function postForm(\n request: APIRequestContext,\n path: string,\n data: Record<string, string>,\n options?: { headers?: Record<string, string> },\n) {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(data)) form.set(key, value);\n return request.post(resolveUrl(path), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n ...(options?.headers ?? {}),\n },\n data: form.toString(),\n });\n}\n\n/**\n * Runs `use` against a request context that shares no cookies with the caller's.\n *\n * The `request` fixture keeps a cookie jar, and `/api/auth/login` sets `auth_token`\n * on it. Every later call through that fixture therefore carries the LAST logged-in\n * user's session, even one that deliberately sends no Authorization header or an\n * `ApiKey` one
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { request as playwrightRequest, type APIRequestContext } from '@playwright/test';\nimport { DEFAULT_CREDENTIALS, type Role } from './auth';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || null;\n\nfunction resolveUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path;\n}\n\n// Cached tokens per credential to dodge the login rate limit\n// (5 attempts/60s per email). Tokens are reused across tests in the same\n// Playwright worker; each worker still mints its own.\nconst tokenCache = new Map<string, { token: string; mintedAt: number }>();\nconst TOKEN_TTL_MS = 45 * 60 * 1000; // 45 min; well under the default 2h session TTL.\n\n/**\n * Drops cached tokens so the next getAuthToken() mints a fresh session.\n *\n * A cached token points at a `sessions` row. Anything that deletes those rows \u2014\n * deactivating the user, a password change, an explicit session purge \u2014 leaves the\n * cached token resolving to 401 for the rest of the TTL, and `apiRequest` neither\n * retries nor re-mints. A spec that revokes sessions for an account other specs also\n * use MUST call this afterwards.\n */\nexport function clearAuthTokenCache(): void {\n tokenCache.clear();\n}\n\nexport async function getAuthToken(\n request: APIRequestContext,\n roleOrEmail: Role | string = 'admin',\n password?: string,\n): Promise<string> {\n const role = roleOrEmail in DEFAULT_CREDENTIALS ? (roleOrEmail as Role) : null;\n const credentialAttempts: Array<{ email: string; password: string }> = [];\n\n if (role) {\n const configured = DEFAULT_CREDENTIALS[role];\n credentialAttempts.push({ email: configured.email, password: password ?? configured.password });\n if (!password) {\n credentialAttempts.push({ email: `${role}@acme.com`, password: 'secret' });\n }\n } else {\n credentialAttempts.push({ email: roleOrEmail, password: password ?? 'secret' });\n }\n\n const cacheKey = credentialAttempts\n .map((entry) => `${entry.email}:${entry.password}`)\n .join('|');\n const cached = tokenCache.get(cacheKey);\n if (cached && Date.now() - cached.mintedAt < TOKEN_TTL_MS) {\n return cached.token;\n }\n\n let lastStatus = 0;\n\n for (const attempt of credentialAttempts) {\n const form = new URLSearchParams();\n form.set('email', attempt.email);\n form.set('password', attempt.password);\n\n // Retry on 429 (auth rate limit kicks in after ~25-30 rapid attempts from\n // the same test run). Capped exponential backoff: 1s, 2s, 4s; 3 retries.\n for (let retry = 0; retry < 4; retry += 1) {\n const response = await request.post(resolveUrl('/api/auth/login'), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: form.toString(),\n });\n\n const raw = await response.text();\n let body: Record<string, unknown> | null = null;\n try {\n body = raw ? (JSON.parse(raw) as Record<string, unknown>) : null;\n } catch {\n body = null;\n }\n\n lastStatus = response.status();\n if (response.ok() && body && typeof body.token === 'string' && body.token) {\n tokenCache.set(cacheKey, { token: body.token, mintedAt: Date.now() });\n return body.token;\n }\n if (response.status() !== 429) break;\n const backoffMs = 1000 * 2 ** retry;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n }\n\n throw new Error(`Failed to obtain auth token (status ${lastStatus})`);\n}\n\nexport async function apiRequest(\n request: APIRequestContext,\n method: string,\n path: string,\n options: {\n token: string;\n data?: unknown;\n timeout?: number;\n retryTransport?: boolean;\n headers?: Record<string, string>;\n },\n) {\n const headers = {\n Authorization: `Bearer ${options.token}`,\n 'Content-Type': 'application/json',\n ...(options.headers ?? {}),\n };\n const timeout = options.timeout ?? 30_000;\n let lastError: unknown = null;\n const maxAttempts = options.retryTransport === false ? 1 : 2;\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n try {\n return await request.fetch(resolveUrl(path), { method, headers, data: options.data, timeout });\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : '';\n const retryable = /timeout|idle-session|socket|ECONNRESET|Target page, context or browser has been closed/i.test(message);\n if (!retryable || attempt === maxAttempts - 1) throw error;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n throw lastError;\n}\n\nexport async function postForm(\n request: APIRequestContext,\n path: string,\n data: Record<string, string>,\n options?: { headers?: Record<string, string> },\n) {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(data)) form.set(key, value);\n return request.post(resolveUrl(path), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n ...(options?.headers ?? {}),\n },\n data: form.toString(),\n });\n}\n\n/**\n * Runs `use` against a request context that shares no cookies with the caller's.\n *\n * The `request` fixture keeps a cookie jar, and `/api/auth/login` sets `auth_token`\n * on it. Every later call through that fixture therefore carries the LAST logged-in\n * user's session, even one that deliberately sends no Authorization header or an\n * `ApiKey` one.\n *\n * A spec asserting \"no credentials are rejected\" or \"this API key alone decides\n * access\" must therefore issue the request from a jar that never saw a login,\n * rather than assuming the fixture's jar is empty. TC-DOCUMENTS-009 and\n * TC-DOCUMENTS-018 both observably failed in the standalone lane \u2014 with the\n * replayed cookie visible in the trace \u2014 while passing in the ephemeral one, and\n * routing them through this helper fixed both.\n */\nexport async function withCredentialIsolatedRequest<T>(\n use: (request: APIRequestContext) => Promise<T>,\n): Promise<T> {\n // A hand-built context does not inherit the project's `use.baseURL` the way the\n // `request` fixture does, so it has to repeat the config's own resolution \u2014 otherwise\n // a relative path throws here on any run that leaves BASE_URL unset, while the same\n // path works through the fixture.\n const context = await playwrightRequest.newContext({\n baseURL: BASE_URL ?? 'http://localhost:3000',\n });\n try {\n return await use(context);\n } finally {\n await context.dispose();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { extensionPoints } from "@open-mercato/core/modules/customers/extension-points";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
-
import { Building2, UserSearch, Users } from "lucide-react";
|
|
6
|
+
import { Building2, FileText, Hash, UserSearch, Users } from "lucide-react";
|
|
7
7
|
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
8
8
|
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
9
9
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
@@ -52,6 +52,10 @@ function DealDetailPage({ params }) {
|
|
|
52
52
|
const pathname = usePathname();
|
|
53
53
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
54
54
|
const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t]);
|
|
55
|
+
const zoneSections = React.useMemo(() => [
|
|
56
|
+
{ id: "details", icon: FileText, label: t("customers.people.detail.deals.form.details", "Deal details") },
|
|
57
|
+
{ id: "custom", icon: Hash, label: t("customers.people.detail.deals.form.customFields", "Custom fields") }
|
|
58
|
+
], [t]);
|
|
55
59
|
const { data, setData, isLoading, error, isNotFound, loadData } = useDealData(id);
|
|
56
60
|
const [isDirty, setIsDirty] = React.useState(false);
|
|
57
61
|
const {
|
|
@@ -580,6 +584,7 @@ function DealDetailPage({ params }) {
|
|
|
580
584
|
pageType: "deal-detail-v3",
|
|
581
585
|
entityName: dealName,
|
|
582
586
|
isDirty,
|
|
587
|
+
sections: zoneSections,
|
|
583
588
|
zone1DefaultWidth: "540px",
|
|
584
589
|
zone1: zone1Content,
|
|
585
590
|
zone2: zone2Content
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/backend/customers/deals/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/customers/extension-points'\nimport Link from 'next/link'\nimport { Building2, UserSearch, Users } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { useRouter, useSearchParams, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { CollapsibleZoneLayout } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { E } from '#generated/entities.ids.generated'\n\nimport { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'\nimport { ChangelogTab } from '../../../../components/detail/ChangelogTab'\nimport { DealClosureActionBar } from '../../../../components/detail/DealClosureActionBar'\nimport { DealDetailHeader } from '../../../../components/detail/DealDetailHeader'\nimport { DealDetailTabs, resolveLegacyTab, type DealTabId } from '../../../../components/detail/DealDetailTabs'\nimport { DealForm, useDealAssociationLookups } from '../../../../components/detail/DealForm'\nimport { DealLinkedEntitiesTab } from '../../../../components/detail/DealLinkedEntitiesTab'\nimport { ConfirmDealLostDialog } from '../../../../components/detail/ConfirmDealLostDialog'\nimport { DealLostSummaryDialog } from '../../../../components/detail/DealLostSummaryDialog'\nimport { DealWonPopup } from '../../../../components/detail/DealWonPopup'\nimport { InlineActivityComposer } from '../../../../components/detail/InlineActivityComposer'\nimport { PipelineStepper } from '../../../../components/detail/PipelineStepper'\nimport { PlannedActivitiesSection } from '../../../../components/detail/PlannedActivitiesSection'\nimport { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'\nimport { createCustomerNotesAdapter } from '../../../../components/detail/notesAdapter'\nimport type { InteractionSummary } from '../../../../components/detail/types'\nimport { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '../../../../lib/markdownPreference'\nimport { ICON_SUGGESTIONS } from '../../../../lib/dictionaries'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nimport { formatCurrency, startOfNextQuarter } from './hooks/formatters'\nimport type { DealDetailPayload } from './hooks/types'\nimport { useDealActivities } from './hooks/useDealActivities'\nimport { useDealAssociations } from './hooks/useDealAssociations'\nimport { useDealClosure } from './hooks/useDealClosure'\nimport { useDealData } from './hooks/useDealData'\nimport { useDealFormHandlers } from './hooks/useDealFormHandlers'\nimport { useDealInjectedTabs } from './hooks/useDealInjectedTabs'\nimport { useDealMutationContext } from './hooks/useDealMutationContext'\nimport { useDealPipeline } from './hooks/useDealPipeline'\nimport { useScheduleDialog } from './hooks/useScheduleDialog'\n\nexport default function DealDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id ?? ''\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n\n const { data, setData, isLoading, error, isNotFound, loadData } = useDealData(id)\n const [isDirty, setIsDirty] = React.useState(false)\n const {\n scheduleDialogOpen,\n scheduleEditData,\n openSchedule,\n openEdit: openScheduleEdit,\n closeSchedule,\n } = useScheduleDialog()\n const formWrapperRef = React.useRef<HTMLDivElement>(null)\n\n const currentDealId = data?.deal.id ?? id\n const { injectionContext, runMutationWithContext } = useDealMutationContext({\n currentDealId,\n fallbackId: id,\n data,\n })\n\n // Publish page-load record context with explicit `updatedAt`/`data` to the\n // AppShell-owned `backend:record:current` mount. The deal path is already in the\n // widget's allowlist (presence works) but supplied no version/record payload \u2014\n // this gives the merge dialog its action-log base and field-diff source.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'customers.deal',\n resourceId: data?.deal.id ?? null,\n updatedAt: data?.deal.updatedAt ?? null,\n data: data as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n const notesAdapter = React.useMemo(\n () => createCustomerNotesAdapter(detailTranslator, { runMutation: runMutationWithContext }),\n [detailTranslator, runMutationWithContext],\n )\n\n const { injectedTabs, injectedTabMap } = useDealInjectedTabs({\n injectionContext,\n data,\n setData,\n })\n\n const injectedTabIds = React.useMemo(() => injectedTabs.map((tab) => tab.id), [injectedTabs])\n const initialTab = React.useMemo(\n () => resolveLegacyTab(searchParams?.get('tab'), injectedTabIds),\n [injectedTabIds, searchParams],\n )\n const [activeTab, setActiveTab] = React.useState<DealTabId>(initialTab)\n\n React.useEffect(() => {\n setActiveTab(initialTab)\n }, [initialTab])\n\n const { searchPeoplePage, fetchPeopleByIds, searchCompaniesPage, fetchCompaniesByIds } = useDealAssociationLookups({\n excludeLinkedDealId: data?.deal.id ?? null,\n })\n\n const {\n plannedActivities,\n activityRefreshKey,\n loadPlannedActivities,\n handleActivityCreated,\n handleMarkDone,\n handleCancelActivity,\n } = useDealActivities({ dealId: id, runMutationWithContext })\n\n React.useEffect(() => {\n void Promise.all([loadData({ cache: true }), loadPlannedActivities({ cache: true })])\n }, [loadData, loadPlannedActivities])\n\n const activityEntities = React.useMemo(\n () => (data\n ? [...data.people, ...data.companies].map((entry) => ({\n id: entry.id,\n label: entry.subtitle ? `${entry.label} \u00B7 ${entry.subtitle}` : entry.label,\n kind: entry.kind,\n isPrimary: entry.isPrimary === true,\n }))\n : []),\n [data],\n )\n const [selectedActivityEntityId, setSelectedActivityEntityId] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n setSelectedActivityEntityId((current) => {\n if (activityEntities.length === 1) return activityEntities[0].id\n if (current && activityEntities.some((entry) => entry.id === current)) return current\n const primary = activityEntities.find((entry) => entry.isPrimary)\n return (primary ?? activityEntities[0])?.id ?? null\n })\n }, [activityEntities])\n\n const selectedActivityEntity = React.useMemo(\n () => activityEntities.find((entry) => entry.id === selectedActivityEntityId) ?? null,\n [activityEntities, selectedActivityEntityId],\n )\n\n const dealOptions = React.useMemo(\n () => data ? [{ id: data.deal.id, label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal') }] : [],\n [data, t],\n )\n\n const entityOptions = React.useMemo(\n () => activityEntities.map(({ id, label }) => ({ id, label })),\n [activityEntities],\n )\n\n const confirmDiscardIfDirty = React.useCallback(async () => {\n if (!isDirty) return true\n return confirm({\n title: t('customers.deals.detail.unsavedTitle', 'Discard unsaved changes?'),\n description: t(\n 'customers.deals.detail.unsavedDescription',\n 'You have unsaved edits in this deal. Save them first or continue to discard them.',\n ),\n confirmText: t('customers.deals.detail.unsavedConfirm', 'Discard changes'),\n cancelText: t('customers.deals.detail.unsavedCancel', 'Keep editing'),\n variant: 'destructive',\n })\n }, [confirm, isDirty, t])\n\n const {\n peopleEditorIds,\n companiesEditorIds,\n peopleSaving,\n companiesSaving,\n handlePeopleAssociationsChange,\n handleCompaniesAssociationsChange,\n loadLinkedPeoplePage,\n loadLinkedCompaniesPage,\n } = useDealAssociations({\n currentDealId,\n data,\n setData,\n runMutationWithContext,\n onRefresh: () => { void loadData() },\n })\n\n const { isStageSaving, handleStageChange } = useDealPipeline({\n currentDealId,\n data,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onStageChanged: loadData,\n })\n\n const {\n lostDialogOpen,\n wonPopupOpen,\n lostPopupOpen,\n wonStats,\n lostStats,\n openLostDialog,\n closeLostDialog,\n closeWonPopup,\n closeLostPopup,\n handleWon,\n handleLostConfirm,\n } = useDealClosure({\n currentDealId,\n dealUpdatedAt: data?.deal.updatedAt ?? null,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onClosed: loadData,\n })\n\n const handleTabChange = React.useCallback(async (tab: DealTabId) => {\n if (!(await confirmDiscardIfDirty())) return\n setActiveTab(tab)\n const nextParams = new URLSearchParams(searchParams?.toString() ?? '')\n nextParams.set('tab', tab)\n router.replace(`/backend/customers/deals/${encodeURIComponent(id)}?${nextParams.toString()}`, { scroll: false })\n }, [confirmDiscardIfDirty, id, router, searchParams])\n\n const { isSaving, handleFormSubmit, handleDelete, handleHeaderSave } = useDealFormHandlers({\n data,\n currentDealId,\n loadData,\n runMutationWithContext,\n formWrapperRef,\n confirm,\n })\n\n const handleEditActivity = React.useCallback((activity: InteractionSummary) => {\n if (activity.entityId && activityEntities.some((entry) => entry.id === activity.entityId)) {\n setSelectedActivityEntityId(activity.entityId)\n }\n // Forward `customValues` so per-type chip state (callPhoneNumber,\n // callDirection, taskPriority) round-trips on edit (#1808 phone persistence).\n // Forward `occurredAt` so historical activity edits prefill from the\n // original moment instead of \"today\" (#1807 prefill).\n const rawActivity = activity as unknown as Record<string, unknown>\n openScheduleEdit({\n id: activity.id,\n updatedAt: typeof rawActivity.updatedAt === 'string' ? rawActivity.updatedAt as string : typeof rawActivity.updated_at === 'string' ? rawActivity.updated_at as string : null,\n interactionType: activity.interactionType,\n title: activity.title ?? null,\n body: activity.body ?? null,\n scheduledAt: activity.scheduledAt ?? null,\n occurredAt: activity.occurredAt ?? null,\n durationMinutes: activity.duration ?? null,\n location: activity.location ?? null,\n allDay: activity.allDay ?? null,\n recurrenceRule: activity.recurrenceRule ?? null,\n recurrenceEnd: activity.recurrenceEnd ?? null,\n participants: activity.participants ?? null,\n reminderMinutes: activity.reminderMinutes ?? null,\n visibility: activity.visibility ?? null,\n linkedEntities: activity.linkedEntities ?? null,\n guestPermissions: activity.guestPermissions ?? null,\n ...(rawActivity.customValues && typeof rawActivity.customValues === 'object'\n ? { customValues: rawActivity.customValues as Record<string, unknown> }\n : {}),\n ...(typeof rawActivity.phoneNumber === 'string'\n ? { phoneNumber: rawActivity.phoneNumber as string }\n : {}),\n } as ScheduleActivityEditData & { customValues?: Record<string, unknown> | null; phoneNumber?: string | null })\n }, [activityEntities, openScheduleEdit])\n\n const handleViewDashboard = React.useCallback(() => {\n closeWonPopup()\n router.push('/backend')\n }, [closeWonPopup, router])\n\n const handleBackToPipeline = React.useCallback(() => {\n closeWonPopup()\n closeLostPopup()\n router.push('/backend/customers/deals/pipeline')\n }, [closeLostPopup, closeWonPopup, router])\n\n const handleScheduleLostFollowUp = React.useCallback(() => {\n if (!data || !selectedActivityEntity) return\n const nextQuarterDate = startOfNextQuarter(new Date())\n closeLostPopup()\n openScheduleEdit({\n id: '',\n interactionType: 'task',\n title: data.deal.title\n ? t('customers.deals.detail.lost.followUpTitle', 'Revisit {{title}}', { title: data.deal.title })\n : t('customers.deals.detail.lost.followUpFallbackTitle', 'Revisit closed deal'),\n body: data.deal.lossNotes ?? null,\n scheduledAt: nextQuarterDate.toISOString(),\n durationMinutes: 30,\n location: null,\n allDay: false,\n recurrenceRule: null,\n recurrenceEnd: null,\n participants: null,\n reminderMinutes: 1440,\n visibility: 'team',\n linkedEntities: [\n {\n id: data.deal.id,\n type: 'deal',\n label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal'),\n },\n ],\n })\n }, [closeLostPopup, data, openScheduleEdit, selectedActivityEntity, t])\n\n const currentPipelineName = data\n ? data.pipelineName ?? wonStats?.pipelineName ?? lostStats?.pipelineName ?? null\n : wonStats?.pipelineName ?? lostStats?.pipelineName ?? null\n const formPipelineOptions = React.useMemo(\n () => data?.deal.pipelineId\n ? [{\n id: data.deal.pipelineId,\n name: currentPipelineName ?? t('customers.deals.detail.pipeline.defaultName', 'Current pipeline'),\n isDefault: false,\n }]\n : [],\n [currentPipelineName, data?.deal.pipelineId, t],\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('customers.deals.detail.loading', 'Loading deal\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customers.deals.detail.error.notFound', 'Deal not found.')}\n backHref=\"/backend/customers/deals\"\n backLabel={t('customers.deals.detail.actions.backToList', 'Back to deals')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customers.deals.detail.error.load', 'Failed to load deal.')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customers/deals\">\n {t('customers.deals.detail.actions.backToList', 'Back to deals')}\n </Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const amountLabel = formatCurrency(data.deal.valueAmount, data.deal.valueCurrency)\n const dealName = data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')\n\n const zone1Content = (\n <div ref={formWrapperRef}>\n <DealForm\n mode=\"edit\"\n embedded\n trackDirtyWhenEmbedded\n hideFooterActions\n singleColumnGroups\n showAssociationsGroup={false}\n showVersionHistory={false}\n showCancelAction={false}\n injectionSpotId={extensionPoints.hosts.dealForm.spotId}\n optimisticLockUpdatedAt={data.deal.updatedAt}\n onDirtyChange={setIsDirty}\n initialPipelineOptions={formPipelineOptions}\n initialPipelineStageOptions={data.pipelineStages}\n collapsibleGroups={{ pageType: 'deal-detail-v3', chevronPosition: 'right' }}\n sortableGroups={{ pageType: 'deal-detail-v3' }}\n initialValues={{\n ...data.deal,\n valueAmount:\n typeof data.deal.valueAmount === 'string' && data.deal.valueAmount.trim().length\n ? Number(data.deal.valueAmount)\n : null,\n personIds: data.linkedPersonIds,\n companyIds: data.linkedCompanyIds,\n customFields: data.customFields,\n ...Object.fromEntries(Object.entries(data.customFields ?? {}).map(([key, value]) => [`cf_${key}`, value])),\n }}\n onSubmit={handleFormSubmit}\n onCancel={() => { void loadData() }}\n onDelete={handleDelete}\n />\n </div>\n )\n\n const zone2Content = (\n <div className=\"rounded-[10px] border border-border bg-card px-5 py-5\">\n {(() => {\n const injected = injectedTabMap.get(activeTab)\n if (injected) return injected()\n\n if (activeTab === 'activities') {\n const activityEntitySelection = activityEntities.length > 1 ? (\n <div className=\"rounded-[10px] border border-border bg-muted/20 px-5 py-5\">\n <label htmlFor=\"deal-activity-entity\" className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}\n </label>\n <div className=\"mt-1 text-sm text-muted-foreground\">\n {t(\n 'customers.deals.detail.activities.selectEntityDescription',\n 'Pick the person or company that should own new deal activities and follow-ups.',\n )}\n </div>\n <select\n id=\"deal-activity-entity\"\n aria-label={t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}\n className=\"mt-4 h-9 w-full rounded border border-muted-foreground/40 bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={selectedActivityEntityId ?? ''}\n onChange={(event) => setSelectedActivityEntityId(event.target.value || null)}\n >\n <option value=\"\">\n {t('customers.deals.detail.activities.selectEntityPlaceholder', 'Select a person or company')}\n </option>\n {activityEntities.map((entry) => (\n <option key={entry.id} value={entry.id}>\n {entry.label}\n </option>\n ))}\n </select>\n </div>\n ) : null\n\n return (\n <div className=\"space-y-4\">\n {activityEntities.length > 1 ? activityEntitySelection : null}\n {activityEntities.length === 0 ? (\n <div className=\"rounded-[10px] border border-border bg-muted/20 px-5 py-5\">\n <div className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.activities.linkEntityTitle', 'Link a person or company first')}\n </div>\n <div className=\"mt-1 text-sm text-muted-foreground\">\n {t('customers.deals.detail.activities.linkEntityDescription', 'Activities on a deal still need a customer record for timeline ownership.')}\n </div>\n <div className=\"mt-4 flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => handleTabChange('people')}>\n {t('customers.deals.detail.tabs.people', 'People')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => handleTabChange('companies')}>\n {t('customers.deals.detail.tabs.companies', 'Companies')}\n </Button>\n </div>\n </div>\n ) : selectedActivityEntity ? (\n <InlineActivityComposer\n entityType={selectedActivityEntity.kind}\n entityId={selectedActivityEntity.id}\n dealId={data.deal.id}\n onActivityCreated={() => { void handleActivityCreated() }}\n runGuardedMutation={runMutationWithContext}\n onScheduleRequested={openSchedule}\n />\n ) : (\n <EmptyState\n size=\"sm\"\n icon={<UserSearch className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t('customers.deals.detail.activities.selectEntityRequiredTitle', 'Choose a person or company to continue')}\n description={t(\n 'customers.deals.detail.activities.selectEntityRequiredDescription',\n 'Select the customer record that should receive new deal activities before logging or scheduling anything.',\n )}\n />\n )}\n <PlannedActivitiesSection\n activities={plannedActivities}\n onComplete={(interactionId) => { void handleMarkDone(interactionId) }}\n onSchedule={selectedActivityEntity ? openSchedule : undefined}\n onEdit={handleEditActivity}\n onCancel={(interactionId) => { void handleCancelActivity(interactionId) }}\n />\n {selectedActivityEntity ? (\n <ActivitiesSection\n entityId={selectedActivityEntity.id}\n entityName={selectedActivityEntity.label}\n dealId={data.deal.id}\n dealOptions={dealOptions}\n entityOptions={entityOptions}\n defaultEntityId={selectedActivityEntity.id}\n addActionLabel={t('customers.deals.detail.activitiesAdd', 'Log activity')}\n emptyState={{\n title: t('customers.deals.detail.activitiesEmptyTitle', 'No activities yet'),\n actionLabel: t('customers.deals.detail.activitiesEmptyAction', 'Log activity'),\n }}\n runGuardedMutation={runMutationWithContext}\n onDataRefresh={() => { void handleActivityCreated() }}\n refreshKey={activityRefreshKey}\n onEditActivity={handleEditActivity}\n />\n ) : null}\n </div>\n )\n }\n\n if (activeTab === 'people') {\n return (\n <DealLinkedEntitiesTab\n entityLabel={t('customers.deals.detail.tabs.peopleSingular', 'Person')}\n entityLabelPlural={t('customers.deals.detail.tabs.people', 'People')}\n manageLabel={t('customers.deals.detail.peopleEditorTitle', 'Manage linked people')}\n searchPlaceholder={t('customers.deals.detail.peopleSearch', 'Search linked people\u2026')}\n linkedItems={data.people}\n linkedCount={data.counts.people}\n selectedIds={peopleEditorIds}\n disabled={peopleSaving || isSaving}\n savePending={peopleSaving}\n hrefBuilder={(personId) => `/backend/customers/people-v2/${encodeURIComponent(personId)}`}\n onSaveSelection={(next) => handlePeopleAssociationsChange(next)}\n loadLinkedPage={loadLinkedPeoplePage}\n searchEntities={searchPeoplePage}\n fetchEntitiesByIds={fetchPeopleByIds}\n icon={<Users className=\"size-4\" />}\n />\n )\n }\n\n if (activeTab === 'companies') {\n return (\n <DealLinkedEntitiesTab\n entityLabel={t('customers.deals.detail.tabs.companySingular', 'Company')}\n entityLabelPlural={t('customers.deals.detail.tabs.companies', 'Companies')}\n manageLabel={t('customers.deals.detail.companiesEditorTitle', 'Manage linked companies')}\n searchPlaceholder={t('customers.deals.detail.companiesSearch', 'Search linked companies\u2026')}\n linkedItems={data.companies}\n linkedCount={data.counts.companies}\n selectedIds={companiesEditorIds}\n disabled={companiesSaving || isSaving}\n savePending={companiesSaving}\n hrefBuilder={(companyId) => `/backend/customers/companies-v2/${encodeURIComponent(companyId)}`}\n onSaveSelection={(next) => handleCompaniesAssociationsChange(next)}\n loadLinkedPage={loadLinkedCompaniesPage}\n searchEntities={searchCompaniesPage}\n fetchEntitiesByIds={fetchCompaniesByIds}\n icon={<Building2 className=\"size-4\" />}\n />\n )\n }\n\n if (activeTab === 'notes') {\n return (\n <NotesSection\n entityId={null}\n dealId={data.deal.id}\n dealOptions={dealOptions}\n entityOptions={entityOptions}\n emptyLabel={t('customers.deals.detail.notesEmpty', 'No notes yet.')}\n viewerUserId={data.viewer?.userId ?? null}\n viewerName={data.viewer?.name ?? null}\n viewerEmail={data.viewer?.email ?? null}\n addActionLabel={t('customers.deals.detail.notesAdd', 'Add note')}\n emptyState={{\n title: t('customers.deals.detail.notesEmptyTitle', 'Keep everyone in the loop'),\n actionLabel: t('customers.deals.detail.notesEmptyAction', 'Add a note'),\n }}\n translator={detailTranslator}\n dataAdapter={notesAdapter}\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n iconSuggestions={ICON_SUGGESTIONS}\n readMarkdownPreference={readMarkdownPreferenceCookie}\n writeMarkdownPreference={writeMarkdownPreferenceCookie}\n />\n )\n }\n\n if (activeTab === 'files') {\n return (\n <AttachmentsSection\n entityId={E.customers.customer_deal}\n recordId={data.deal.id}\n title={t('customers.deals.detail.tabs.files', 'Files')}\n description={t('customers.deals.detail.files.subtitle', 'Upload and manage files linked to this deal.')}\n />\n )\n }\n\n if (activeTab === 'changelog') {\n return <ChangelogTab entityId={data.deal.id} entityType=\"deal\" />\n }\n\n return null\n })()}\n </div>\n )\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <InjectionSpot spotId={extensionPoints.hosts.dealHeader.spotId} context={injectionContext} data={data} />\n\n <DealDetailHeader\n deal={data.deal}\n owner={data.owner}\n people={data.people}\n companies={data.companies}\n pipelineName={currentPipelineName}\n stageOptions={data.pipelineStages}\n currentStageId={data.deal.pipelineStageId}\n onStageChange={handleStageChange}\n isStageSaving={isStageSaving}\n onSave={handleHeaderSave}\n onDelete={handleDelete}\n isDirty={isDirty}\n isSaving={isSaving}\n />\n\n <InjectionSpot spotId={extensionPoints.hosts.dealStatusBadges.spotId} context={injectionContext} data={data} onDataChange={setData} />\n\n <PipelineStepper\n stages={data.pipelineStages}\n transitions={data.stageTransitions}\n currentStageId={data.deal.pipelineStageId}\n pipelineName={currentPipelineName}\n closureOutcome={data.deal.closureOutcome}\n footer={data.deal.closureOutcome ? null : (\n <DealClosureActionBar\n embedded\n closureOutcome={data.deal.closureOutcome}\n onWon={() => { void handleWon() }}\n onLost={openLostDialog}\n />\n )}\n />\n\n <DealDetailTabs\n activeTab={activeTab}\n onTabChange={handleTabChange}\n injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}\n peopleCount={data.counts.people}\n companiesCount={data.counts.companies}\n >\n <CollapsibleZoneLayout\n pageType=\"deal-detail-v3\"\n entityName={dealName}\n isDirty={isDirty}\n zone1DefaultWidth=\"540px\"\n zone1={zone1Content}\n zone2={zone2Content}\n />\n </DealDetailTabs>\n\n <InjectionSpot spotId={extensionPoints.hosts.dealFooter.spotId} context={injectionContext} data={data} />\n </div>\n\n {ConfirmDialogElement}\n\n {selectedActivityEntity ? (\n <ScheduleActivityDialog\n open={scheduleDialogOpen}\n onClose={closeSchedule}\n entityId={selectedActivityEntity.id}\n dealId={data.deal.id}\n entityType={selectedActivityEntity.kind}\n entityName={selectedActivityEntity.label}\n companyName={selectedActivityEntity.kind === 'company' ? selectedActivityEntity.label : data.companies[0]?.label ?? null}\n onActivityCreated={() => { void handleActivityCreated() }}\n editData={scheduleEditData}\n />\n ) : null}\n\n <ConfirmDealLostDialog\n open={lostDialogOpen}\n onClose={closeLostDialog}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n dealValue={amountLabel}\n companyName={data.companies[0]?.label ?? null}\n onConfirm={handleLostConfirm}\n />\n\n <DealWonPopup\n open={wonPopupOpen}\n onClose={closeWonPopup}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n stats={wonStats}\n onViewDashboard={handleViewDashboard}\n onBackToPipeline={handleBackToPipeline}\n />\n\n <DealLostSummaryDialog\n open={lostPopupOpen}\n onClose={closeLostPopup}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n lossNotes={data.deal.lossNotes}\n stats={lostStats}\n onBackToPipeline={handleBackToPipeline}\n onScheduleFollowUp={selectedActivityEntity ? handleScheduleLostFollowUp : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/customers/extension-points'\nimport Link from 'next/link'\nimport { Building2, FileText, Hash, UserSearch, Users } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { useRouter, useSearchParams, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { E } from '#generated/entities.ids.generated'\n\nimport { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'\nimport { ChangelogTab } from '../../../../components/detail/ChangelogTab'\nimport { DealClosureActionBar } from '../../../../components/detail/DealClosureActionBar'\nimport { DealDetailHeader } from '../../../../components/detail/DealDetailHeader'\nimport { DealDetailTabs, resolveLegacyTab, type DealTabId } from '../../../../components/detail/DealDetailTabs'\nimport { DealForm, useDealAssociationLookups } from '../../../../components/detail/DealForm'\nimport { DealLinkedEntitiesTab } from '../../../../components/detail/DealLinkedEntitiesTab'\nimport { ConfirmDealLostDialog } from '../../../../components/detail/ConfirmDealLostDialog'\nimport { DealLostSummaryDialog } from '../../../../components/detail/DealLostSummaryDialog'\nimport { DealWonPopup } from '../../../../components/detail/DealWonPopup'\nimport { InlineActivityComposer } from '../../../../components/detail/InlineActivityComposer'\nimport { PipelineStepper } from '../../../../components/detail/PipelineStepper'\nimport { PlannedActivitiesSection } from '../../../../components/detail/PlannedActivitiesSection'\nimport { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'\nimport { createCustomerNotesAdapter } from '../../../../components/detail/notesAdapter'\nimport type { InteractionSummary } from '../../../../components/detail/types'\nimport { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '../../../../lib/markdownPreference'\nimport { ICON_SUGGESTIONS } from '../../../../lib/dictionaries'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nimport { formatCurrency, startOfNextQuarter } from './hooks/formatters'\nimport type { DealDetailPayload } from './hooks/types'\nimport { useDealActivities } from './hooks/useDealActivities'\nimport { useDealAssociations } from './hooks/useDealAssociations'\nimport { useDealClosure } from './hooks/useDealClosure'\nimport { useDealData } from './hooks/useDealData'\nimport { useDealFormHandlers } from './hooks/useDealFormHandlers'\nimport { useDealInjectedTabs } from './hooks/useDealInjectedTabs'\nimport { useDealMutationContext } from './hooks/useDealMutationContext'\nimport { useDealPipeline } from './hooks/useDealPipeline'\nimport { useScheduleDialog } from './hooks/useScheduleDialog'\n\nexport default function DealDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id ?? ''\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [\n { id: 'details', icon: FileText, label: t('customers.people.detail.deals.form.details', 'Deal details') },\n { id: 'custom', icon: Hash, label: t('customers.people.detail.deals.form.customFields', 'Custom fields') },\n ], [t])\n\n const { data, setData, isLoading, error, isNotFound, loadData } = useDealData(id)\n const [isDirty, setIsDirty] = React.useState(false)\n const {\n scheduleDialogOpen,\n scheduleEditData,\n openSchedule,\n openEdit: openScheduleEdit,\n closeSchedule,\n } = useScheduleDialog()\n const formWrapperRef = React.useRef<HTMLDivElement>(null)\n\n const currentDealId = data?.deal.id ?? id\n const { injectionContext, runMutationWithContext } = useDealMutationContext({\n currentDealId,\n fallbackId: id,\n data,\n })\n\n // Publish page-load record context with explicit `updatedAt`/`data` to the\n // AppShell-owned `backend:record:current` mount. The deal path is already in the\n // widget's allowlist (presence works) but supplied no version/record payload \u2014\n // this gives the merge dialog its action-log base and field-diff source.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'customers.deal',\n resourceId: data?.deal.id ?? null,\n updatedAt: data?.deal.updatedAt ?? null,\n data: data as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n const notesAdapter = React.useMemo(\n () => createCustomerNotesAdapter(detailTranslator, { runMutation: runMutationWithContext }),\n [detailTranslator, runMutationWithContext],\n )\n\n const { injectedTabs, injectedTabMap } = useDealInjectedTabs({\n injectionContext,\n data,\n setData,\n })\n\n const injectedTabIds = React.useMemo(() => injectedTabs.map((tab) => tab.id), [injectedTabs])\n const initialTab = React.useMemo(\n () => resolveLegacyTab(searchParams?.get('tab'), injectedTabIds),\n [injectedTabIds, searchParams],\n )\n const [activeTab, setActiveTab] = React.useState<DealTabId>(initialTab)\n\n React.useEffect(() => {\n setActiveTab(initialTab)\n }, [initialTab])\n\n const { searchPeoplePage, fetchPeopleByIds, searchCompaniesPage, fetchCompaniesByIds } = useDealAssociationLookups({\n excludeLinkedDealId: data?.deal.id ?? null,\n })\n\n const {\n plannedActivities,\n activityRefreshKey,\n loadPlannedActivities,\n handleActivityCreated,\n handleMarkDone,\n handleCancelActivity,\n } = useDealActivities({ dealId: id, runMutationWithContext })\n\n React.useEffect(() => {\n void Promise.all([loadData({ cache: true }), loadPlannedActivities({ cache: true })])\n }, [loadData, loadPlannedActivities])\n\n const activityEntities = React.useMemo(\n () => (data\n ? [...data.people, ...data.companies].map((entry) => ({\n id: entry.id,\n label: entry.subtitle ? `${entry.label} \u00B7 ${entry.subtitle}` : entry.label,\n kind: entry.kind,\n isPrimary: entry.isPrimary === true,\n }))\n : []),\n [data],\n )\n const [selectedActivityEntityId, setSelectedActivityEntityId] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n setSelectedActivityEntityId((current) => {\n if (activityEntities.length === 1) return activityEntities[0].id\n if (current && activityEntities.some((entry) => entry.id === current)) return current\n const primary = activityEntities.find((entry) => entry.isPrimary)\n return (primary ?? activityEntities[0])?.id ?? null\n })\n }, [activityEntities])\n\n const selectedActivityEntity = React.useMemo(\n () => activityEntities.find((entry) => entry.id === selectedActivityEntityId) ?? null,\n [activityEntities, selectedActivityEntityId],\n )\n\n const dealOptions = React.useMemo(\n () => data ? [{ id: data.deal.id, label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal') }] : [],\n [data, t],\n )\n\n const entityOptions = React.useMemo(\n () => activityEntities.map(({ id, label }) => ({ id, label })),\n [activityEntities],\n )\n\n const confirmDiscardIfDirty = React.useCallback(async () => {\n if (!isDirty) return true\n return confirm({\n title: t('customers.deals.detail.unsavedTitle', 'Discard unsaved changes?'),\n description: t(\n 'customers.deals.detail.unsavedDescription',\n 'You have unsaved edits in this deal. Save them first or continue to discard them.',\n ),\n confirmText: t('customers.deals.detail.unsavedConfirm', 'Discard changes'),\n cancelText: t('customers.deals.detail.unsavedCancel', 'Keep editing'),\n variant: 'destructive',\n })\n }, [confirm, isDirty, t])\n\n const {\n peopleEditorIds,\n companiesEditorIds,\n peopleSaving,\n companiesSaving,\n handlePeopleAssociationsChange,\n handleCompaniesAssociationsChange,\n loadLinkedPeoplePage,\n loadLinkedCompaniesPage,\n } = useDealAssociations({\n currentDealId,\n data,\n setData,\n runMutationWithContext,\n onRefresh: () => { void loadData() },\n })\n\n const { isStageSaving, handleStageChange } = useDealPipeline({\n currentDealId,\n data,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onStageChanged: loadData,\n })\n\n const {\n lostDialogOpen,\n wonPopupOpen,\n lostPopupOpen,\n wonStats,\n lostStats,\n openLostDialog,\n closeLostDialog,\n closeWonPopup,\n closeLostPopup,\n handleWon,\n handleLostConfirm,\n } = useDealClosure({\n currentDealId,\n dealUpdatedAt: data?.deal.updatedAt ?? null,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onClosed: loadData,\n })\n\n const handleTabChange = React.useCallback(async (tab: DealTabId) => {\n if (!(await confirmDiscardIfDirty())) return\n setActiveTab(tab)\n const nextParams = new URLSearchParams(searchParams?.toString() ?? '')\n nextParams.set('tab', tab)\n router.replace(`/backend/customers/deals/${encodeURIComponent(id)}?${nextParams.toString()}`, { scroll: false })\n }, [confirmDiscardIfDirty, id, router, searchParams])\n\n const { isSaving, handleFormSubmit, handleDelete, handleHeaderSave } = useDealFormHandlers({\n data,\n currentDealId,\n loadData,\n runMutationWithContext,\n formWrapperRef,\n confirm,\n })\n\n const handleEditActivity = React.useCallback((activity: InteractionSummary) => {\n if (activity.entityId && activityEntities.some((entry) => entry.id === activity.entityId)) {\n setSelectedActivityEntityId(activity.entityId)\n }\n // Forward `customValues` so per-type chip state (callPhoneNumber,\n // callDirection, taskPriority) round-trips on edit (#1808 phone persistence).\n // Forward `occurredAt` so historical activity edits prefill from the\n // original moment instead of \"today\" (#1807 prefill).\n const rawActivity = activity as unknown as Record<string, unknown>\n openScheduleEdit({\n id: activity.id,\n updatedAt: typeof rawActivity.updatedAt === 'string' ? rawActivity.updatedAt as string : typeof rawActivity.updated_at === 'string' ? rawActivity.updated_at as string : null,\n interactionType: activity.interactionType,\n title: activity.title ?? null,\n body: activity.body ?? null,\n scheduledAt: activity.scheduledAt ?? null,\n occurredAt: activity.occurredAt ?? null,\n durationMinutes: activity.duration ?? null,\n location: activity.location ?? null,\n allDay: activity.allDay ?? null,\n recurrenceRule: activity.recurrenceRule ?? null,\n recurrenceEnd: activity.recurrenceEnd ?? null,\n participants: activity.participants ?? null,\n reminderMinutes: activity.reminderMinutes ?? null,\n visibility: activity.visibility ?? null,\n linkedEntities: activity.linkedEntities ?? null,\n guestPermissions: activity.guestPermissions ?? null,\n ...(rawActivity.customValues && typeof rawActivity.customValues === 'object'\n ? { customValues: rawActivity.customValues as Record<string, unknown> }\n : {}),\n ...(typeof rawActivity.phoneNumber === 'string'\n ? { phoneNumber: rawActivity.phoneNumber as string }\n : {}),\n } as ScheduleActivityEditData & { customValues?: Record<string, unknown> | null; phoneNumber?: string | null })\n }, [activityEntities, openScheduleEdit])\n\n const handleViewDashboard = React.useCallback(() => {\n closeWonPopup()\n router.push('/backend')\n }, [closeWonPopup, router])\n\n const handleBackToPipeline = React.useCallback(() => {\n closeWonPopup()\n closeLostPopup()\n router.push('/backend/customers/deals/pipeline')\n }, [closeLostPopup, closeWonPopup, router])\n\n const handleScheduleLostFollowUp = React.useCallback(() => {\n if (!data || !selectedActivityEntity) return\n const nextQuarterDate = startOfNextQuarter(new Date())\n closeLostPopup()\n openScheduleEdit({\n id: '',\n interactionType: 'task',\n title: data.deal.title\n ? t('customers.deals.detail.lost.followUpTitle', 'Revisit {{title}}', { title: data.deal.title })\n : t('customers.deals.detail.lost.followUpFallbackTitle', 'Revisit closed deal'),\n body: data.deal.lossNotes ?? null,\n scheduledAt: nextQuarterDate.toISOString(),\n durationMinutes: 30,\n location: null,\n allDay: false,\n recurrenceRule: null,\n recurrenceEnd: null,\n participants: null,\n reminderMinutes: 1440,\n visibility: 'team',\n linkedEntities: [\n {\n id: data.deal.id,\n type: 'deal',\n label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal'),\n },\n ],\n })\n }, [closeLostPopup, data, openScheduleEdit, selectedActivityEntity, t])\n\n const currentPipelineName = data\n ? data.pipelineName ?? wonStats?.pipelineName ?? lostStats?.pipelineName ?? null\n : wonStats?.pipelineName ?? lostStats?.pipelineName ?? null\n const formPipelineOptions = React.useMemo(\n () => data?.deal.pipelineId\n ? [{\n id: data.deal.pipelineId,\n name: currentPipelineName ?? t('customers.deals.detail.pipeline.defaultName', 'Current pipeline'),\n isDefault: false,\n }]\n : [],\n [currentPipelineName, data?.deal.pipelineId, t],\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('customers.deals.detail.loading', 'Loading deal\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customers.deals.detail.error.notFound', 'Deal not found.')}\n backHref=\"/backend/customers/deals\"\n backLabel={t('customers.deals.detail.actions.backToList', 'Back to deals')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customers.deals.detail.error.load', 'Failed to load deal.')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customers/deals\">\n {t('customers.deals.detail.actions.backToList', 'Back to deals')}\n </Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const amountLabel = formatCurrency(data.deal.valueAmount, data.deal.valueCurrency)\n const dealName = data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')\n\n const zone1Content = (\n <div ref={formWrapperRef}>\n <DealForm\n mode=\"edit\"\n embedded\n trackDirtyWhenEmbedded\n hideFooterActions\n singleColumnGroups\n showAssociationsGroup={false}\n showVersionHistory={false}\n showCancelAction={false}\n injectionSpotId={extensionPoints.hosts.dealForm.spotId}\n optimisticLockUpdatedAt={data.deal.updatedAt}\n onDirtyChange={setIsDirty}\n initialPipelineOptions={formPipelineOptions}\n initialPipelineStageOptions={data.pipelineStages}\n collapsibleGroups={{ pageType: 'deal-detail-v3', chevronPosition: 'right' }}\n sortableGroups={{ pageType: 'deal-detail-v3' }}\n initialValues={{\n ...data.deal,\n valueAmount:\n typeof data.deal.valueAmount === 'string' && data.deal.valueAmount.trim().length\n ? Number(data.deal.valueAmount)\n : null,\n personIds: data.linkedPersonIds,\n companyIds: data.linkedCompanyIds,\n customFields: data.customFields,\n ...Object.fromEntries(Object.entries(data.customFields ?? {}).map(([key, value]) => [`cf_${key}`, value])),\n }}\n onSubmit={handleFormSubmit}\n onCancel={() => { void loadData() }}\n onDelete={handleDelete}\n />\n </div>\n )\n\n const zone2Content = (\n <div className=\"rounded-[10px] border border-border bg-card px-5 py-5\">\n {(() => {\n const injected = injectedTabMap.get(activeTab)\n if (injected) return injected()\n\n if (activeTab === 'activities') {\n const activityEntitySelection = activityEntities.length > 1 ? (\n <div className=\"rounded-[10px] border border-border bg-muted/20 px-5 py-5\">\n <label htmlFor=\"deal-activity-entity\" className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}\n </label>\n <div className=\"mt-1 text-sm text-muted-foreground\">\n {t(\n 'customers.deals.detail.activities.selectEntityDescription',\n 'Pick the person or company that should own new deal activities and follow-ups.',\n )}\n </div>\n <select\n id=\"deal-activity-entity\"\n aria-label={t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}\n className=\"mt-4 h-9 w-full rounded border border-muted-foreground/40 bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={selectedActivityEntityId ?? ''}\n onChange={(event) => setSelectedActivityEntityId(event.target.value || null)}\n >\n <option value=\"\">\n {t('customers.deals.detail.activities.selectEntityPlaceholder', 'Select a person or company')}\n </option>\n {activityEntities.map((entry) => (\n <option key={entry.id} value={entry.id}>\n {entry.label}\n </option>\n ))}\n </select>\n </div>\n ) : null\n\n return (\n <div className=\"space-y-4\">\n {activityEntities.length > 1 ? activityEntitySelection : null}\n {activityEntities.length === 0 ? (\n <div className=\"rounded-[10px] border border-border bg-muted/20 px-5 py-5\">\n <div className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.activities.linkEntityTitle', 'Link a person or company first')}\n </div>\n <div className=\"mt-1 text-sm text-muted-foreground\">\n {t('customers.deals.detail.activities.linkEntityDescription', 'Activities on a deal still need a customer record for timeline ownership.')}\n </div>\n <div className=\"mt-4 flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => handleTabChange('people')}>\n {t('customers.deals.detail.tabs.people', 'People')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => handleTabChange('companies')}>\n {t('customers.deals.detail.tabs.companies', 'Companies')}\n </Button>\n </div>\n </div>\n ) : selectedActivityEntity ? (\n <InlineActivityComposer\n entityType={selectedActivityEntity.kind}\n entityId={selectedActivityEntity.id}\n dealId={data.deal.id}\n onActivityCreated={() => { void handleActivityCreated() }}\n runGuardedMutation={runMutationWithContext}\n onScheduleRequested={openSchedule}\n />\n ) : (\n <EmptyState\n size=\"sm\"\n icon={<UserSearch className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t('customers.deals.detail.activities.selectEntityRequiredTitle', 'Choose a person or company to continue')}\n description={t(\n 'customers.deals.detail.activities.selectEntityRequiredDescription',\n 'Select the customer record that should receive new deal activities before logging or scheduling anything.',\n )}\n />\n )}\n <PlannedActivitiesSection\n activities={plannedActivities}\n onComplete={(interactionId) => { void handleMarkDone(interactionId) }}\n onSchedule={selectedActivityEntity ? openSchedule : undefined}\n onEdit={handleEditActivity}\n onCancel={(interactionId) => { void handleCancelActivity(interactionId) }}\n />\n {selectedActivityEntity ? (\n <ActivitiesSection\n entityId={selectedActivityEntity.id}\n entityName={selectedActivityEntity.label}\n dealId={data.deal.id}\n dealOptions={dealOptions}\n entityOptions={entityOptions}\n defaultEntityId={selectedActivityEntity.id}\n addActionLabel={t('customers.deals.detail.activitiesAdd', 'Log activity')}\n emptyState={{\n title: t('customers.deals.detail.activitiesEmptyTitle', 'No activities yet'),\n actionLabel: t('customers.deals.detail.activitiesEmptyAction', 'Log activity'),\n }}\n runGuardedMutation={runMutationWithContext}\n onDataRefresh={() => { void handleActivityCreated() }}\n refreshKey={activityRefreshKey}\n onEditActivity={handleEditActivity}\n />\n ) : null}\n </div>\n )\n }\n\n if (activeTab === 'people') {\n return (\n <DealLinkedEntitiesTab\n entityLabel={t('customers.deals.detail.tabs.peopleSingular', 'Person')}\n entityLabelPlural={t('customers.deals.detail.tabs.people', 'People')}\n manageLabel={t('customers.deals.detail.peopleEditorTitle', 'Manage linked people')}\n searchPlaceholder={t('customers.deals.detail.peopleSearch', 'Search linked people\u2026')}\n linkedItems={data.people}\n linkedCount={data.counts.people}\n selectedIds={peopleEditorIds}\n disabled={peopleSaving || isSaving}\n savePending={peopleSaving}\n hrefBuilder={(personId) => `/backend/customers/people-v2/${encodeURIComponent(personId)}`}\n onSaveSelection={(next) => handlePeopleAssociationsChange(next)}\n loadLinkedPage={loadLinkedPeoplePage}\n searchEntities={searchPeoplePage}\n fetchEntitiesByIds={fetchPeopleByIds}\n icon={<Users className=\"size-4\" />}\n />\n )\n }\n\n if (activeTab === 'companies') {\n return (\n <DealLinkedEntitiesTab\n entityLabel={t('customers.deals.detail.tabs.companySingular', 'Company')}\n entityLabelPlural={t('customers.deals.detail.tabs.companies', 'Companies')}\n manageLabel={t('customers.deals.detail.companiesEditorTitle', 'Manage linked companies')}\n searchPlaceholder={t('customers.deals.detail.companiesSearch', 'Search linked companies\u2026')}\n linkedItems={data.companies}\n linkedCount={data.counts.companies}\n selectedIds={companiesEditorIds}\n disabled={companiesSaving || isSaving}\n savePending={companiesSaving}\n hrefBuilder={(companyId) => `/backend/customers/companies-v2/${encodeURIComponent(companyId)}`}\n onSaveSelection={(next) => handleCompaniesAssociationsChange(next)}\n loadLinkedPage={loadLinkedCompaniesPage}\n searchEntities={searchCompaniesPage}\n fetchEntitiesByIds={fetchCompaniesByIds}\n icon={<Building2 className=\"size-4\" />}\n />\n )\n }\n\n if (activeTab === 'notes') {\n return (\n <NotesSection\n entityId={null}\n dealId={data.deal.id}\n dealOptions={dealOptions}\n entityOptions={entityOptions}\n emptyLabel={t('customers.deals.detail.notesEmpty', 'No notes yet.')}\n viewerUserId={data.viewer?.userId ?? null}\n viewerName={data.viewer?.name ?? null}\n viewerEmail={data.viewer?.email ?? null}\n addActionLabel={t('customers.deals.detail.notesAdd', 'Add note')}\n emptyState={{\n title: t('customers.deals.detail.notesEmptyTitle', 'Keep everyone in the loop'),\n actionLabel: t('customers.deals.detail.notesEmptyAction', 'Add a note'),\n }}\n translator={detailTranslator}\n dataAdapter={notesAdapter}\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n iconSuggestions={ICON_SUGGESTIONS}\n readMarkdownPreference={readMarkdownPreferenceCookie}\n writeMarkdownPreference={writeMarkdownPreferenceCookie}\n />\n )\n }\n\n if (activeTab === 'files') {\n return (\n <AttachmentsSection\n entityId={E.customers.customer_deal}\n recordId={data.deal.id}\n title={t('customers.deals.detail.tabs.files', 'Files')}\n description={t('customers.deals.detail.files.subtitle', 'Upload and manage files linked to this deal.')}\n />\n )\n }\n\n if (activeTab === 'changelog') {\n return <ChangelogTab entityId={data.deal.id} entityType=\"deal\" />\n }\n\n return null\n })()}\n </div>\n )\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <InjectionSpot spotId={extensionPoints.hosts.dealHeader.spotId} context={injectionContext} data={data} />\n\n <DealDetailHeader\n deal={data.deal}\n owner={data.owner}\n people={data.people}\n companies={data.companies}\n pipelineName={currentPipelineName}\n stageOptions={data.pipelineStages}\n currentStageId={data.deal.pipelineStageId}\n onStageChange={handleStageChange}\n isStageSaving={isStageSaving}\n onSave={handleHeaderSave}\n onDelete={handleDelete}\n isDirty={isDirty}\n isSaving={isSaving}\n />\n\n <InjectionSpot spotId={extensionPoints.hosts.dealStatusBadges.spotId} context={injectionContext} data={data} onDataChange={setData} />\n\n <PipelineStepper\n stages={data.pipelineStages}\n transitions={data.stageTransitions}\n currentStageId={data.deal.pipelineStageId}\n pipelineName={currentPipelineName}\n closureOutcome={data.deal.closureOutcome}\n footer={data.deal.closureOutcome ? null : (\n <DealClosureActionBar\n embedded\n closureOutcome={data.deal.closureOutcome}\n onWon={() => { void handleWon() }}\n onLost={openLostDialog}\n />\n )}\n />\n\n <DealDetailTabs\n activeTab={activeTab}\n onTabChange={handleTabChange}\n injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}\n peopleCount={data.counts.people}\n companiesCount={data.counts.companies}\n >\n <CollapsibleZoneLayout\n pageType=\"deal-detail-v3\"\n entityName={dealName}\n isDirty={isDirty}\n sections={zoneSections}\n zone1DefaultWidth=\"540px\"\n zone1={zone1Content}\n zone2={zone2Content}\n />\n </DealDetailTabs>\n\n <InjectionSpot spotId={extensionPoints.hosts.dealFooter.spotId} context={injectionContext} data={data} />\n </div>\n\n {ConfirmDialogElement}\n\n {selectedActivityEntity ? (\n <ScheduleActivityDialog\n open={scheduleDialogOpen}\n onClose={closeSchedule}\n entityId={selectedActivityEntity.id}\n dealId={data.deal.id}\n entityType={selectedActivityEntity.kind}\n entityName={selectedActivityEntity.label}\n companyName={selectedActivityEntity.kind === 'company' ? selectedActivityEntity.label : data.companies[0]?.label ?? null}\n onActivityCreated={() => { void handleActivityCreated() }}\n editData={scheduleEditData}\n />\n ) : null}\n\n <ConfirmDealLostDialog\n open={lostDialogOpen}\n onClose={closeLostDialog}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n dealValue={amountLabel}\n companyName={data.companies[0]?.label ?? null}\n onConfirm={handleLostConfirm}\n />\n\n <DealWonPopup\n open={wonPopupOpen}\n onClose={closeWonPopup}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n stats={wonStats}\n onViewDashboard={handleViewDashboard}\n onBackToPipeline={handleBackToPipeline}\n />\n\n <DealLostSummaryDialog\n open={lostPopupOpen}\n onClose={closeLostPopup}\n dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n lossNotes={data.deal.lossNotes}\n stats={lostStats}\n onBackToPipeline={handleBackToPipeline}\n onScheduleFollowUp={selectedActivityEntity ? handleScheduleLostFollowUp : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["id"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7048.1.b011912b63",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -252,16 +252,16 @@
|
|
|
252
252
|
"zod": "^4.4.3"
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7048.1.b011912b63",
|
|
256
|
+
"@open-mercato/shared": "0.6.8-develop.7048.1.b011912b63",
|
|
257
|
+
"@open-mercato/ui": "0.6.8-develop.7048.1.b011912b63",
|
|
258
258
|
"react": "^19.0.0",
|
|
259
259
|
"react-dom": "^19.0.0"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
263
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
264
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
262
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7048.1.b011912b63",
|
|
263
|
+
"@open-mercato/shared": "0.6.8-develop.7048.1.b011912b63",
|
|
264
|
+
"@open-mercato/ui": "0.6.8-develop.7048.1.b011912b63",
|
|
265
265
|
"@testing-library/dom": "^10.4.1",
|
|
266
266
|
"@testing-library/jest-dom": "^7.0.0",
|
|
267
267
|
"@testing-library/react": "^16.3.1",
|
|
@@ -148,21 +148,25 @@ export async function postForm(
|
|
|
148
148
|
* The `request` fixture keeps a cookie jar, and `/api/auth/login` sets `auth_token`
|
|
149
149
|
* on it. Every later call through that fixture therefore carries the LAST logged-in
|
|
150
150
|
* user's session, even one that deliberately sends no Authorization header or an
|
|
151
|
-
* `ApiKey` one.
|
|
152
|
-
* the login route marks it `secure` only when `NODE_ENV === 'production'`, so a lane
|
|
153
|
-
* that serves the app with any other NODE_ENV over http keeps it while a production
|
|
154
|
-
* lane silently drops it.
|
|
151
|
+
* `ApiKey` one.
|
|
155
152
|
*
|
|
156
153
|
* A spec asserting "no credentials are rejected" or "this API key alone decides
|
|
157
|
-
* access" must
|
|
158
|
-
*
|
|
159
|
-
* both failed in the standalone lane
|
|
160
|
-
* ephemeral one
|
|
154
|
+
* access" must therefore issue the request from a jar that never saw a login,
|
|
155
|
+
* rather than assuming the fixture's jar is empty. TC-DOCUMENTS-009 and
|
|
156
|
+
* TC-DOCUMENTS-018 both observably failed in the standalone lane — with the
|
|
157
|
+
* replayed cookie visible in the trace — while passing in the ephemeral one, and
|
|
158
|
+
* routing them through this helper fixed both.
|
|
161
159
|
*/
|
|
162
160
|
export async function withCredentialIsolatedRequest<T>(
|
|
163
161
|
use: (request: APIRequestContext) => Promise<T>,
|
|
164
162
|
): Promise<T> {
|
|
165
|
-
|
|
163
|
+
// A hand-built context does not inherit the project's `use.baseURL` the way the
|
|
164
|
+
// `request` fixture does, so it has to repeat the config's own resolution — otherwise
|
|
165
|
+
// a relative path throws here on any run that leaves BASE_URL unset, while the same
|
|
166
|
+
// path works through the fixture.
|
|
167
|
+
const context = await playwrightRequest.newContext({
|
|
168
|
+
baseURL: BASE_URL ?? 'http://localhost:3000',
|
|
169
|
+
});
|
|
166
170
|
try {
|
|
167
171
|
return await use(context);
|
|
168
172
|
} finally {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { extensionPoints } from '@open-mercato/core/modules/customers/extension-points'
|
|
5
5
|
import Link from 'next/link'
|
|
6
|
-
import { Building2, UserSearch, Users } from 'lucide-react'
|
|
6
|
+
import { Building2, FileText, Hash, UserSearch, Users } from 'lucide-react'
|
|
7
7
|
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
8
8
|
import { useRouter, useSearchParams, usePathname } from 'next/navigation'
|
|
9
9
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
@@ -12,7 +12,7 @@ import { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordN
|
|
|
12
12
|
import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
|
|
13
13
|
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
14
14
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
15
|
-
import { CollapsibleZoneLayout } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
|
|
15
|
+
import { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
|
|
16
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
17
|
import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
18
18
|
import { E } from '#generated/entities.ids.generated'
|
|
@@ -57,6 +57,10 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
|
|
|
57
57
|
const pathname = usePathname()
|
|
58
58
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
59
59
|
const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
|
|
60
|
+
const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [
|
|
61
|
+
{ id: 'details', icon: FileText, label: t('customers.people.detail.deals.form.details', 'Deal details') },
|
|
62
|
+
{ id: 'custom', icon: Hash, label: t('customers.people.detail.deals.form.customFields', 'Custom fields') },
|
|
63
|
+
], [t])
|
|
60
64
|
|
|
61
65
|
const { data, setData, isLoading, error, isNotFound, loadData } = useDealData(id)
|
|
62
66
|
const [isDirty, setIsDirty] = React.useState(false)
|
|
@@ -664,6 +668,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
|
|
|
664
668
|
pageType="deal-detail-v3"
|
|
665
669
|
entityName={dealName}
|
|
666
670
|
isDirty={isDirty}
|
|
671
|
+
sections={zoneSections}
|
|
667
672
|
zone1DefaultWidth="540px"
|
|
668
673
|
zone1={zone1Content}
|
|
669
674
|
zone2={zone2Content}
|