@aglyn/plugins-crm 1.0.0-beta.234 → 1.0.0-beta.236
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/package.json +14 -14
- package/src/lib/components/company-detail-page.js +3 -5
- package/src/lib/components/company-detail-page.js.map +1 -1
- package/src/lib/components/company-properties-card.d.ts +11 -0
- package/src/lib/components/company-properties-card.js +7 -3
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +2 -3
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-known-by-card.js +1 -1
- package/src/lib/components/contact-known-by-card.js.map +1 -1
- package/src/lib/components/contact-properties-card.js +1 -1
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/deal-properties-card.js +1 -1
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.js +1 -1
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +4 -8
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-history-card.js +1 -1
- package/src/lib/components/lead-history-card.js.map +1 -1
- package/src/lib/components/lead-properties-card.d.ts +10 -0
- package/src/lib/components/lead-properties-card.js +8 -4
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/linked-deals-card.js +1 -1
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/record-sharing-card.js +3 -1
- package/src/lib/components/record-sharing-card.js.map +1 -1
- package/src/lib/components/settings-section.js +3 -1
- package/src/lib/components/settings-section.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-crm",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.236",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.236",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.236",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.236",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.236",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.236",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.236",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.236",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.236",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.236",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.236",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.236",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.236",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.236",
|
|
41
41
|
"@dnd-kit/core": "^6.3.1",
|
|
42
42
|
"@dnd-kit/utilities": "^3.2.2",
|
|
43
43
|
"@swc/helpers": "0.5.23",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
-
import { CRM_COLLECTIONS
|
|
19
|
+
import { CRM_COLLECTIONS } from "@aglyn/aglyn";
|
|
20
20
|
import { useFirestore, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
|
|
21
21
|
import { Stack, Typography } from "@mui/material";
|
|
22
22
|
import { doc } from "firebase/firestore";
|
|
@@ -32,7 +32,7 @@ import { CrmRecordHeader } from "./crm-record-header.js";
|
|
|
32
32
|
import { RecordActivityCard } from "./record-activity-card.js";
|
|
33
33
|
import RecordFilesCard from "./record-files-card.js";
|
|
34
34
|
import { RecordTasksCard } from "./record-tasks-card.js";
|
|
35
|
-
import CompanyPropertiesCard from "./company-properties-card.js";
|
|
35
|
+
import CompanyPropertiesCard, { COMPANY_PAGE_HELP } from "./company-properties-card.js";
|
|
36
36
|
import { RecordSharingCard } from "./record-sharing-card.js";
|
|
37
37
|
import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
|
|
38
38
|
/**
|
|
@@ -80,9 +80,7 @@ import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
|
|
|
80
80
|
return /*#__PURE__*/ _jsx(CrmRecordHeader, {
|
|
81
81
|
kind: "Company",
|
|
82
82
|
title: undefined,
|
|
83
|
-
help:
|
|
84
|
-
anchor: '#a-companys-page'
|
|
85
|
-
}),
|
|
83
|
+
help: COMPANY_PAGE_HELP,
|
|
86
84
|
backHref: routes.section('companies'),
|
|
87
85
|
backLabel: "Back to companies",
|
|
88
86
|
loading: !settled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmCompany, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard from './company-properties-card'\nimport { RecordSharingCard } from './record-sharing-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n {/* Where the company is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"companies\"\n id={id}\n record={company as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={crmScope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","RecordSharingCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","crmScope","id","basePath","hostId","org","routes","router","firestore","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","anchor","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","object","record","viewingHostIds","consentGroup","hostIds","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, type CrmCompany } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard, { COMPANY_PAGE_HELP } from './company-properties-card'\nimport { RecordSharingCard } from './record-sharing-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={COMPANY_PAGE_HELP}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n {/* Where the company is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"companies\"\n id={id}\n record={company as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={crmScope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","COMPANY_PAGE_HELP","RecordSharingCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","crmScope","id","basePath","hostId","org","routes","router","firestore","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","object","record","viewingHostIds","consentGroup","hostIds","displayName"],"mappings":"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"}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { type AglynOrgBilling, type CrmCompany } from '@aglyn/aglyn';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
2
3
|
import type { CrmScope } from '../hooks/use-crm-scope';
|
|
3
4
|
import type { OrgMemberOptions } from '../hooks/use-org-member-options';
|
|
4
5
|
import type { CrmRoutes } from '../model/crm-routes';
|
|
6
|
+
/**
|
|
7
|
+
* The help on a company's header card — one card, so the page's
|
|
8
|
+
* not-found branch carries the same link as the loaded one.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPANY_PAGE_HELP: {
|
|
11
|
+
title: string;
|
|
12
|
+
excerpt: ReactNode;
|
|
13
|
+
excerptText: string;
|
|
14
|
+
href: string;
|
|
15
|
+
};
|
|
5
16
|
export interface CompanyPropertiesCardProps {
|
|
6
17
|
company: Partial<CrmCompany> & {
|
|
7
18
|
$id: string;
|
|
@@ -32,6 +32,12 @@ import { formatContactCustomValue } from "./contact-custom-columns.js";
|
|
|
32
32
|
import { CrmCallButton, CrmPhoneLink } from "./crm-call-actions.js";
|
|
33
33
|
import { CrmRecordChip, CrmRecordHeader } from "./crm-record-header.js";
|
|
34
34
|
import { useCrmApi } from "./use-crm-api.js";
|
|
35
|
+
/**
|
|
36
|
+
* The help on a company's header card — one card, so the page's
|
|
37
|
+
* not-found branch carries the same link as the loaded one.
|
|
38
|
+
*/ export const COMPANY_PAGE_HELP = pluginDocsHelp('companies', {
|
|
39
|
+
anchor: '#a-companys-page'
|
|
40
|
+
});
|
|
35
41
|
/** A postal address on one line, or nothing. */ function formatAddress(address) {
|
|
36
42
|
if (!address) return '';
|
|
37
43
|
return [
|
|
@@ -281,9 +287,7 @@ import { useCrmApi } from "./use-crm-api.js";
|
|
|
281
287
|
kind: "Company",
|
|
282
288
|
title: String(company.name || company.$id),
|
|
283
289
|
subtitle: company.domain,
|
|
284
|
-
help:
|
|
285
|
-
anchor: '#a-companys-page'
|
|
286
|
-
}),
|
|
290
|
+
help: COMPANY_PAGE_HELP,
|
|
287
291
|
backHref: routes.section('companies'),
|
|
288
292
|
backLabel: "Back to companies",
|
|
289
293
|
actions: /*#__PURE__*/ _jsxs(_Fragment, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n type AglynPostalAddress,\n type CrmCompany,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Button, Link, Stack, Typography } from '@mui/material'\nimport { type ReactNode, useCallback, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport type { CrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport {\n companyDeleteFailureMessage,\n deleteCompanyThroughRoute,\n} from '../model/company-delete'\nimport type { CrmRoutes } from '../model/crm-routes'\nimport { formatMoney } from '../model/deal-board-model'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { formatContactCustomValue } from './contact-custom-columns'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { useCrmApi } from './use-crm-api'\n\nexport interface CompanyPropertiesCardProps {\n company: Partial<CrmCompany> & { $id: string }\n /** Whether the document the card holds is server-confirmed. */\n seed: { fromCache: boolean; unreadable: boolean }\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n crmScope: CrmScope\n members: OrgMemberOptions\n routes: CrmRoutes\n /** Called once the record is gone, so the page can leave it. */\n onDeleted: () => void\n}\n\n/** A postal address on one line, or nothing. */\nfunction formatAddress(address: AglynPostalAddress | null | undefined): string {\n if (!address) return ''\n return [\n address.line1,\n address.line2,\n [address.city, address.state].filter(Boolean).join(', '),\n address.postalCode,\n address.country,\n ]\n .map((part) => String(part ?? '').trim())\n .filter(Boolean)\n .join(' · ')\n}\n\n/**\n * WHAT A COMPANY IS: its fields, read off the one document the page holds,\n * with the edit and the delete beside them (AGL-2597).\n *\n * ## Edit is the create drawer\n *\n * The same form the list creates with, opened on the record. One form means\n * one set of refusals — a domain that is not a domain is refused the same\n * way on day one and on a rename — and one place to add a field.\n *\n * ## Delete is a DETACH first\n *\n * `crm/company-delete` — the route the bulk bar calls per row as well —\n * unlinks the contacts that name this company before removing it, bounded\n * at {@link COMPANY_DETACH_LIMIT} per pass and honest past it: the company\n * stands, and the person is told more remain. The unlink clears each\n * holder's facet that named the company, which is the server's to write\n * (AGL-2804). What this card adds is the confirm, the sentence for each\n * outcome, and leaving the page once the record is gone.\n */\nexport function CompanyPropertiesCard(props: CompanyPropertiesCardProps) {\n const {\n company,\n seed,\n hostId,\n org,\n crmScope,\n members,\n routes,\n onDeleted,\n } = props\n const { scope } = crmScope\n const callCrm = useCrmApi(hostId)\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // The feed the act is logged in, decided by the level it was performed\n // at (AGL-2738): this site's under a site, the organization's at the org\n // hub, where a client-direct append to the company's own site would face\n // a gate the record write never had to pass.\n const logActivity = useCrmActivityLogger(hostId)\n const [editing, setEditing] = useState(false)\n const [deleting, setDeleting] = useState(false)\n // The org's company fields (AGL-2661): one row each under the fixed\n // ones, read by type the way the list's columns read them.\n const fields = useContactFieldDefinitions(crmScope.orgId, 'company')\n\n const handleDelete = useCallback(async () => {\n if (!scope || deleting) return\n const name = String(company.name ?? company.$id)\n const accepted = await confirm({\n title: 'Delete this company?',\n description:\n `\"${name}\" is removed from Companies and unlinked from every ` +\n 'contact at it. The contacts themselves are kept, and so is ' +\n 'anything else filed against the company.',\n confirmationText: 'Delete company',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel, so gating\n // on the resolved value alone would make this always return.\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n setDeleting(true)\n try {\n const outcome = await deleteCompanyThroughRoute(callCrm, company.$id)\n if (!outcome.deleted) {\n enqueueSnackbar(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n `from \"${name}\" and more remain. Delete again to continue.`,\n { variant: 'warning' },\n )\n return\n }\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed.\n logActivity('Deleted company', { type: 'company', id: company.$id, name })\n enqueueSnackbar(\n outcome.detached\n ? `Company deleted and unlinked from ${outcome.detached.toLocaleString()} ` +\n `contact${outcome.detached === 1 ? '' : 's'}`\n : 'Company deleted',\n { variant: 'success', persist: false },\n )\n onDeleted()\n } catch (error) {\n console.error(error)\n enqueueSnackbar(companyDeleteFailureMessage(error), {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setDeleting(false)\n }\n }, [scope, deleting, company, confirm, callCrm, logActivity, enqueueSnackbar, onDeleted])\n\n // The parent company by name (AGL-3514), resolved once and cached.\n const parentId = company.parentCompanyId || null\n const recordName = useCrmRecordNames({\n orgId: crmScope.orgId,\n groupId: null,\n records: parentId ? [{ kind: 'company', id: parentId }] : [],\n })\n /*\n * Grouped as Salesforce's Account page groups them (AGL-3514): what the\n * company is, where it is, and what the team knows. The domain, the\n * industry, the type, the rating and the owner read on the header — the\n * subtitle and the chip row — so the rows list what is left.\n */\n const rows: Array<{ label: string; value: ReactNode }> = [\n {\n label: 'Parent company',\n value: parentId ? (\n <AppLink href={routes.company(parentId)}>\n {recordName('company', parentId) || 'Open company'}\n </AppLink>\n ) : null,\n },\n {\n label: 'Website',\n value: company.website ? (\n <Link href={company.website} target=\"_blank\" rel=\"noreferrer noopener\">\n {company.website}\n </Link>\n ) : null,\n },\n // Callable in one tap (AGL-2661); plain text for a value no dialer takes.\n { label: 'Phone', value: <CrmPhoneLink phone={company.phone} /> },\n { label: 'Fax', value: company.fax },\n { label: 'Ownership', value: company.ownership },\n { label: 'Account source', value: company.accountSource },\n { label: 'Account number', value: company.accountNumber },\n { label: 'Account site', value: company.site },\n { label: 'Ticker symbol', value: company.tickerSymbol },\n { label: 'SIC code', value: company.sicCode },\n {\n label: 'Employees',\n value:\n typeof company.numberOfEmployees === 'number'\n ? company.numberOfEmployees.toLocaleString()\n : null,\n },\n {\n label: 'Annual revenue',\n value:\n typeof company.annualRevenueCents === 'number'\n ? formatMoney(company.annualRevenueCents, company.currency)\n : null,\n },\n { label: 'Tags', value: (company.tags ?? []).join(', ') },\n ]\n const addressRows: Array<{ label: string; value: ReactNode }> = [\n { label: 'Billing address', value: formatAddress(company.address) },\n { label: 'Shipping address', value: formatAddress(company.shippingAddress) },\n ]\n const descriptionRows: Array<{ label: string; value: ReactNode }> = [\n { label: 'Notes', value: company.notes },\n ]\n // The org's own company fields (AGL-2661), after the built-in ones under\n // \"More fields\", as a record's Details keep them; none defined, none drawn.\n const moreRows: Array<{ label: string; value: ReactNode }> = fields.active.map(\n (definition) => {\n const text = formatContactCustomValue(definition, company.custom?.[definition.key])\n return {\n label: definition.label || definition.key,\n value:\n text && definition.type === 'url' ? (\n <Link href={text} target=\"_blank\" rel=\"noreferrer noopener\">\n {text}\n </Link>\n ) : (\n text\n ),\n }\n },\n )\n const factRows = (list: Array<{ label: string; value: ReactNode }>) =>\n list.map((row) => (\n <Stack\n key={row.label}\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'baseline' }}\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ minWidth: 96, flexShrink: 0 }}\n >\n {row.label}\n </Typography>\n <Typography\n variant=\"body2\"\n color={row.value ? 'text.primary' : 'text.secondary'}\n sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}\n >\n {row.value || '—'}\n </Typography>\n </Stack>\n ))\n\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={String(company.name || company.$id)}\n subtitle={company.domain}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n actions={\n <>\n <CrmCallButton\n hostId={hostId}\n org={org}\n link={{ companyId: company.$id }}\n phone={company.phone}\n />\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n onClick={() => setEditing(true)}\n >\n {'Edit'}\n </Button>\n </>\n }\n menuItems={[\n {\n key: 'delete',\n label: deleting ? 'Deleting…' : 'Delete company',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !scope || deleting,\n disabledReason: deleting ? 'The company is being deleted' : 'The organization has not loaded',\n onClick: () => void handleDelete(),\n },\n ]}\n chips={\n <>\n <CrmRecordChip label=\"Type\" value={company.type ?? undefined} />\n <CrmRecordChip label=\"Industry\" value={company.industry} />\n <CrmRecordChip label=\"Rating\" value={company.rating ?? undefined} />\n <CrmRecordChip\n label=\"Owner\"\n value={\n company.ownerUid\n ? members.ready\n ? members.labelFor(company.ownerUid)\n : '…'\n : undefined\n }\n />\n </>\n }\n >\n <Stack spacing={1}>\n {factRows(rows)}\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'Address information'}\n </Typography>\n {factRows(addressRows)}\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'Description information'}\n </Typography>\n {factRows(descriptionRows)}\n {moreRows.length ? (\n <>\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'More fields'}\n </Typography>\n {factRows(moreRows)}\n </>\n ) : null}\n </Stack>\n {editing ? (\n <CompanyEditDrawer\n open\n onClose={() => setEditing(false)}\n hostId={hostId}\n org={org}\n company={company}\n seed={seed}\n members={members}\n onSaved={() => setEditing(false)}\n />\n ) : null}\n </CrmRecordHeader>\n )\n}\nCompanyPropertiesCard.displayName = 'CompanyPropertiesCard'\n\nexport default CompanyPropertiesCard\n"],"names":["pluginDocsHelp","mdiDeleteOutline","mdiPencilOutline","AppLink","MdiIcon","useConfirmationContext","useSnackbar","Button","Link","Stack","Typography","useCallback","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordNames","COMPANY_DETACH_LIMIT","companyDeleteFailureMessage","deleteCompanyThroughRoute","formatMoney","CompanyEditDrawer","formatContactCustomValue","CrmCallButton","CrmPhoneLink","CrmRecordChip","CrmRecordHeader","useCrmApi","formatAddress","address","line1","line2","city","state","filter","Boolean","join","postalCode","country","map","part","String","trim","CompanyPropertiesCard","props","company","seed","hostId","org","crmScope","members","routes","onDeleted","scope","callCrm","confirm","enqueueSnackbar","logActivity","editing","setEditing","deleting","setDeleting","fields","orgId","handleDelete","name","$id","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","deleted","toLocaleString","variant","type","id","detached","persist","error","console","allowDuplicate","parentId","parentCompanyId","recordName","groupId","records","kind","rows","label","value","href","website","target","rel","phone","fax","ownership","accountSource","accountNumber","site","tickerSymbol","sicCode","numberOfEmployees","annualRevenueCents","currency","tags","addressRows","shippingAddress","descriptionRows","notes","moreRows","active","definition","text","custom","key","factRows","list","row","direction","spacing","sx","alignItems","minWidth","flexShrink","whiteSpace","overflowWrap","subtitle","domain","help","anchor","backHref","section","backLabel","actions","link","companyId","size","startIcon","path","onClick","menuItems","icon","destructive","disabled","disabledReason","chips","undefined","industry","rating","ownerUid","ready","labelFor","pt","length","open","onClose","onSaved","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n type AglynPostalAddress,\n type CrmCompany,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Button, Link, Stack, Typography } from '@mui/material'\nimport { type ReactNode, useCallback, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport type { CrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport {\n companyDeleteFailureMessage,\n deleteCompanyThroughRoute,\n} from '../model/company-delete'\nimport type { CrmRoutes } from '../model/crm-routes'\nimport { formatMoney } from '../model/deal-board-model'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { formatContactCustomValue } from './contact-custom-columns'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { useCrmApi } from './use-crm-api'\n\n/**\n * The help on a company's header card — one card, so the page's\n * not-found branch carries the same link as the loaded one.\n */\nexport const COMPANY_PAGE_HELP = pluginDocsHelp('companies', { anchor: '#a-companys-page' })\n\nexport interface CompanyPropertiesCardProps {\n company: Partial<CrmCompany> & { $id: string }\n /** Whether the document the card holds is server-confirmed. */\n seed: { fromCache: boolean; unreadable: boolean }\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n crmScope: CrmScope\n members: OrgMemberOptions\n routes: CrmRoutes\n /** Called once the record is gone, so the page can leave it. */\n onDeleted: () => void\n}\n\n/** A postal address on one line, or nothing. */\nfunction formatAddress(address: AglynPostalAddress | null | undefined): string {\n if (!address) return ''\n return [\n address.line1,\n address.line2,\n [address.city, address.state].filter(Boolean).join(', '),\n address.postalCode,\n address.country,\n ]\n .map((part) => String(part ?? '').trim())\n .filter(Boolean)\n .join(' · ')\n}\n\n/**\n * WHAT A COMPANY IS: its fields, read off the one document the page holds,\n * with the edit and the delete beside them (AGL-2597).\n *\n * ## Edit is the create drawer\n *\n * The same form the list creates with, opened on the record. One form means\n * one set of refusals — a domain that is not a domain is refused the same\n * way on day one and on a rename — and one place to add a field.\n *\n * ## Delete is a DETACH first\n *\n * `crm/company-delete` — the route the bulk bar calls per row as well —\n * unlinks the contacts that name this company before removing it, bounded\n * at {@link COMPANY_DETACH_LIMIT} per pass and honest past it: the company\n * stands, and the person is told more remain. The unlink clears each\n * holder's facet that named the company, which is the server's to write\n * (AGL-2804). What this card adds is the confirm, the sentence for each\n * outcome, and leaving the page once the record is gone.\n */\nexport function CompanyPropertiesCard(props: CompanyPropertiesCardProps) {\n const {\n company,\n seed,\n hostId,\n org,\n crmScope,\n members,\n routes,\n onDeleted,\n } = props\n const { scope } = crmScope\n const callCrm = useCrmApi(hostId)\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // The feed the act is logged in, decided by the level it was performed\n // at (AGL-2738): this site's under a site, the organization's at the org\n // hub, where a client-direct append to the company's own site would face\n // a gate the record write never had to pass.\n const logActivity = useCrmActivityLogger(hostId)\n const [editing, setEditing] = useState(false)\n const [deleting, setDeleting] = useState(false)\n // The org's company fields (AGL-2661): one row each under the fixed\n // ones, read by type the way the list's columns read them.\n const fields = useContactFieldDefinitions(crmScope.orgId, 'company')\n\n const handleDelete = useCallback(async () => {\n if (!scope || deleting) return\n const name = String(company.name ?? company.$id)\n const accepted = await confirm({\n title: 'Delete this company?',\n description:\n `\"${name}\" is removed from Companies and unlinked from every ` +\n 'contact at it. The contacts themselves are kept, and so is ' +\n 'anything else filed against the company.',\n confirmationText: 'Delete company',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel, so gating\n // on the resolved value alone would make this always return.\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n setDeleting(true)\n try {\n const outcome = await deleteCompanyThroughRoute(callCrm, company.$id)\n if (!outcome.deleted) {\n enqueueSnackbar(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n `from \"${name}\" and more remain. Delete again to continue.`,\n { variant: 'warning' },\n )\n return\n }\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed.\n logActivity('Deleted company', { type: 'company', id: company.$id, name })\n enqueueSnackbar(\n outcome.detached\n ? `Company deleted and unlinked from ${outcome.detached.toLocaleString()} ` +\n `contact${outcome.detached === 1 ? '' : 's'}`\n : 'Company deleted',\n { variant: 'success', persist: false },\n )\n onDeleted()\n } catch (error) {\n console.error(error)\n enqueueSnackbar(companyDeleteFailureMessage(error), {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setDeleting(false)\n }\n }, [scope, deleting, company, confirm, callCrm, logActivity, enqueueSnackbar, onDeleted])\n\n // The parent company by name (AGL-3514), resolved once and cached.\n const parentId = company.parentCompanyId || null\n const recordName = useCrmRecordNames({\n orgId: crmScope.orgId,\n groupId: null,\n records: parentId ? [{ kind: 'company', id: parentId }] : [],\n })\n /*\n * Grouped as Salesforce's Account page groups them (AGL-3514): what the\n * company is, where it is, and what the team knows. The domain, the\n * industry, the type, the rating and the owner read on the header — the\n * subtitle and the chip row — so the rows list what is left.\n */\n const rows: Array<{ label: string; value: ReactNode }> = [\n {\n label: 'Parent company',\n value: parentId ? (\n <AppLink href={routes.company(parentId)}>\n {recordName('company', parentId) || 'Open company'}\n </AppLink>\n ) : null,\n },\n {\n label: 'Website',\n value: company.website ? (\n <Link href={company.website} target=\"_blank\" rel=\"noreferrer noopener\">\n {company.website}\n </Link>\n ) : null,\n },\n // Callable in one tap (AGL-2661); plain text for a value no dialer takes.\n { label: 'Phone', value: <CrmPhoneLink phone={company.phone} /> },\n { label: 'Fax', value: company.fax },\n { label: 'Ownership', value: company.ownership },\n { label: 'Account source', value: company.accountSource },\n { label: 'Account number', value: company.accountNumber },\n { label: 'Account site', value: company.site },\n { label: 'Ticker symbol', value: company.tickerSymbol },\n { label: 'SIC code', value: company.sicCode },\n {\n label: 'Employees',\n value:\n typeof company.numberOfEmployees === 'number'\n ? company.numberOfEmployees.toLocaleString()\n : null,\n },\n {\n label: 'Annual revenue',\n value:\n typeof company.annualRevenueCents === 'number'\n ? formatMoney(company.annualRevenueCents, company.currency)\n : null,\n },\n { label: 'Tags', value: (company.tags ?? []).join(', ') },\n ]\n const addressRows: Array<{ label: string; value: ReactNode }> = [\n { label: 'Billing address', value: formatAddress(company.address) },\n { label: 'Shipping address', value: formatAddress(company.shippingAddress) },\n ]\n const descriptionRows: Array<{ label: string; value: ReactNode }> = [\n { label: 'Notes', value: company.notes },\n ]\n // The org's own company fields (AGL-2661), after the built-in ones under\n // \"More fields\", as a record's Details keep them; none defined, none drawn.\n const moreRows: Array<{ label: string; value: ReactNode }> = fields.active.map(\n (definition) => {\n const text = formatContactCustomValue(definition, company.custom?.[definition.key])\n return {\n label: definition.label || definition.key,\n value:\n text && definition.type === 'url' ? (\n <Link href={text} target=\"_blank\" rel=\"noreferrer noopener\">\n {text}\n </Link>\n ) : (\n text\n ),\n }\n },\n )\n const factRows = (list: Array<{ label: string; value: ReactNode }>) =>\n list.map((row) => (\n <Stack\n key={row.label}\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'baseline' }}\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ minWidth: 96, flexShrink: 0 }}\n >\n {row.label}\n </Typography>\n <Typography\n variant=\"body2\"\n color={row.value ? 'text.primary' : 'text.secondary'}\n sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}\n >\n {row.value || '—'}\n </Typography>\n </Stack>\n ))\n\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={String(company.name || company.$id)}\n subtitle={company.domain}\n help={COMPANY_PAGE_HELP}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n actions={\n <>\n <CrmCallButton\n hostId={hostId}\n org={org}\n link={{ companyId: company.$id }}\n phone={company.phone}\n />\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n onClick={() => setEditing(true)}\n >\n {'Edit'}\n </Button>\n </>\n }\n menuItems={[\n {\n key: 'delete',\n label: deleting ? 'Deleting…' : 'Delete company',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !scope || deleting,\n disabledReason: deleting ? 'The company is being deleted' : 'The organization has not loaded',\n onClick: () => void handleDelete(),\n },\n ]}\n chips={\n <>\n <CrmRecordChip label=\"Type\" value={company.type ?? undefined} />\n <CrmRecordChip label=\"Industry\" value={company.industry} />\n <CrmRecordChip label=\"Rating\" value={company.rating ?? undefined} />\n <CrmRecordChip\n label=\"Owner\"\n value={\n company.ownerUid\n ? members.ready\n ? members.labelFor(company.ownerUid)\n : '…'\n : undefined\n }\n />\n </>\n }\n >\n <Stack spacing={1}>\n {factRows(rows)}\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'Address information'}\n </Typography>\n {factRows(addressRows)}\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'Description information'}\n </Typography>\n {factRows(descriptionRows)}\n {moreRows.length ? (\n <>\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'More fields'}\n </Typography>\n {factRows(moreRows)}\n </>\n ) : null}\n </Stack>\n {editing ? (\n <CompanyEditDrawer\n open\n onClose={() => setEditing(false)}\n hostId={hostId}\n org={org}\n company={company}\n seed={seed}\n members={members}\n onSaved={() => setEditing(false)}\n />\n ) : null}\n </CrmRecordHeader>\n )\n}\nCompanyPropertiesCard.displayName = 'CompanyPropertiesCard'\n\nexport default CompanyPropertiesCard\n"],"names":["pluginDocsHelp","mdiDeleteOutline","mdiPencilOutline","AppLink","MdiIcon","useConfirmationContext","useSnackbar","Button","Link","Stack","Typography","useCallback","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordNames","COMPANY_DETACH_LIMIT","companyDeleteFailureMessage","deleteCompanyThroughRoute","formatMoney","CompanyEditDrawer","formatContactCustomValue","CrmCallButton","CrmPhoneLink","CrmRecordChip","CrmRecordHeader","useCrmApi","COMPANY_PAGE_HELP","anchor","formatAddress","address","line1","line2","city","state","filter","Boolean","join","postalCode","country","map","part","String","trim","CompanyPropertiesCard","props","company","seed","hostId","org","crmScope","members","routes","onDeleted","scope","callCrm","confirm","enqueueSnackbar","logActivity","editing","setEditing","deleting","setDeleting","fields","orgId","handleDelete","name","$id","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","deleted","toLocaleString","variant","type","id","detached","persist","error","console","allowDuplicate","parentId","parentCompanyId","recordName","groupId","records","kind","rows","label","value","href","website","target","rel","phone","fax","ownership","accountSource","accountNumber","site","tickerSymbol","sicCode","numberOfEmployees","annualRevenueCents","currency","tags","addressRows","shippingAddress","descriptionRows","notes","moreRows","active","definition","text","custom","key","factRows","list","row","direction","spacing","sx","alignItems","minWidth","flexShrink","whiteSpace","overflowWrap","subtitle","domain","help","backHref","section","backLabel","actions","link","companyId","size","startIcon","path","onClick","menuItems","icon","destructive","disabled","disabledReason","chips","undefined","industry","rating","ownerUid","ready","labelFor","pt","length","open","onClose","onSaved","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAIEA,cAAc,QACT,eAAc;AACrB,SAASC,gBAAgB,EAAEC,gBAAgB,QAAQ,yBAAwB;AAC3E,SAASC,OAAO,EAAEC,OAAO,EAAEC,sBAAsB,QAAQ,uBAAsB;AAC/E,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SAASC,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAC/D,SAAyBC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7D,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,oBAAoB,QAAQ,sCAAkC;AACvE,SAASC,iBAAiB,QAAQ,mCAA+B;AAGjE,SAASC,oBAAoB,QAAQ,wBAAoB;AACzD,SACEC,2BAA2B,EAC3BC,yBAAyB,QACpB,6BAAyB;AAEhC,SAASC,WAAW,QAAQ,+BAA2B;AACvD,OAAOC,uBAAuB,2BAAuB;AACrD,SAASC,wBAAwB,QAAQ,8BAA0B;AACnE,SAASC,aAAa,EAAEC,YAAY,QAAQ,wBAAoB;AAChE,SAASC,aAAa,EAAEC,eAAe,QAAQ,yBAAqB;AACpE,SAASC,SAAS,QAAQ,mBAAe;AAEzC;;;CAGC,GACD,OAAO,MAAMC,oBAAoB3B,eAAe,aAAa;IAAE4B,QAAQ;AAAmB,GAAE;AAgB5F,8CAA8C,GAC9C,SAASC,cAAcC,OAA8C;IACnE,IAAI,CAACA,SAAS,OAAO;IACrB,OAAO;QACLA,QAAQC,KAAK;QACbD,QAAQE,KAAK;QACb;YAACF,QAAQG,IAAI;YAAEH,QAAQI,KAAK;SAAC,CAACC,MAAM,CAACC,SAASC,IAAI,CAAC;QACnDP,QAAQQ,UAAU;QAClBR,QAAQS,OAAO;KAChB,CACEC,GAAG,CAAC,CAACC,OAASC,OAAOD,eAAAA,OAAQ,IAAIE,IAAI,IACrCR,MAAM,CAACC,SACPC,IAAI,CAAC;AACV;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASO,sBAAsBC,KAAiC;QAkI1CC,eA4FgBA,eAEEA;IA/N7C,MAAM,EACJA,OAAO,EACPC,IAAI,EACJC,MAAM,EACNC,GAAG,EACHC,QAAQ,EACRC,OAAO,EACPC,MAAM,EACNC,SAAS,EACV,GAAGR;IACJ,MAAM,EAAES,KAAK,EAAE,GAAGJ;IAClB,MAAMK,UAAU7B,UAAUsB;IAC1B,MAAM,EAAEQ,OAAO,EAAE,GAAGnD;IACpB,MAAM,EAAEoD,eAAe,EAAE,GAAGnD;IAC5B,uEAAuE;IACvE,yEAAyE;IACzE,yEAAyE;IACzE,6CAA6C;IAC7C,MAAMoD,cAAc5C,qBAAqBkC;IACzC,MAAM,CAACW,SAASC,WAAW,GAAGhD,SAAS;IACvC,MAAM,CAACiD,UAAUC,YAAY,GAAGlD,SAAS;IACzC,oEAAoE;IACpE,2DAA2D;IAC3D,MAAMmD,SAASlD,2BAA2BqC,SAASc,KAAK,EAAE;IAE1D,MAAMC,eAAetD,YAAY;YAEXmC;QADpB,IAAI,CAACQ,SAASO,UAAU;QACxB,MAAMK,OAAOxB,QAAOI,gBAAAA,QAAQoB,IAAI,YAAZpB,gBAAgBA,QAAQqB,GAAG;QAC/C,MAAMC,WAAW,MAAMZ,QAAQ;YAC7Ba,OAAO;YACPC,aACE,CAAC,CAAC,EAAEJ,KAAK,oDAAoD,CAAC,GAC9D,gEACA;YACFK,kBAAkB;YAClBC,yBAAyB;gBAAEC,OAAO;YAAQ;QAC5C,EACE,oEAAoE;QACpE,6DAA6D;SAC5DC,IAAI,CAAC,IAAM,MACXC,KAAK,CAAC,IAAM;QACf,IAAI,CAACP,UAAU;QACfN,YAAY;QACZ,IAAI;YACF,MAAMc,UAAU,MAAM1D,0BAA0BqC,SAAST,QAAQqB,GAAG;YACpE,IAAI,CAACS,QAAQC,OAAO,EAAE;gBACpBpB,gBACE,GAAGzC,qBAAqB8D,cAAc,GAAG,wBAAwB,CAAC,GAChE,CAAC,MAAM,EAAEZ,KAAK,4CAA4C,CAAC,EAC7D;oBAAEa,SAAS;gBAAU;gBAEvB;YACF;YACA,iEAAiE;YACjE,mEAAmE;YACnE,YAAY;YACZrB,YAAY,mBAAmB;gBAAEsB,MAAM;gBAAWC,IAAInC,QAAQqB,GAAG;gBAAED;YAAK;YACxET,gBACEmB,QAAQM,QAAQ,GACZ,CAAC,kCAAkC,EAAEN,QAAQM,QAAQ,CAACJ,cAAc,GAAG,CAAC,CAAC,GACvE,CAAC,OAAO,EAAEF,QAAQM,QAAQ,KAAK,IAAI,KAAK,KAAK,GAC/C,mBACJ;gBAAEH,SAAS;gBAAWI,SAAS;YAAM;YAEvC9B;QACF,EAAE,OAAO+B,OAAO;YACdC,QAAQD,KAAK,CAACA;YACd3B,gBAAgBxC,4BAA4BmE,QAAQ;gBAClDL,SAAS;gBACTO,gBAAgB;YAClB;QACF,SAAU;YACRxB,YAAY;QACd;IACF,GAAG;QAACR;QAAOO;QAAUf;QAASU;QAASD;QAASG;QAAaD;QAAiBJ;KAAU;IAExF,mEAAmE;IACnE,MAAMkC,WAAWzC,QAAQ0C,eAAe,IAAI;IAC5C,MAAMC,aAAa1E,kBAAkB;QACnCiD,OAAOd,SAASc,KAAK;QACrB0B,SAAS;QACTC,SAASJ,WAAW;YAAC;gBAAEK,MAAM;gBAAWX,IAAIM;YAAS;SAAE,GAAG,EAAE;IAC9D;IACA;;;;;GAKC,GACD,MAAMM,OAAmD;QACvD;YACEC,OAAO;YACPC,OAAOR,yBACL,KAACpF;gBAAQ6F,MAAM5C,OAAON,OAAO,CAACyC;0BAC3BE,WAAW,WAAWF,aAAa;iBAEpC;QACN;QACA;YACEO,OAAO;YACPC,OAAOjD,QAAQmD,OAAO,iBACpB,KAACzF;gBAAKwF,MAAMlD,QAAQmD,OAAO;gBAAEC,QAAO;gBAASC,KAAI;0BAC9CrD,QAAQmD,OAAO;iBAEhB;QACN;QACA,0EAA0E;QAC1E;YAAEH,OAAO;YAASC,qBAAO,KAACxE;gBAAa6E,OAAOtD,QAAQsD,KAAK;;QAAK;QAChE;YAAEN,OAAO;YAAOC,OAAOjD,QAAQuD,GAAG;QAAC;QACnC;YAAEP,OAAO;YAAaC,OAAOjD,QAAQwD,SAAS;QAAC;QAC/C;YAAER,OAAO;YAAkBC,OAAOjD,QAAQyD,aAAa;QAAC;QACxD;YAAET,OAAO;YAAkBC,OAAOjD,QAAQ0D,aAAa;QAAC;QACxD;YAAEV,OAAO;YAAgBC,OAAOjD,QAAQ2D,IAAI;QAAC;QAC7C;YAAEX,OAAO;YAAiBC,OAAOjD,QAAQ4D,YAAY;QAAC;QACtD;YAAEZ,OAAO;YAAYC,OAAOjD,QAAQ6D,OAAO;QAAC;QAC5C;YACEb,OAAO;YACPC,OACE,OAAOjD,QAAQ8D,iBAAiB,KAAK,WACjC9D,QAAQ8D,iBAAiB,CAAC9B,cAAc,KACxC;QACR;QACA;YACEgB,OAAO;YACPC,OACE,OAAOjD,QAAQ+D,kBAAkB,KAAK,WAClC1F,YAAY2B,QAAQ+D,kBAAkB,EAAE/D,QAAQgE,QAAQ,IACxD;QACR;QACA;YAAEhB,OAAO;YAAQC,OAAO,EAACjD,gBAAAA,QAAQiE,IAAI,YAAZjE,gBAAgB,EAAE,EAAET,IAAI,CAAC;QAAM;KACzD;IACD,MAAM2E,cAA0D;QAC9D;YAAElB,OAAO;YAAmBC,OAAOlE,cAAciB,QAAQhB,OAAO;QAAE;QAClE;YAAEgE,OAAO;YAAoBC,OAAOlE,cAAciB,QAAQmE,eAAe;QAAE;KAC5E;IACD,MAAMC,kBAA8D;QAClE;YAAEpB,OAAO;YAASC,OAAOjD,QAAQqE,KAAK;QAAC;KACxC;IACD,yEAAyE;IACzE,4EAA4E;IAC5E,MAAMC,WAAuDrD,OAAOsD,MAAM,CAAC7E,GAAG,CAC5E,CAAC8E;YACmDxE;QAAlD,MAAMyE,OAAOlG,yBAAyBiG,aAAYxE,kBAAAA,QAAQ0E,MAAM,qBAAd1E,eAAgB,CAACwE,WAAWG,GAAG,CAAC;QAClF,OAAO;YACL3B,OAAOwB,WAAWxB,KAAK,IAAIwB,WAAWG,GAAG;YACzC1B,OACEwB,QAAQD,WAAWtC,IAAI,KAAK,sBAC1B,KAACxE;gBAAKwF,MAAMuB;gBAAMrB,QAAO;gBAASC,KAAI;0BACnCoB;iBAGHA;QAEN;IACF;IAEF,MAAMG,WAAW,CAACC,OAChBA,KAAKnF,GAAG,CAAC,CAACoF,oBACR,MAACnH;gBAECoH,WAAU;gBACVC,SAAS;gBACTC,IAAI;oBAAEC,YAAY;gBAAW;;kCAE7B,KAACtH;wBACCqE,SAAQ;wBACRN,OAAM;wBACNsD,IAAI;4BAAEE,UAAU;4BAAIC,YAAY;wBAAE;kCAEjCN,IAAI9B,KAAK;;kCAEZ,KAACpF;wBACCqE,SAAQ;wBACRN,OAAOmD,IAAI7B,KAAK,GAAG,iBAAiB;wBACpCgC,IAAI;4BAAEI,YAAY;4BAAYC,cAAc;wBAAW;kCAEtDR,IAAI7B,KAAK,IAAI;;;eAjBX6B,IAAI9B,KAAK;IAsBpB,qBACE,MAACrE;QACCmE,MAAK;QACLvB,OAAO3B,OAAOI,QAAQoB,IAAI,IAAIpB,QAAQqB,GAAG;QACzCkE,UAAUvF,QAAQwF,MAAM;QACxBC,MAAM5G;QACN6G,UAAUpF,OAAOqF,OAAO,CAAC;QACzBC,WAAU;QACVC,uBACE;;8BACE,KAACrH;oBACC0B,QAAQA;oBACRC,KAAKA;oBACL2F,MAAM;wBAAEC,WAAW/F,QAAQqB,GAAG;oBAAC;oBAC/BiC,OAAOtD,QAAQsD,KAAK;;8BAEtB,KAAC7F;oBACCuI,MAAK;oBACLrE,OAAM;oBACNM,SAAQ;oBACRgE,yBAAW,KAAC3I;wBAAQ4I,MAAM9I,iBAAiB8I,IAAI;wBAAEF,MAAM;;oBACvDG,SAAS,IAAMrF,WAAW;8BAEzB;;;;QAIPsF,WAAW;YACT;gBACEzB,KAAK;gBACL3B,OAAOjC,WAAW,cAAc;gBAChCsF,oBAAM,KAAC/I;oBAAQ4I,MAAM/I,iBAAiB+I,IAAI;oBAAEF,MAAM;;gBAClDM,aAAa;gBACbC,UAAU,CAAC/F,SAASO;gBACpByF,gBAAgBzF,WAAW,iCAAiC;gBAC5DoF,SAAS,IAAM,KAAKhF;YACtB;SACD;QACDsF,qBACE;;8BACE,KAAC/H;oBAAcsE,OAAM;oBAAOC,KAAK,GAAEjD,gBAAAA,QAAQkC,IAAI,YAAZlC,gBAAgB0G;;8BACnD,KAAChI;oBAAcsE,OAAM;oBAAWC,OAAOjD,QAAQ2G,QAAQ;;8BACvD,KAACjI;oBAAcsE,OAAM;oBAASC,KAAK,GAAEjD,kBAAAA,QAAQ4G,MAAM,YAAd5G,kBAAkB0G;;8BACvD,KAAChI;oBACCsE,OAAM;oBACNC,OACEjD,QAAQ6G,QAAQ,GACZxG,QAAQyG,KAAK,GACXzG,QAAQ0G,QAAQ,CAAC/G,QAAQ6G,QAAQ,IACjC,MACFH;;;;;0BAMZ,MAAC/I;gBAAMqH,SAAS;;oBACbJ,SAAS7B;kCACV,KAACnF;wBAAWqE,SAAQ;wBAAYgD,IAAI;4BAAE+B,IAAI;wBAAE;kCACzC;;oBAEFpC,SAASV;kCACV,KAACtG;wBAAWqE,SAAQ;wBAAYgD,IAAI;4BAAE+B,IAAI;wBAAE;kCACzC;;oBAEFpC,SAASR;oBACTE,SAAS2C,MAAM,iBACd;;0CACE,KAACrJ;gCAAWqE,SAAQ;gCAAYgD,IAAI;oCAAE+B,IAAI;gCAAE;0CACzC;;4BAEFpC,SAASN;;yBAEV;;;YAELzD,wBACC,KAACvC;gBACC4I,IAAI;gBACJC,SAAS,IAAMrG,WAAW;gBAC1BZ,QAAQA;gBACRC,KAAKA;gBACLH,SAASA;gBACTC,MAAMA;gBACNI,SAASA;gBACT+G,SAAS,IAAMtG,WAAW;iBAE1B;;;AAGV;AACAhB,sBAAsBuH,WAAW,GAAG;AAEpC,eAAevH,sBAAqB"}
|
|
@@ -56,9 +56,8 @@ import { CrmShareChipView, RecordSharingCard } from "./record-sharing-card.js";
|
|
|
56
56
|
import { RecordTasksCard } from "./record-tasks-card.js";
|
|
57
57
|
import { useSiteRouteContext } from "./use-site-route-context.js";
|
|
58
58
|
import { useOrgMembers } from "./use-org-members.js";
|
|
59
|
-
const contactDocsHelp = pluginDocsHelp('
|
|
60
|
-
anchor: '#the-
|
|
61
|
-
excerpt: "One person's record: the profile your team keeps on them, where they " + 'came from, what they are filed under, and what the site recorded.'
|
|
59
|
+
const contactDocsHelp = pluginDocsHelp('contactRecord', {
|
|
60
|
+
anchor: '#the-record-page'
|
|
62
61
|
});
|
|
63
62
|
/**
|
|
64
63
|
* `/crm/contacts/{contactId}` — one person (AGL-2596).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CONTACT_LIFECYCLE_STAGE_LABELS, pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n pluginRecordByEmailHref,\n pluginRecordHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { mdiDeleteOutline, mdiMerge } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Tooltip, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useCallback, useMemo } from 'react'\nimport { useContactRemove } from '../hooks/use-contact-remove'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { CrmCreateSiteDefault, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { contactPrimaryGroup, contactRecordFromDoc } from '../model/contact-record'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport ContactAssociationsCard from './contact-associations-card'\nimport ContactDuplicatesCard from './contact-duplicates-card'\nimport ContactKnownByCard from './contact-known-by-card'\nimport ContactMergeDialog, { useContactMergeDialog } from './contact-merge-dialog'\nimport ContactPropertiesCard from './contact-properties-card'\nimport ContactTimelineCard from './contact-timeline-card'\nimport { AddToListButton } from './add-to-list-button'\nimport { useBookingDoor } from './crm-booking-zone'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport { ContactDealsCard } from './contact-deals-card'\nimport { CrmCallButton } from './crm-call-actions'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { CrmAccountLockChip, CrmEmailGatewayChip } from './crm-email-check'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { CrmSuiteNotice, crmSuiteIncluded, crmSuiteLockedReason } from './crm-suite-lock'\nimport { useErasePersonAction } from './erase-person-action'\nimport RecordFilesCard from './record-files-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport { useSiteRouteContext } from './use-site-route-context'\nimport { useOrgMembers } from './use-org-members'\n\nconst contactDocsHelp = pluginDocsHelp('contacts', {\n anchor: '#the-contacts-page',\n excerpt:\n \"One person's record: the profile your team keeps on them, where they \" +\n 'came from, what they are filed under, and what the site recorded.',\n})\n\n/**\n * `/crm/contacts/{contactId}` — one person (AGL-2596).\n *\n * A ROUTE rather than the v1 drawer, because a person is the thing every\n * other CRM record points at: a deal, a task and an activity all name a\n * contact, and a link that opens a drawer on top of a list is not a link\n * anybody can paste.\n *\n * ## What this page reads\n *\n * ONE document, by id, through the scoped rules — a single-document get is\n * judged on that document's own `visibleTo`, so no `array-contains-any`\n * filter is needed here the way it is on the list. The row is then flattened\n * through the viewing group's facet ONCE (`contactRecordFromDoc`) and the\n * cards below read the flat record: none of them reaches into\n * `facets.{groupId}` for itself, which is what keeps a card from ever\n * showing another holder's notes.\n *\n * Beyond the document: the team roster (one request, for the owner picker)\n * and the site's campaigns (one listen, for the filing picker) — both paid\n * here because opening a record IS the ask for them, and neither is paid by\n * the list.\n *\n * ## The cards\n *\n * Each in its own file, so a card can grow without this page growing:\n * properties (the editable profile, with the org's custom fields under it),\n * the relationship (sources, attribution, consent, campaign filing) and the\n * timeline. Other CRM sections add a card here by inserting one import and\n * one line.\n *\n * ## Delete is a detach\n *\n * The row is shared by every site that has captured this person, so the\n * action in the overflow menu drops THIS group's facet, consent, attribution\n * and scope tokens, and deletes the document only when nobody else is left\n * holding it. `crm/contact-remove` decides which against the stored\n * document and keeps any marketing refusal the person gave (AGL-3338); the\n * rules refuse a client the write. Not the erasure path: a privacy erasure\n * removes the person regardless of who else holds them.\n */\nexport function ContactDetailPage(props: CrmDetailPageProps) {\n const { hostId, org, id, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n // The org root, the viewing group and what this viewer may list — the\n // merge search's and the duplicates query's one clause — from the one\n // scope hook (AGL-2614); at the organization level the group is `null`\n // and resolved per person below, and the listeners carry no clause\n // (AGL-2630).\n const {\n scope,\n orgId,\n consentGroup: viewingGroup,\n visibleTo,\n } = useCrmScope({ hostId, org })\n const mount = useCrmOrgMount()\n const merge = useContactMergeDialog()\n /*\n * THE CRM (AGL-2788). This page is the CRM's, and the shell mounts no CRM\n * page for a plan without it (AGL-2851). What locks below — owner, stage\n * and company; tasks, deals, files and custom fields; a logged call or\n * activity; one-to-one email; a merge — draws only for an org whose plan\n * lacks the CRM. The shell mounted this page once the org had settled, so\n * this is the plan, not a paint of the org still loading.\n */\n const suiteIncluded = crmSuiteIncluded(org)\n\n const {\n data: row,\n status,\n fromCache,\n } = useFirestoreDoc<Record<string, any>>(\n () => (scope ? doc(firestore, scope[0], scope[1], 'contacts', id) : null),\n [firestore, scope, id],\n { idField: '$id' },\n )\n /*\n * The controller this page is showing. Under a site: the sites declared\n * to be one sender, or this site alone — resolved from the org document\n * the shell already passed, so it costs no read. At the ORGANIZATION\n * level: the person's own PRIMARY holder, the first capturing site's\n * group, so an org-wide member reads the profile that site keeps and an\n * edit goes back to the same facet; the \"Known by\" card says which other\n * sites hold one.\n */\n const consentGroup = useMemo(\n () => viewingGroup ?? contactPrimaryGroup(row, org as Record<string, unknown>),\n [viewingGroup, row, org],\n )\n /**\n * The SITE the page acts as, for what needs one — the lead lookup, the\n * campaign filing, the site's activity feed: the mounted site, or the\n * primary holder's, or nothing for a row no site has captured.\n */\n const siteHostId = hostId ?? (consentGroup.hostId || null)\n /*\n * The feed the act is logged in, decided by the level it was performed\n * at (AGL-2738) and so given the MOUNTED site rather than `siteHostId`:\n * at the org hub a client-direct append to the holder's own site would\n * face a gate the record write never had to pass.\n */\n const logActivity = useCrmActivityLogger(hostId)\n const record = useMemo(\n () => (row ? contactRecordFromDoc(row, consentGroup) : null),\n [row, consentGroup],\n )\n const notFound = Boolean(scope) && status !== 'loading' && !row\n /** How the site the delete detaches from is named — see `handleRemove`. */\n const siteLabel = hostId\n ? 'this site'\n : siteHostId\n ? (mount?.siteName(siteHostId) ?? siteHostId)\n : 'its site'\n\n // The roster, for the owner picker and the owner's name — read because a\n // record page is the one place both are shown.\n const members = useOrgMembers(orgId, { enabled: Boolean(record) })\n /*\n * Where this person's ORDERS are read (AGL-2622): the site's orders list,\n * narrowed to their address, at the address the plugin that keeps orders\n * publishes. The count on the header is the number; the list is the rows.\n * Built from the route params already in the URL, so no document is read\n * to draw a link, and absent off a site — the org-level mount has no\n * orders list to point at — and where no plugin keeps orders.\n */\n const params = useParams<{ orgSlug?: string; host?: string }>()\n const ordersHref =\n params?.orgSlug && params?.host && record?.email\n ? pluginRecordByEmailHref(\n 'order',\n { orgSlug: String(params.orgSlug), host: String(params.host) },\n record.email,\n )\n : null\n /*\n * Where this person's BOOKINGS are read (AGL-2660): the site's Bookings\n * page narrowed to their address, under the same match rule the booking\n * row's own \"View in CRM\" uses. Offered only where the site has a booking\n * door — a site that never enabled Bookings should not carry the row. At\n * the organization level the capturing site's page, addressed through\n * the subdomain the mount knows it by.\n */\n const bookingDoor = useBookingDoor(siteHostId, org)\n const siteSlug = params?.host\n ? String(params.host)\n : siteHostId && mount\n ? mount.siteSubdomain(siteHostId)\n : null\n const bookingsHref =\n bookingDoor.open && params?.orgSlug && siteSlug && record?.email\n ? pluginRecordByEmailHref(\n 'booking',\n { orgSlug: String(params.orgSlug), host: siteSlug },\n record.email,\n )\n : null\n\n /*\n * Where a campaign entry on the timeline links to: the email's own report\n * (AGL-2616), at the address the plugin that keeps messages publishes as\n * `emailMessage` (AGL-3080). Only a campaign THIS site sent can be\n * addressed — a sibling site in the same consent group has pages of its\n * own under a subdomain this page does not know — so the builder answers\n * `null` for those, and where nothing publishes the kind, and the entry\n * draws unlinked.\n */\n const siteRoute = useSiteRouteContext()\n const campaignHref = useCallback(\n (email: Aglyn.ContactCampaignEmail) =>\n siteRoute && email.hostId === hostId\n ? pluginRecordHref('emailMessage', siteRoute, email.campaignId)\n : null,\n [siteRoute, hostId],\n )\n\n const contactRemove = useContactRemove(hostId)\n const handleRemove = useCallback(async () => {\n if (!row || !scope) return\n const confirmed = await confirm({\n title: 'Delete this contact?',\n description:\n `\"${record?.email ?? id}\" is removed from ${siteLabel}'s ` +\n 'Contacts, along with its notes, tags and timeline. Other sites ' +\n 'that captured the same person keep their own records. Their form ' +\n 'submissions, orders, bookings, and membership records are ' +\n 'separate — delete those from their own pages if the request ' +\n 'covers them.',\n confirmationText: 'Delete contact',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n // The route decides against the stored document, keeps any refusal,\n // and restamps what the Contacts list filters by (AGL-3338).\n const removed = await contactRemove.removeOne(id)\n // `siteLabel` and not \"this site\": at the organization level the line\n // lands in a feed that spans every site, so it has to name the one.\n logActivity(`Removed contact from ${siteLabel}`, {\n type: 'contact',\n id,\n name: record?.name || record?.email,\n })\n enqueueSnackbar(\n removed === 'deleted'\n ? 'Contact deleted'\n : `Contact removed from ${siteLabel}`,\n { variant: 'success', persist: false },\n )\n router.push(routes.section('contacts'))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n confirm,\n contactRemove,\n enqueueSnackbar,\n id,\n logActivity,\n record,\n router,\n routes,\n row,\n scope,\n siteLabel,\n ])\n\n const overflowItems: RowActionsMenuItem[] = [\n /*\n * Two records for one person become one (AGL-2625). Opened empty, to be\n * searched, with the OTHER record surviving: a reader on the record they\n * want gone says \"merge this into…\". The dialog lets them switch.\n */\n {\n key: 'merge',\n label: 'Merge into…',\n icon: <MdiIcon path={mdiMerge.path} size={0.8} />,\n disabled: !row || !suiteIncluded,\n disabledReason: suiteIncluded ? 'The contact has not loaded' : crmSuiteLockedReason(),\n onClick: () => merge.open(),\n },\n {\n key: 'remove',\n label: 'Delete contact',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !row,\n disabledReason: 'The contact has not loaded',\n onClick: () => void handleRemove(),\n },\n ]\n // The privacy erasure (AGL-2623): its item joins the overflow, its banner\n // sits under the header while a request waits, its dialog renders once.\n const erase = useErasePersonAction({\n // Filed as the site the page acts as — at the organization level the\n // person's primary holder (AGL-2630).\n hostId: siteHostId,\n orgId,\n subject: record ? { kind: 'contact', id, email: record.email } : null,\n requestedAtMs: Aglyn.readErasureRequestedAtMs(row),\n })\n\n if (notFound) {\n return (\n <CrmRecordHeader\n kind=\"Contact\"\n title={undefined}\n help={contactDocsHelp}\n backHref={routes.section('contacts')}\n backLabel=\"Back to contacts\"\n >\n {/*\n * Not \"no data\". A contact that cannot be read is a different\n * situation from one with an empty profile — it may have been\n * removed from this site, or be held only by a site this reader\n * cannot see.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This contact could not be loaded. It may have been removed from this site.'}\n </Typography>\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity, a deal — files\n // under the site that captured this person unless the reader says\n // otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={siteHostId}>\n <Stack spacing={3}>\n <CrmRecordHeader\n kind=\"Contact\"\n title={record ? record.name || record.email : undefined}\n // The identity, and the other addresses a merge folded into it.\n subtitle={\n record\n ? [record.email, ...record.alternateEmails.map((email) => `also ${email}`)].join(' · ')\n : undefined\n }\n help={contactDocsHelp}\n backHref={routes.section('contacts')}\n backLabel=\"Back to contacts\"\n actions={\n record ? (\n <>\n <AddToListButton hostId={hostId} org={org} contactId={id} email={record.email} />\n {/* Dial the number, and log the call it was (AGL-2661). */}\n <CrmCallButton\n hostId={siteHostId}\n org={org}\n link={{ contactId: id }}\n // The main number, or the mobile when that is all there is (AGL-3515).\n phone={record.phone || record.mobilePhone}\n doNotCall={record.doNotCall}\n suiteLocked={!suiteIncluded}\n />\n <CrmSendEmailButton\n hostId={siteHostId}\n org={org}\n contactId={id}\n email={record.email}\n name={record.name}\n emailState={record.emailState}\n suiteLocked={!suiteIncluded}\n />\n </>\n ) : null\n }\n menuItems={[...overflowItems, ...erase.menuItems]}\n // The booking door (AGL-2660): this site's services, or at the\n // organization level the capturing site's.\n booking={record ? { hostId: siteHostId, org, kind: 'contact', recordId: id } : undefined}\n loading={!record}\n chips={\n record ? (\n <>\n {/* The verdict on the address (AGL-3245), first: a bounce or a\n do-not-contact mark is what a person deciding to write must see. */}\n <CrmEmailStateChip state={record.emailState} />\n {/* Seen here only through a share (AGL-3336): who shared it. */}\n <CrmShareChipView chip={crmShareChipFor(row, viewingGroup?.hostIds ?? [])} org={org} />\n {/* The mail gateway in front of the address (AGL-3328). */}\n <CrmEmailGatewayChip\n hostId={siteHostId}\n email={record.email}\n emailState={record.emailState}\n enabled={suiteIncluded}\n />\n <CrmAccountLockChip\n hostId={siteHostId}\n email={record.email}\n enabled={suiteIncluded}\n />\n <CrmRecordChip\n label=\"Stage\"\n value={\n record.lifecycleStage\n ? CONTACT_LIFECYCLE_STAGE_LABELS[record.lifecycleStage]\n : undefined\n }\n />\n <CrmRecordChip\n label=\"Owner\"\n value={record.ownerUid ? members.memberName(record.ownerUid) : undefined}\n />\n {/*\n The last time they opened or clicked one of this site's\n campaigns (AGL-2616) — the relationship's pulse, beside the\n owner who keeps it.\n */}\n <CrmRecordChip\n label=\"Last engaged\"\n value={\n record.lastEmailEngagementAtMs ? (\n <Tooltip title={new Date(record.lastEmailEngagementAtMs).toLocaleString()}>\n <span>\n {Aglyn.activityTimeLabel(record.lastEmailEngagementAtMs, Date.now())}\n </span>\n </Tooltip>\n ) : undefined\n }\n />\n <CrmRecordChip\n label=\"Orders\"\n value={\n record.ordersCount > 0 ? (\n ordersHref ? (\n <AppLink href={ordersHref} color=\"inherit\" underline=\"hover\">\n {`${record.ordersCount.toLocaleString()} · $${(record.ltvCents / 100).toFixed(2)} lifetime`}\n </AppLink>\n ) : (\n `${record.ordersCount.toLocaleString()} · $${(record.ltvCents / 100).toFixed(2)} lifetime`\n )\n ) : undefined\n }\n />\n </>\n ) : null\n }\n >\n {erase.banner}\n </CrmRecordHeader>\n {record && row && scope ? (\n <>\n {suiteIncluded ? null : (\n <CrmSuiteNotice>\n {'Working this contact — the owner, stage and company, tasks, ' +\n 'deals, files and custom fields, logging a call or an activity, ' +\n 'one-to-one email and merging duplicates — is part of the CRM.'}\n </CrmSuiteNotice>\n )}\n {/*\n The cross-site fact, at the organization level only (AGL-2630):\n which sites know this person, and their consent for each. Under\n a site the page IS one site's view and the card would say so on\n every record.\n */}\n {/* What an assistant proposes about the person (AGL-2917), where the plan works a contact. */}\n {suiteIncluded ? (\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"contact\"\n recordId={id}\n name={record.name || record.email}\n taskLink={{ contactId: id }}\n />\n ) : null}\n {mount ? <ContactKnownByCard row={row} contactId={id} org={org} /> : null}\n {/* Where the person is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"contacts\"\n id={id}\n record={row}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={viewingGroup?.hostIds}\n />\n <ContactPropertiesCard\n hostId={hostId}\n org={org}\n record={record}\n orgId={orgId}\n consentGroup={consentGroup}\n seed={{ status, fromCache }}\n members={members}\n suiteLocked={!suiteIncluded}\n />\n <ContactAssociationsCard\n hostId={siteHostId}\n record={record}\n row={row}\n consentGroup={consentGroup}\n seed={{ status, fromCache }}\n basePath={basePath}\n bookingsHref={bookingsHref}\n />\n <ContactTimelineCard\n hostId={hostId}\n org={org}\n contactId={id}\n contact={row}\n campaignHref={campaignHref}\n suiteLocked={!suiteIncluded}\n />\n {/*\n On a plan without the CRM, the cards that only work a record —\n deals, tasks, files — are named in the notice above rather than\n drawn as upsells in a row.\n */}\n {suiteIncluded ? (\n <>\n <ContactDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n contactId={id}\n contactName={record.name || record.email}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} contactId={id} />\n {/*\n The files are the VIEWING holder's, like everything else on this\n page: the write is a facet path, so a contract one brand filed\n never appears on a sibling brand's copy of the person.\n */}\n <RecordFilesCard\n scope={scope}\n collection=\"contacts\"\n recordId={id}\n hostId={hostId}\n facetGroupId={consentGroup.groupId}\n mediaIds={record.mediaIds}\n topic=\"contactRecord\"\n />\n </>\n ) : null}\n <ContactDuplicatesCard\n current={{ id, doc: row }}\n scope={scope}\n consentGroup={consentGroup}\n visibleTo={visibleTo}\n basePath={basePath}\n onMerge={(candidate) => merge.open({ other: candidate, keep: 'current' })}\n suiteLocked={!suiteIncluded}\n />\n <ContactMergeDialog\n {...merge.state}\n onClose={merge.close}\n hostId={siteHostId}\n current={{ id, doc: row }}\n scope={scope}\n consentGroup={consentGroup}\n visibleTo={visibleTo}\n memberName={members.memberName}\n // The page's own record was folded away: its address now opens\n // the survivor, which is where the reader is taken.\n onMerged={(survivorId) => {\n if (survivorId !== id) router.replace(routes.contact(survivorId))\n }}\n />\n </>\n ) : null}\n {erase.dialog}\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nContactDetailPage.displayName = 'ContactDetailPage'\n\nexport default ContactDetailPage\n"],"names":["Aglyn","CONTACT_LIFECYCLE_STAGE_LABELS","pluginDocsHelp","pluginRecordByEmailHref","pluginRecordHref","mdiDeleteOutline","mdiMerge","AppLink","MdiIcon","useConfirmationContext","useSnackbar","useFirestore","useFirestoreDoc","Stack","Tooltip","Typography","doc","useParams","useRouter","useCallback","useMemo","useContactRemove","useCrmActivityLogger","CrmCreateSiteDefault","useCrmOrgMount","crmShareChipFor","useCrmScope","contactPrimaryGroup","contactRecordFromDoc","crmRoutes","ContactAssociationsCard","ContactDuplicatesCard","ContactKnownByCard","ContactMergeDialog","useContactMergeDialog","ContactPropertiesCard","ContactTimelineCard","AddToListButton","useBookingDoor","CrmSendEmailButton","ContactDealsCard","CrmCallButton","CrmEmailStateChip","CrmAccountLockChip","CrmEmailGatewayChip","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","CrmSuiteNotice","crmSuiteIncluded","crmSuiteLockedReason","useErasePersonAction","RecordFilesCard","CrmShareChipView","RecordSharingCard","RecordTasksCard","useSiteRouteContext","useOrgMembers","contactDocsHelp","anchor","excerpt","ContactDetailPage","props","hostId","org","id","basePath","routes","router","firestore","enqueueSnackbar","confirm","scope","orgId","consentGroup","viewingGroup","visibleTo","mount","merge","suiteIncluded","data","row","status","fromCache","idField","siteHostId","logActivity","record","notFound","Boolean","siteLabel","siteName","members","enabled","params","ordersHref","orgSlug","host","email","String","bookingDoor","siteSlug","siteSubdomain","bookingsHref","open","siteRoute","campaignHref","campaignId","contactRemove","handleRemove","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","removed","removeOne","type","name","variant","persist","push","section","error","console","allowDuplicate","overflowItems","key","label","icon","path","size","disabled","disabledReason","onClick","destructive","erase","subject","kind","requestedAtMs","readErasureRequestedAtMs","undefined","help","backHref","backLabel","spacing","subtitle","alternateEmails","map","join","actions","contactId","link","phone","mobilePhone","doNotCall","suiteLocked","emailState","menuItems","booking","recordId","loading","chips","state","chip","hostIds","value","lifecycleStage","ownerUid","memberName","lastEmailEngagementAtMs","Date","toLocaleString","span","activityTimeLabel","now","ordersCount","href","underline","ltvCents","toFixed","banner","taskLink","object","viewingHostIds","seed","contact","contactName","collection","facetGroupId","groupId","mediaIds","topic","current","onMerge","candidate","other","keep","onClose","close","onMerged","survivorId","replace","dialog","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-detail-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CONTACT_LIFECYCLE_STAGE_LABELS, pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n pluginRecordByEmailHref,\n pluginRecordHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { mdiDeleteOutline, mdiMerge } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Tooltip, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useCallback, useMemo } from 'react'\nimport { useContactRemove } from '../hooks/use-contact-remove'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { CrmCreateSiteDefault, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { contactPrimaryGroup, contactRecordFromDoc } from '../model/contact-record'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport ContactAssociationsCard from './contact-associations-card'\nimport ContactDuplicatesCard from './contact-duplicates-card'\nimport ContactKnownByCard from './contact-known-by-card'\nimport ContactMergeDialog, { useContactMergeDialog } from './contact-merge-dialog'\nimport ContactPropertiesCard from './contact-properties-card'\nimport ContactTimelineCard from './contact-timeline-card'\nimport { AddToListButton } from './add-to-list-button'\nimport { useBookingDoor } from './crm-booking-zone'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport { ContactDealsCard } from './contact-deals-card'\nimport { CrmCallButton } from './crm-call-actions'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { CrmAccountLockChip, CrmEmailGatewayChip } from './crm-email-check'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { CrmSuiteNotice, crmSuiteIncluded, crmSuiteLockedReason } from './crm-suite-lock'\nimport { useErasePersonAction } from './erase-person-action'\nimport RecordFilesCard from './record-files-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport { useSiteRouteContext } from './use-site-route-context'\nimport { useOrgMembers } from './use-org-members'\n\nconst contactDocsHelp = pluginDocsHelp('contactRecord', { anchor: '#the-record-page' })\n\n/**\n * `/crm/contacts/{contactId}` — one person (AGL-2596).\n *\n * A ROUTE rather than the v1 drawer, because a person is the thing every\n * other CRM record points at: a deal, a task and an activity all name a\n * contact, and a link that opens a drawer on top of a list is not a link\n * anybody can paste.\n *\n * ## What this page reads\n *\n * ONE document, by id, through the scoped rules — a single-document get is\n * judged on that document's own `visibleTo`, so no `array-contains-any`\n * filter is needed here the way it is on the list. The row is then flattened\n * through the viewing group's facet ONCE (`contactRecordFromDoc`) and the\n * cards below read the flat record: none of them reaches into\n * `facets.{groupId}` for itself, which is what keeps a card from ever\n * showing another holder's notes.\n *\n * Beyond the document: the team roster (one request, for the owner picker)\n * and the site's campaigns (one listen, for the filing picker) — both paid\n * here because opening a record IS the ask for them, and neither is paid by\n * the list.\n *\n * ## The cards\n *\n * Each in its own file, so a card can grow without this page growing:\n * properties (the editable profile, with the org's custom fields under it),\n * the relationship (sources, attribution, consent, campaign filing) and the\n * timeline. Other CRM sections add a card here by inserting one import and\n * one line.\n *\n * ## Delete is a detach\n *\n * The row is shared by every site that has captured this person, so the\n * action in the overflow menu drops THIS group's facet, consent, attribution\n * and scope tokens, and deletes the document only when nobody else is left\n * holding it. `crm/contact-remove` decides which against the stored\n * document and keeps any marketing refusal the person gave (AGL-3338); the\n * rules refuse a client the write. Not the erasure path: a privacy erasure\n * removes the person regardless of who else holds them.\n */\nexport function ContactDetailPage(props: CrmDetailPageProps) {\n const { hostId, org, id, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n // The org root, the viewing group and what this viewer may list — the\n // merge search's and the duplicates query's one clause — from the one\n // scope hook (AGL-2614); at the organization level the group is `null`\n // and resolved per person below, and the listeners carry no clause\n // (AGL-2630).\n const {\n scope,\n orgId,\n consentGroup: viewingGroup,\n visibleTo,\n } = useCrmScope({ hostId, org })\n const mount = useCrmOrgMount()\n const merge = useContactMergeDialog()\n /*\n * THE CRM (AGL-2788). This page is the CRM's, and the shell mounts no CRM\n * page for a plan without it (AGL-2851). What locks below — owner, stage\n * and company; tasks, deals, files and custom fields; a logged call or\n * activity; one-to-one email; a merge — draws only for an org whose plan\n * lacks the CRM. The shell mounted this page once the org had settled, so\n * this is the plan, not a paint of the org still loading.\n */\n const suiteIncluded = crmSuiteIncluded(org)\n\n const {\n data: row,\n status,\n fromCache,\n } = useFirestoreDoc<Record<string, any>>(\n () => (scope ? doc(firestore, scope[0], scope[1], 'contacts', id) : null),\n [firestore, scope, id],\n { idField: '$id' },\n )\n /*\n * The controller this page is showing. Under a site: the sites declared\n * to be one sender, or this site alone — resolved from the org document\n * the shell already passed, so it costs no read. At the ORGANIZATION\n * level: the person's own PRIMARY holder, the first capturing site's\n * group, so an org-wide member reads the profile that site keeps and an\n * edit goes back to the same facet; the \"Known by\" card says which other\n * sites hold one.\n */\n const consentGroup = useMemo(\n () => viewingGroup ?? contactPrimaryGroup(row, org as Record<string, unknown>),\n [viewingGroup, row, org],\n )\n /**\n * The SITE the page acts as, for what needs one — the lead lookup, the\n * campaign filing, the site's activity feed: the mounted site, or the\n * primary holder's, or nothing for a row no site has captured.\n */\n const siteHostId = hostId ?? (consentGroup.hostId || null)\n /*\n * The feed the act is logged in, decided by the level it was performed\n * at (AGL-2738) and so given the MOUNTED site rather than `siteHostId`:\n * at the org hub a client-direct append to the holder's own site would\n * face a gate the record write never had to pass.\n */\n const logActivity = useCrmActivityLogger(hostId)\n const record = useMemo(\n () => (row ? contactRecordFromDoc(row, consentGroup) : null),\n [row, consentGroup],\n )\n const notFound = Boolean(scope) && status !== 'loading' && !row\n /** How the site the delete detaches from is named — see `handleRemove`. */\n const siteLabel = hostId\n ? 'this site'\n : siteHostId\n ? (mount?.siteName(siteHostId) ?? siteHostId)\n : 'its site'\n\n // The roster, for the owner picker and the owner's name — read because a\n // record page is the one place both are shown.\n const members = useOrgMembers(orgId, { enabled: Boolean(record) })\n /*\n * Where this person's ORDERS are read (AGL-2622): the site's orders list,\n * narrowed to their address, at the address the plugin that keeps orders\n * publishes. The count on the header is the number; the list is the rows.\n * Built from the route params already in the URL, so no document is read\n * to draw a link, and absent off a site — the org-level mount has no\n * orders list to point at — and where no plugin keeps orders.\n */\n const params = useParams<{ orgSlug?: string; host?: string }>()\n const ordersHref =\n params?.orgSlug && params?.host && record?.email\n ? pluginRecordByEmailHref(\n 'order',\n { orgSlug: String(params.orgSlug), host: String(params.host) },\n record.email,\n )\n : null\n /*\n * Where this person's BOOKINGS are read (AGL-2660): the site's Bookings\n * page narrowed to their address, under the same match rule the booking\n * row's own \"View in CRM\" uses. Offered only where the site has a booking\n * door — a site that never enabled Bookings should not carry the row. At\n * the organization level the capturing site's page, addressed through\n * the subdomain the mount knows it by.\n */\n const bookingDoor = useBookingDoor(siteHostId, org)\n const siteSlug = params?.host\n ? String(params.host)\n : siteHostId && mount\n ? mount.siteSubdomain(siteHostId)\n : null\n const bookingsHref =\n bookingDoor.open && params?.orgSlug && siteSlug && record?.email\n ? pluginRecordByEmailHref(\n 'booking',\n { orgSlug: String(params.orgSlug), host: siteSlug },\n record.email,\n )\n : null\n\n /*\n * Where a campaign entry on the timeline links to: the email's own report\n * (AGL-2616), at the address the plugin that keeps messages publishes as\n * `emailMessage` (AGL-3080). Only a campaign THIS site sent can be\n * addressed — a sibling site in the same consent group has pages of its\n * own under a subdomain this page does not know — so the builder answers\n * `null` for those, and where nothing publishes the kind, and the entry\n * draws unlinked.\n */\n const siteRoute = useSiteRouteContext()\n const campaignHref = useCallback(\n (email: Aglyn.ContactCampaignEmail) =>\n siteRoute && email.hostId === hostId\n ? pluginRecordHref('emailMessage', siteRoute, email.campaignId)\n : null,\n [siteRoute, hostId],\n )\n\n const contactRemove = useContactRemove(hostId)\n const handleRemove = useCallback(async () => {\n if (!row || !scope) return\n const confirmed = await confirm({\n title: 'Delete this contact?',\n description:\n `\"${record?.email ?? id}\" is removed from ${siteLabel}'s ` +\n 'Contacts, along with its notes, tags and timeline. Other sites ' +\n 'that captured the same person keep their own records. Their form ' +\n 'submissions, orders, bookings, and membership records are ' +\n 'separate — delete those from their own pages if the request ' +\n 'covers them.',\n confirmationText: 'Delete contact',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n // The route decides against the stored document, keeps any refusal,\n // and restamps what the Contacts list filters by (AGL-3338).\n const removed = await contactRemove.removeOne(id)\n // `siteLabel` and not \"this site\": at the organization level the line\n // lands in a feed that spans every site, so it has to name the one.\n logActivity(`Removed contact from ${siteLabel}`, {\n type: 'contact',\n id,\n name: record?.name || record?.email,\n })\n enqueueSnackbar(\n removed === 'deleted'\n ? 'Contact deleted'\n : `Contact removed from ${siteLabel}`,\n { variant: 'success', persist: false },\n )\n router.push(routes.section('contacts'))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n confirm,\n contactRemove,\n enqueueSnackbar,\n id,\n logActivity,\n record,\n router,\n routes,\n row,\n scope,\n siteLabel,\n ])\n\n const overflowItems: RowActionsMenuItem[] = [\n /*\n * Two records for one person become one (AGL-2625). Opened empty, to be\n * searched, with the OTHER record surviving: a reader on the record they\n * want gone says \"merge this into…\". The dialog lets them switch.\n */\n {\n key: 'merge',\n label: 'Merge into…',\n icon: <MdiIcon path={mdiMerge.path} size={0.8} />,\n disabled: !row || !suiteIncluded,\n disabledReason: suiteIncluded ? 'The contact has not loaded' : crmSuiteLockedReason(),\n onClick: () => merge.open(),\n },\n {\n key: 'remove',\n label: 'Delete contact',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !row,\n disabledReason: 'The contact has not loaded',\n onClick: () => void handleRemove(),\n },\n ]\n // The privacy erasure (AGL-2623): its item joins the overflow, its banner\n // sits under the header while a request waits, its dialog renders once.\n const erase = useErasePersonAction({\n // Filed as the site the page acts as — at the organization level the\n // person's primary holder (AGL-2630).\n hostId: siteHostId,\n orgId,\n subject: record ? { kind: 'contact', id, email: record.email } : null,\n requestedAtMs: Aglyn.readErasureRequestedAtMs(row),\n })\n\n if (notFound) {\n return (\n <CrmRecordHeader\n kind=\"Contact\"\n title={undefined}\n help={contactDocsHelp}\n backHref={routes.section('contacts')}\n backLabel=\"Back to contacts\"\n >\n {/*\n * Not \"no data\". A contact that cannot be read is a different\n * situation from one with an empty profile — it may have been\n * removed from this site, or be held only by a site this reader\n * cannot see.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This contact could not be loaded. It may have been removed from this site.'}\n </Typography>\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity, a deal — files\n // under the site that captured this person unless the reader says\n // otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={siteHostId}>\n <Stack spacing={3}>\n <CrmRecordHeader\n kind=\"Contact\"\n title={record ? record.name || record.email : undefined}\n // The identity, and the other addresses a merge folded into it.\n subtitle={\n record\n ? [record.email, ...record.alternateEmails.map((email) => `also ${email}`)].join(' · ')\n : undefined\n }\n help={contactDocsHelp}\n backHref={routes.section('contacts')}\n backLabel=\"Back to contacts\"\n actions={\n record ? (\n <>\n <AddToListButton hostId={hostId} org={org} contactId={id} email={record.email} />\n {/* Dial the number, and log the call it was (AGL-2661). */}\n <CrmCallButton\n hostId={siteHostId}\n org={org}\n link={{ contactId: id }}\n // The main number, or the mobile when that is all there is (AGL-3515).\n phone={record.phone || record.mobilePhone}\n doNotCall={record.doNotCall}\n suiteLocked={!suiteIncluded}\n />\n <CrmSendEmailButton\n hostId={siteHostId}\n org={org}\n contactId={id}\n email={record.email}\n name={record.name}\n emailState={record.emailState}\n suiteLocked={!suiteIncluded}\n />\n </>\n ) : null\n }\n menuItems={[...overflowItems, ...erase.menuItems]}\n // The booking door (AGL-2660): this site's services, or at the\n // organization level the capturing site's.\n booking={record ? { hostId: siteHostId, org, kind: 'contact', recordId: id } : undefined}\n loading={!record}\n chips={\n record ? (\n <>\n {/* The verdict on the address (AGL-3245), first: a bounce or a\n do-not-contact mark is what a person deciding to write must see. */}\n <CrmEmailStateChip state={record.emailState} />\n {/* Seen here only through a share (AGL-3336): who shared it. */}\n <CrmShareChipView chip={crmShareChipFor(row, viewingGroup?.hostIds ?? [])} org={org} />\n {/* The mail gateway in front of the address (AGL-3328). */}\n <CrmEmailGatewayChip\n hostId={siteHostId}\n email={record.email}\n emailState={record.emailState}\n enabled={suiteIncluded}\n />\n <CrmAccountLockChip\n hostId={siteHostId}\n email={record.email}\n enabled={suiteIncluded}\n />\n <CrmRecordChip\n label=\"Stage\"\n value={\n record.lifecycleStage\n ? CONTACT_LIFECYCLE_STAGE_LABELS[record.lifecycleStage]\n : undefined\n }\n />\n <CrmRecordChip\n label=\"Owner\"\n value={record.ownerUid ? members.memberName(record.ownerUid) : undefined}\n />\n {/*\n The last time they opened or clicked one of this site's\n campaigns (AGL-2616) — the relationship's pulse, beside the\n owner who keeps it.\n */}\n <CrmRecordChip\n label=\"Last engaged\"\n value={\n record.lastEmailEngagementAtMs ? (\n <Tooltip title={new Date(record.lastEmailEngagementAtMs).toLocaleString()}>\n <span>\n {Aglyn.activityTimeLabel(record.lastEmailEngagementAtMs, Date.now())}\n </span>\n </Tooltip>\n ) : undefined\n }\n />\n <CrmRecordChip\n label=\"Orders\"\n value={\n record.ordersCount > 0 ? (\n ordersHref ? (\n <AppLink href={ordersHref} color=\"inherit\" underline=\"hover\">\n {`${record.ordersCount.toLocaleString()} · $${(record.ltvCents / 100).toFixed(2)} lifetime`}\n </AppLink>\n ) : (\n `${record.ordersCount.toLocaleString()} · $${(record.ltvCents / 100).toFixed(2)} lifetime`\n )\n ) : undefined\n }\n />\n </>\n ) : null\n }\n >\n {erase.banner}\n </CrmRecordHeader>\n {record && row && scope ? (\n <>\n {suiteIncluded ? null : (\n <CrmSuiteNotice>\n {'Working this contact — the owner, stage and company, tasks, ' +\n 'deals, files and custom fields, logging a call or an activity, ' +\n 'one-to-one email and merging duplicates — is part of the CRM.'}\n </CrmSuiteNotice>\n )}\n {/*\n The cross-site fact, at the organization level only (AGL-2630):\n which sites know this person, and their consent for each. Under\n a site the page IS one site's view and the card would say so on\n every record.\n */}\n {/* What an assistant proposes about the person (AGL-2917), where the plan works a contact. */}\n {suiteIncluded ? (\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"contact\"\n recordId={id}\n name={record.name || record.email}\n taskLink={{ contactId: id }}\n />\n ) : null}\n {mount ? <ContactKnownByCard row={row} contactId={id} org={org} /> : null}\n {/* Where the person is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"contacts\"\n id={id}\n record={row}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={viewingGroup?.hostIds}\n />\n <ContactPropertiesCard\n hostId={hostId}\n org={org}\n record={record}\n orgId={orgId}\n consentGroup={consentGroup}\n seed={{ status, fromCache }}\n members={members}\n suiteLocked={!suiteIncluded}\n />\n <ContactAssociationsCard\n hostId={siteHostId}\n record={record}\n row={row}\n consentGroup={consentGroup}\n seed={{ status, fromCache }}\n basePath={basePath}\n bookingsHref={bookingsHref}\n />\n <ContactTimelineCard\n hostId={hostId}\n org={org}\n contactId={id}\n contact={row}\n campaignHref={campaignHref}\n suiteLocked={!suiteIncluded}\n />\n {/*\n On a plan without the CRM, the cards that only work a record —\n deals, tasks, files — are named in the notice above rather than\n drawn as upsells in a row.\n */}\n {suiteIncluded ? (\n <>\n <ContactDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n contactId={id}\n contactName={record.name || record.email}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} contactId={id} />\n {/*\n The files are the VIEWING holder's, like everything else on this\n page: the write is a facet path, so a contract one brand filed\n never appears on a sibling brand's copy of the person.\n */}\n <RecordFilesCard\n scope={scope}\n collection=\"contacts\"\n recordId={id}\n hostId={hostId}\n facetGroupId={consentGroup.groupId}\n mediaIds={record.mediaIds}\n topic=\"contactRecord\"\n />\n </>\n ) : null}\n <ContactDuplicatesCard\n current={{ id, doc: row }}\n scope={scope}\n consentGroup={consentGroup}\n visibleTo={visibleTo}\n basePath={basePath}\n onMerge={(candidate) => merge.open({ other: candidate, keep: 'current' })}\n suiteLocked={!suiteIncluded}\n />\n <ContactMergeDialog\n {...merge.state}\n onClose={merge.close}\n hostId={siteHostId}\n current={{ id, doc: row }}\n scope={scope}\n consentGroup={consentGroup}\n visibleTo={visibleTo}\n memberName={members.memberName}\n // The page's own record was folded away: its address now opens\n // the survivor, which is where the reader is taken.\n onMerged={(survivorId) => {\n if (survivorId !== id) router.replace(routes.contact(survivorId))\n }}\n />\n </>\n ) : null}\n {erase.dialog}\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nContactDetailPage.displayName = 'ContactDetailPage'\n\nexport default ContactDetailPage\n"],"names":["Aglyn","CONTACT_LIFECYCLE_STAGE_LABELS","pluginDocsHelp","pluginRecordByEmailHref","pluginRecordHref","mdiDeleteOutline","mdiMerge","AppLink","MdiIcon","useConfirmationContext","useSnackbar","useFirestore","useFirestoreDoc","Stack","Tooltip","Typography","doc","useParams","useRouter","useCallback","useMemo","useContactRemove","useCrmActivityLogger","CrmCreateSiteDefault","useCrmOrgMount","crmShareChipFor","useCrmScope","contactPrimaryGroup","contactRecordFromDoc","crmRoutes","ContactAssociationsCard","ContactDuplicatesCard","ContactKnownByCard","ContactMergeDialog","useContactMergeDialog","ContactPropertiesCard","ContactTimelineCard","AddToListButton","useBookingDoor","CrmSendEmailButton","ContactDealsCard","CrmCallButton","CrmEmailStateChip","CrmAccountLockChip","CrmEmailGatewayChip","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","CrmSuiteNotice","crmSuiteIncluded","crmSuiteLockedReason","useErasePersonAction","RecordFilesCard","CrmShareChipView","RecordSharingCard","RecordTasksCard","useSiteRouteContext","useOrgMembers","contactDocsHelp","anchor","ContactDetailPage","props","hostId","org","id","basePath","routes","router","firestore","enqueueSnackbar","confirm","scope","orgId","consentGroup","viewingGroup","visibleTo","mount","merge","suiteIncluded","data","row","status","fromCache","idField","siteHostId","logActivity","record","notFound","Boolean","siteLabel","siteName","members","enabled","params","ordersHref","orgSlug","host","email","String","bookingDoor","siteSlug","siteSubdomain","bookingsHref","open","siteRoute","campaignHref","campaignId","contactRemove","handleRemove","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","removed","removeOne","type","name","variant","persist","push","section","error","console","allowDuplicate","overflowItems","key","label","icon","path","size","disabled","disabledReason","onClick","destructive","erase","subject","kind","requestedAtMs","readErasureRequestedAtMs","undefined","help","backHref","backLabel","spacing","subtitle","alternateEmails","map","join","actions","contactId","link","phone","mobilePhone","doNotCall","suiteLocked","emailState","menuItems","booking","recordId","loading","chips","state","chip","hostIds","value","lifecycleStage","ownerUid","memberName","lastEmailEngagementAtMs","Date","toLocaleString","span","activityTimeLabel","now","ordersCount","href","underline","ltvCents","toFixed","banner","taskLink","object","viewingHostIds","seed","contact","contactName","collection","facetGroupId","groupId","mediaIds","topic","current","onMerge","candidate","other","keep","onClose","close","onMerged","survivorId","replace","dialog","displayName"],"mappings":"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"}
|
|
@@ -75,7 +75,7 @@ ConsentChip.displayName = 'ConsentChip';
|
|
|
75
75
|
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
76
76
|
header: 'Known by',
|
|
77
77
|
help: pluginDocsHelp('contacts', {
|
|
78
|
-
anchor: '#
|
|
78
|
+
anchor: '#known-by'
|
|
79
79
|
}),
|
|
80
80
|
contentGutterX: true,
|
|
81
81
|
contentGutterY: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-known-by-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n orgContactConsentLabel,\n orgContactRow,\n pluginDocsHelp,\n type OrgContactHostConsent,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Chip, Stack, Typography } from '@mui/material'\nimport { useMemo } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { CrmOrgDoc } from '../hooks/use-crm-scope'\n\nexport interface ContactKnownByCardProps {\n /** The document as read — the projection below is an allow-list over it. */\n row: Record<string, unknown>\n contactId: string\n /** The org document, the only input that can resolve a declared consent group. */\n org: CrmOrgDoc\n}\n\n/**\n * One consent verdict, rendered.\n *\n * The label always names the controller — `orgContactConsentLabel` is what\n * guarantees that — and the color follows the basis rather than replacing\n * it: a green chip with no name on it would be a bare \"consented\", which is\n * the claim the per-brand model exists to stop anybody making.\n */\nfunction ConsentChip(props: { entry: OrgContactHostConsent; siteName: string }) {\n const { entry, siteName } = props\n return (\n <Chip\n size=\"small\"\n color={\n entry.basis === 'granted'\n ? 'success'\n : entry.basis === 'declined'\n ? 'error'\n : 'default'\n }\n variant={entry.basis === 'unrecorded' ? 'outlined' : 'filled'}\n label={orgContactConsentLabel(entry, siteName)}\n />\n )\n}\nConsentChip.displayName = 'ConsentChip'\n\n/**\n * KNOWN BY — the cross-site fact, on the organization-level record\n * (AGL-2630).\n *\n * A contact document is shared by every site that has captured the person:\n * one human who touched two of an org's sites is ONE row, which is the\n * dedupe the shared address book exists for and what makes the billing unit\n * — unique people per org — mean anything. Under a site that fact is\n * invisible on purpose: a site's hub is one holder's view. This card is the\n * one place it is shown, and it shows exactly what the old org-level\n * address book showed and nothing more: which sites know the person, and\n * the person's marketing consent FOR EACH of those sites — one verdict per\n * capturing site, each naming the controller it was read for, because a\n * basis is per (person, controller) and \"consented\" with nobody attached\n * would be a different and false claim.\n *\n * Notes, tags, activity and commercial figures are each holder's own\n * records and are not projected here: `orgContactRow` is an allow-list for\n * that reason, and the card renders nothing it did not return. The rest of\n * the record page reads through the person's PRIMARY holder; this card is\n * how a reader learns which other sites hold a profile, and the link on\n * each site is how they open it there.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the card.\n */\nexport function ContactKnownByCard(props: ContactKnownByCardProps) {\n const { row, contactId, org } = props\n const mount = useCrmOrgMount()\n const projected = useMemo(\n () => orgContactRow(row, contactId, (org ?? null) as Record<string, unknown> | null),\n [row, contactId, org],\n )\n if (!mount) return null\n const { capturedByHostIds, consent } = projected\n return (\n <CardDisplay\n header={'Known by'}\n help={pluginDocsHelp('contacts', { anchor: '#
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-known-by-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n orgContactConsentLabel,\n orgContactRow,\n pluginDocsHelp,\n type OrgContactHostConsent,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Chip, Stack, Typography } from '@mui/material'\nimport { useMemo } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { CrmOrgDoc } from '../hooks/use-crm-scope'\n\nexport interface ContactKnownByCardProps {\n /** The document as read — the projection below is an allow-list over it. */\n row: Record<string, unknown>\n contactId: string\n /** The org document, the only input that can resolve a declared consent group. */\n org: CrmOrgDoc\n}\n\n/**\n * One consent verdict, rendered.\n *\n * The label always names the controller — `orgContactConsentLabel` is what\n * guarantees that — and the color follows the basis rather than replacing\n * it: a green chip with no name on it would be a bare \"consented\", which is\n * the claim the per-brand model exists to stop anybody making.\n */\nfunction ConsentChip(props: { entry: OrgContactHostConsent; siteName: string }) {\n const { entry, siteName } = props\n return (\n <Chip\n size=\"small\"\n color={\n entry.basis === 'granted'\n ? 'success'\n : entry.basis === 'declined'\n ? 'error'\n : 'default'\n }\n variant={entry.basis === 'unrecorded' ? 'outlined' : 'filled'}\n label={orgContactConsentLabel(entry, siteName)}\n />\n )\n}\nConsentChip.displayName = 'ConsentChip'\n\n/**\n * KNOWN BY — the cross-site fact, on the organization-level record\n * (AGL-2630).\n *\n * A contact document is shared by every site that has captured the person:\n * one human who touched two of an org's sites is ONE row, which is the\n * dedupe the shared address book exists for and what makes the billing unit\n * — unique people per org — mean anything. Under a site that fact is\n * invisible on purpose: a site's hub is one holder's view. This card is the\n * one place it is shown, and it shows exactly what the old org-level\n * address book showed and nothing more: which sites know the person, and\n * the person's marketing consent FOR EACH of those sites — one verdict per\n * capturing site, each naming the controller it was read for, because a\n * basis is per (person, controller) and \"consented\" with nobody attached\n * would be a different and false claim.\n *\n * Notes, tags, activity and commercial figures are each holder's own\n * records and are not projected here: `orgContactRow` is an allow-list for\n * that reason, and the card renders nothing it did not return. The rest of\n * the record page reads through the person's PRIMARY holder; this card is\n * how a reader learns which other sites hold a profile, and the link on\n * each site is how they open it there.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the card.\n */\nexport function ContactKnownByCard(props: ContactKnownByCardProps) {\n const { row, contactId, org } = props\n const mount = useCrmOrgMount()\n const projected = useMemo(\n () => orgContactRow(row, contactId, (org ?? null) as Record<string, unknown> | null),\n [row, contactId, org],\n )\n if (!mount) return null\n const { capturedByHostIds, consent } = projected\n return (\n <CardDisplay\n header={'Known by'}\n help={pluginDocsHelp('contacts', { anchor: '#known-by' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n {capturedByHostIds.length ? (\n <Stack spacing={1.5}>\n {capturedByHostIds.map((hostId) => {\n const entry = consent.find((verdict) => verdict.hostId === hostId)\n const href = mount.siteHubHref(hostId)\n const name = mount.siteName(hostId)\n return (\n <Stack\n useFlexGap\n key={hostId}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n {/*\n A site whose subdomain has not resolved must still be\n NAMED — the relationship is real, and hiding it would\n understate who knows this person — but not linked,\n because the route would not exist; a plain span, not an\n anchor to nowhere. Two elements are safe here: the card\n mounts only once the record's listener has answered, so\n there is no server render for a client render to disagree\n with.\n */}\n {href ? (\n <AppLink\n href={`${href}/contacts/${encodeURIComponent(contactId)}`}\n underline=\"hover\"\n >\n {name}\n </AppLink>\n ) : (\n <Typography component=\"span\" variant=\"body2\">\n {name}\n </Typography>\n )}\n {entry ? <ConsentChip entry={entry} siteName={name} /> : null}\n {entry?.declared ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Consent given to ${entry.groupName ?? entry.groupId}, which ${name} is part of.`}\n </Typography>\n ) : null}\n </Stack>\n )\n })}\n </Stack>\n ) : (\n /*\n * Unattributed, not unknown. A row written before the attribution\n * existed names no site, and reading that as \"every site\" would\n * put a person in front of businesses that never met them.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No site recorded'}\n </Typography>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The profile, notes, tags and timeline on this page are the first ' +\n \"capturing site's. Open the person in another site to see what \" +\n 'it holds.'}\n </Typography>\n </Stack>\n </CardDisplay>\n )\n}\nContactKnownByCard.displayName = 'ContactKnownByCard'\n\nexport default ContactKnownByCard\n"],"names":["orgContactConsentLabel","orgContactRow","pluginDocsHelp","AppLink","CardDisplay","Chip","Stack","Typography","useMemo","useCrmOrgMount","ConsentChip","props","entry","siteName","size","color","basis","variant","label","displayName","ContactKnownByCard","row","contactId","org","mount","projected","capturedByHostIds","consent","header","help","anchor","contentGutterX","contentGutterY","spacing","length","map","hostId","find","verdict","href","siteHubHref","name","useFlexGap","direction","sx","alignItems","flexWrap","encodeURIComponent","underline","component","declared","groupName","groupId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SACEA,sBAAsB,EACtBC,aAAa,EACbC,cAAc,QAET,eAAc;AACrB,SAASC,OAAO,EAAEC,WAAW,QAAQ,uBAAsB;AAC3D,SAASC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACvD,SAASC,OAAO,QAAQ,QAAO;AAC/B,SAASC,cAAc,QAAQ,gCAA4B;AAW3D;;;;;;;CAOC,GACD,SAASC,YAAYC,KAAyD;IAC5E,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGF;IAC5B,qBACE,KAACN;QACCS,MAAK;QACLC,OACEH,MAAMI,KAAK,KAAK,YACZ,YACAJ,MAAMI,KAAK,KAAK,aACd,UACA;QAERC,SAASL,MAAMI,KAAK,KAAK,eAAe,aAAa;QACrDE,OAAOlB,uBAAuBY,OAAOC;;AAG3C;AACAH,YAAYS,WAAW,GAAG;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,mBAAmBT,KAA8B;IAC/D,MAAM,EAAEU,GAAG,EAAEC,SAAS,EAAEC,GAAG,EAAE,GAAGZ;IAChC,MAAMa,QAAQf;IACd,MAAMgB,YAAYjB,QAChB,IAAMP,cAAcoB,KAAKC,WAAYC,cAAAA,MAAO,OAC5C;QAACF;QAAKC;QAAWC;KAAI;IAEvB,IAAI,CAACC,OAAO,OAAO;IACnB,MAAM,EAAEE,iBAAiB,EAAEC,OAAO,EAAE,GAAGF;IACvC,qBACE,KAACrB;QACCwB,QAAQ;QACRC,MAAM3B,eAAe,YAAY;YAAE4B,QAAQ;QAAY;QACvDC,cAAc;QACdC,cAAc;kBAEd,cAAA,MAAC1B;YAAM2B,SAAS;;gBACbP,kBAAkBQ,MAAM,iBACvB,KAAC5B;oBAAM2B,SAAS;8BACbP,kBAAkBS,GAAG,CAAC,CAACC;4BAqCOxB;wBApC7B,MAAMA,QAAQe,QAAQU,IAAI,CAAC,CAACC,UAAYA,QAAQF,MAAM,KAAKA;wBAC3D,MAAMG,OAAOf,MAAMgB,WAAW,CAACJ;wBAC/B,MAAMK,OAAOjB,MAAMX,QAAQ,CAACuB;wBAC5B,qBACE,MAAC9B;4BACCoC,UAAU;4BAEVC,WAAU;4BACVV,SAAS;4BACTW,IAAI;gCAAEC,YAAY;gCAAUC,UAAU;4BAAO;;gCAY5CP,qBACC,KAACpC;oCACCoC,MAAM,GAAGA,KAAK,UAAU,EAAEQ,mBAAmBzB,YAAY;oCACzD0B,WAAU;8CAETP;mDAGH,KAAClC;oCAAW0C,WAAU;oCAAOhC,SAAQ;8CAClCwB;;gCAGJ7B,sBAAQ,KAACF;oCAAYE,OAAOA;oCAAOC,UAAU4B;qCAAW;gCACxD7B,CAAAA,yBAAAA,MAAOsC,QAAQ,kBACd,KAAC3C;oCAAWU,SAAQ;oCAAUF,OAAM;8CACjC,CAAC,iBAAiB,GAAEH,mBAAAA,MAAMuC,SAAS,YAAfvC,mBAAmBA,MAAMwC,OAAO,CAAC,QAAQ,EAAEX,KAAK,YAAY,CAAC;qCAElF;;2BAhCCL;oBAmCX;qBAGF;;;;WAIC,iBACD,KAAC7B;oBAAWU,SAAQ;oBAAQF,OAAM;8BAC/B;;8BAGL,KAACR;oBAAWU,SAAQ;oBAAUF,OAAM;8BACjC,sEACC,mEACA;;;;;AAKZ;AACAK,mBAAmBD,WAAW,GAAG;AAEjC,eAAeC,mBAAkB"}
|
|
@@ -459,7 +459,7 @@ export function ContactPropertiesCard(props) {
|
|
|
459
459
|
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
460
460
|
header: 'Properties',
|
|
461
461
|
help: Aglyn.pluginDocsHelp('contactRecord', {
|
|
462
|
-
anchor: '#
|
|
462
|
+
anchor: '#contact-properties'
|
|
463
463
|
}),
|
|
464
464
|
contentGutterX: true,
|
|
465
465
|
contentGutterY: true,
|