@aglyn/plugins-crm 1.0.0-beta.232 → 1.0.0-beta.233

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/companies-section.js +43 -8
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/company-edit-drawer.js +3 -1
  5. package/src/lib/components/company-edit-drawer.js.map +1 -1
  6. package/src/lib/components/contact-detail-page.js +6 -1
  7. package/src/lib/components/contact-detail-page.js.map +1 -1
  8. package/src/lib/components/contacts-section.js +37 -8
  9. package/src/lib/components/contacts-section.js.map +1 -1
  10. package/src/lib/components/crm-email-check.d.ts +14 -0
  11. package/src/lib/components/crm-email-check.js +38 -1
  12. package/src/lib/components/crm-email-check.js.map +1 -1
  13. package/src/lib/components/crm-next-activity-column.d.ts +5 -4
  14. package/src/lib/components/crm-next-activity-column.js +5 -4
  15. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  16. package/src/lib/components/deal-edit-drawer.js +3 -1
  17. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  18. package/src/lib/components/deals-section.js +50 -6
  19. package/src/lib/components/deals-section.js.map +1 -1
  20. package/src/lib/components/lead-properties-card.js +9 -3
  21. package/src/lib/components/lead-properties-card.js.map +1 -1
  22. package/src/lib/components/leads-section.js +61 -11
  23. package/src/lib/components/leads-section.js.map +1 -1
  24. package/src/lib/components/tasks-section.js +61 -9
  25. package/src/lib/components/tasks-section.js.map +1 -1
  26. package/src/lib/constants/company-filters.js +40 -2
  27. package/src/lib/constants/company-filters.js.map +1 -1
  28. package/src/lib/constants/contact-filters.d.ts +10 -1
  29. package/src/lib/constants/contact-filters.js +62 -2
  30. package/src/lib/constants/contact-filters.js.map +1 -1
  31. package/src/lib/constants/deal-filters.js +78 -0
  32. package/src/lib/constants/deal-filters.js.map +1 -1
  33. package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
  34. package/src/lib/hooks/use-crm-column-sort.js +156 -0
  35. package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
  36. package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
  37. package/src/lib/hooks/use-crm-tasks.js +10 -8
  38. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  39. package/src/lib/model/crm-list-query.d.ts +6 -0
  40. package/src/lib/model/crm-list-query.js +13 -0
  41. package/src/lib/model/crm-list-query.js.map +1 -1
  42. package/src/lib/model/lead-filters.d.ts +19 -2
  43. package/src/lib/model/lead-filters.js +112 -2
  44. package/src/lib/model/lead-filters.js.map +1 -1
  45. package/src/lib/model/native-vocabulary.d.ts +41 -0
  46. package/src/lib/model/native-vocabulary.js +59 -0
  47. package/src/lib/model/native-vocabulary.js.map +1 -0
  48. package/src/lib/model/task-views.d.ts +15 -2
  49. package/src/lib/model/task-views.js +50 -4
  50. package/src/lib/model/task-views.js.map +1 -1
  51. package/src/lib/server/email-check.d.ts +4 -1
  52. package/src/lib/server/email-check.js +18 -6
  53. package/src/lib/server/email-check.js.map +1 -1
  54. package/src/lib/transfer/tasks.js +2 -1
  55. package/src/lib/transfer/tasks.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.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 createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n CRM_COMPANY_PICKLIST_FIELDS,\n CRM_COMPANY_TEXT_MAX,\n type CrmCompany,\n type CrmCompanyPicklistField,\n crmCompanyParentId,\n crmCompanyParentRefusal,\n crmNewRecordListFields,\n crmPicklistDefaultLabel,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n getDoc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useCompanyPicklists } from '../hooks/use-company-picklists'\nimport { DEAL_CURRENCIES } from '../model/deal-board-model'\nimport { CompanyPicker, useCompanyOptions } from './company-picker'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from '../model/crm-list-query'\n\n/**\n * The list fields a client edit of a company rewrites: the search tokens,\n * and the key beside each picklist field the Companies list filters by\n * (AGL-3514).\n */\nconst COMPANY_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n ...CRM_COMPANY_PICKLIST_FIELDS.flatMap((entry) => ('keyField' in entry ? [entry.keyField] : [])),\n]\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\ntype AddressDraftField = 'address' | 'shippingAddress'\n\n/** How each picklist field is labeled on a company. */\nconst COMPANY_PICKLIST_LABELS: Record<CrmCompanyPicklistField, string> = {\n type: 'Type',\n industry: 'Industry',\n rating: 'Rating',\n ownership: 'Ownership',\n accountSource: 'Account source',\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, scope?.[1] ?? null)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n /*\n * SALESFORCE'S ACCOUNT FIELDS (AGL-3514): the org's lists behind Type,\n * Industry, Rating, Ownership and Account Source, and the companies a\n * parent may be chosen from — every one the reader can see but this one.\n */\n const picklists = useCompanyPicklists(scope?.[1] ?? null)\n const seededDefaults = useRef(false)\n const companyOptions = useCompanyOptions({ hostId, org, enabled: open })\n const parentOptions = useMemo(\n () => companyOptions.options.filter((option) => option.id !== company?.$id),\n [companyOptions.options, company?.$id],\n )\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n seededDefaults.current = false\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n /*\n * A NEW company starts each picklist on the org's default, once the\n * lists have answered — Salesforce's form opens on it too — and only a\n * field nobody has picked a value for yet.\n */\n useEffect(() => {\n if (!open || company || !picklists.ready || seededDefaults.current) return\n seededDefaults.current = true\n setDraft((current) => {\n const next = { ...current }\n for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS) {\n const list = picklists.lists[picklistId]\n const fallback = list ? crmPicklistDefaultLabel(list) : null\n if (!next[field] && fallback) next[field] = fallback\n }\n return next\n })\n }, [open, company, picklists])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, AddressDraftField>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (which: AddressDraftField, field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n [which]: { ...current[which], [field]: value },\n })),\n [],\n )\n /** The parent's own parent, as the cycle guard walks it; `null` for one this reader cannot see. */\n const readParent = useCallback(\n async (id: string) => {\n if (!scope) return null\n try {\n const snapshot = await getDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n )\n return snapshot.exists()\n ? { parentCompanyId: crmCompanyParentId(snapshot.get('parentCompanyId')) }\n : null\n } catch {\n return null\n }\n },\n [firestore, scope],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft, { lists: picklists.lists, current: company })\n if (result.ok === false) return setError(result.error)\n // A parent is never this company, nor one already under it (AGL-3514).\n const parentId = crmCompanyParentId(result.set['parentCompanyId'])\n if (parentId && parentId !== crmCompanyParentId(company?.parentCompanyId)) {\n const refusal = await crmCompanyParentRefusal(company?.$id ?? null, parentId, readParent)\n if (refusal) return setError(refusal)\n }\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n COMPANY_CLIENT_LIST_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), and the picklist keys what it filters by\n // (AGL-3514), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n followUpSharing('companies', [company.$id])\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\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. Creation only — an edit is a save the card reports.\n followUpSharing('companies', [id])\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n followUpSharing,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n picklists.lists,\n readParent,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n {/*\n Grouped as Salesforce's Account page groups them (AGL-3514):\n what the company is, where it is, and what the team knows.\n */}\n <Typography variant=\"subtitle2\">{'Account information'}</Typography>\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <CompanyPicker\n label=\"Parent company\"\n options={parentOptions}\n value={draft.parentCompanyId || null}\n onChange={(id) => patch('parentCompanyId', id ?? '')}\n ready={companyOptions.ready}\n truncated={companyOptions.truncated}\n disabled={busy}\n helperText=\"The company this one belongs to, such as a subsidiary's group.\"\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Fax\"\n placeholder=\"+1 512 555 0124\"\n value={draft.fax}\n onChange={(event) => patch('fax', event.target.value)}\n />\n {CRM_COMPANY_PICKLIST_FIELDS.map(({ field, picklistId }) => {\n const list = picklists.lists[picklistId]\n return list ? (\n <CrmPicklistSelect\n key={field}\n picklistId={picklistId}\n picklist={list}\n label={COMPANY_PICKLIST_LABELS[field]}\n value={draft[field]}\n stored={company ? String(company[field] ?? '') : undefined}\n onChange={(label) => patch(field, label)}\n disabled={busy}\n />\n ) : null\n })}\n <TextField\n size=\"small\"\n label=\"Account number\"\n value={draft.accountNumber}\n onChange={(event) => patch('accountNumber', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.accountNumber } }}\n />\n <TextField\n size=\"small\"\n label=\"Account site\"\n placeholder=\"Headquarters\"\n helperText=\"Which of the company's locations this record is.\"\n value={draft.site}\n onChange={(event) => patch('site', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.site } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Ticker symbol\"\n value={draft.tickerSymbol}\n onChange={(event) => patch('tickerSymbol', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.tickerSymbol } }}\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n label=\"SIC code\"\n value={draft.sicCode}\n onChange={(event) => patch('sicCode', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.sicCode } }}\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n size=\"small\"\n label=\"Employees\"\n value={draft.numberOfEmployees}\n onChange={(event) => patch('numberOfEmployees', event.target.value)}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Annual revenue\"\n placeholder=\"0.00\"\n value={draft.annualRevenue}\n onChange={(event) => patch('annualRevenue', event.target.value)}\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n sx={{ flex: 1 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Currency\"\n value={draft.currency}\n onChange={(event) => patch('currency', event.target.value)}\n sx={{ width: 120 }}\n >\n {/* A stored code outside the short list stays selectable as itself. */}\n {[...new Set<string>([...DEAL_CURRENCIES, draft.currency])].map((code) => (\n <MenuItem key={code} value={code}>\n {code.toUpperCase()}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address information'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Billing address'}\n </Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={`address.${field.key}`}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress('address', field.key, event.target.value)}\n />\n ))}\n <Stack direction=\"row\" sx={{ alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Shipping address'}\n </Typography>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() =>\n setDraft((current) => ({ ...current, shippingAddress: { ...current.address } }))\n }\n >\n {'Copy billing address'}\n </Button>\n </Stack>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={`shippingAddress.${field.key}`}\n size=\"small\"\n label={field.label}\n value={draft.shippingAddress[field.key] ?? ''}\n onChange={(event) => patchAddress('shippingAddress', field.key, event.target.value)}\n />\n ))}\n <Typography variant=\"subtitle2\">{'Description information'}</Typography>\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","CRM_COMPANY_PICKLIST_FIELDS","CRM_COMPANY_TEXT_MAX","crmCompanyParentId","crmCompanyParentRefusal","crmNewRecordListFields","crmPicklistDefaultLabel","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","getDoc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useRef","useState","useCompanyPicklists","DEAL_CURRENCIES","CompanyPicker","useCompanyOptions","CrmPicklistSelect","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","crmClientListFields","COMPANY_CLIENT_LIST_FIELDS","flatMap","entry","keyField","useCrmSharingFollowUp","COMPANY_PICKLIST_LABELS","type","industry","rating","ownership","accountSource","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","followUpSharing","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","picklists","seededDefaults","companyOptions","enabled","parentOptions","options","filter","option","id","$id","companyId","ownerUid","uid","current","next","field","picklistId","list","lists","fallback","patch","value","patchAddress","which","readParent","snapshot","companies","exists","parentCompanyId","get","handleSave","result","ok","parentId","set","refusal","missing","active","length","join","customDocument","ref","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","updatedAt","message","variant","persist","visibleTo","createdByUid","createdAt","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","truncated","placeholder","domain","website","phone","fax","picklist","String","undefined","accountNumber","slotProps","htmlInput","maxLength","site","direction","tickerSymbol","flex","sicCode","numberOfEmployees","inputMode","annualRevenue","select","currency","width","Set","code","toUpperCase","some","labelFor","tags","address","alignItems","justifyContent","shippingAddress","notes","multiline","minRows","definition","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.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 createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n CRM_COMPANY_PICKLIST_FIELDS,\n CRM_COMPANY_TEXT_MAX,\n type CrmCompany,\n type CrmCompanyPicklistField,\n crmCompanyParentId,\n crmCompanyParentRefusal,\n crmNewRecordListFields,\n crmPicklistDefaultLabel,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n getDoc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useCompanyPicklists } from '../hooks/use-company-picklists'\nimport { DEAL_CURRENCIES } from '../model/deal-board-model'\nimport { CompanyPicker, useCompanyOptions } from './company-picker'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport {\n CRM_CLIENT_SEARCH_FIELDS,\n CRM_CLIENT_SORT_FIELDS,\n crmClientListFields,\n} from '../model/crm-list-query'\n\n/**\n * The list fields a client edit of a company rewrites: the search tokens,\n * and the key beside each picklist field the Companies list filters by\n * (AGL-3514).\n */\nconst COMPANY_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n // Any header sort key the edit's inputs move (AGL-3680).\n ...CRM_CLIENT_SORT_FIELDS,\n ...CRM_COMPANY_PICKLIST_FIELDS.flatMap((entry) => ('keyField' in entry ? [entry.keyField] : [])),\n]\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\ntype AddressDraftField = 'address' | 'shippingAddress'\n\n/** How each picklist field is labeled on a company. */\nconst COMPANY_PICKLIST_LABELS: Record<CrmCompanyPicklistField, string> = {\n type: 'Type',\n industry: 'Industry',\n rating: 'Rating',\n ownership: 'Ownership',\n accountSource: 'Account source',\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, scope?.[1] ?? null)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n /*\n * SALESFORCE'S ACCOUNT FIELDS (AGL-3514): the org's lists behind Type,\n * Industry, Rating, Ownership and Account Source, and the companies a\n * parent may be chosen from — every one the reader can see but this one.\n */\n const picklists = useCompanyPicklists(scope?.[1] ?? null)\n const seededDefaults = useRef(false)\n const companyOptions = useCompanyOptions({ hostId, org, enabled: open })\n const parentOptions = useMemo(\n () => companyOptions.options.filter((option) => option.id !== company?.$id),\n [companyOptions.options, company?.$id],\n )\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n seededDefaults.current = false\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n /*\n * A NEW company starts each picklist on the org's default, once the\n * lists have answered — Salesforce's form opens on it too — and only a\n * field nobody has picked a value for yet.\n */\n useEffect(() => {\n if (!open || company || !picklists.ready || seededDefaults.current) return\n seededDefaults.current = true\n setDraft((current) => {\n const next = { ...current }\n for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS) {\n const list = picklists.lists[picklistId]\n const fallback = list ? crmPicklistDefaultLabel(list) : null\n if (!next[field] && fallback) next[field] = fallback\n }\n return next\n })\n }, [open, company, picklists])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, AddressDraftField>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (which: AddressDraftField, field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n [which]: { ...current[which], [field]: value },\n })),\n [],\n )\n /** The parent's own parent, as the cycle guard walks it; `null` for one this reader cannot see. */\n const readParent = useCallback(\n async (id: string) => {\n if (!scope) return null\n try {\n const snapshot = await getDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n )\n return snapshot.exists()\n ? { parentCompanyId: crmCompanyParentId(snapshot.get('parentCompanyId')) }\n : null\n } catch {\n return null\n }\n },\n [firestore, scope],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft, { lists: picklists.lists, current: company })\n if (result.ok === false) return setError(result.error)\n // A parent is never this company, nor one already under it (AGL-3514).\n const parentId = crmCompanyParentId(result.set['parentCompanyId'])\n if (parentId && parentId !== crmCompanyParentId(company?.parentCompanyId)) {\n const refusal = await crmCompanyParentRefusal(company?.$id ?? null, parentId, readParent)\n if (refusal) return setError(refusal)\n }\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n COMPANY_CLIENT_LIST_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), and the picklist keys what it filters by\n // (AGL-3514), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n followUpSharing('companies', [company.$id])\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\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. Creation only — an edit is a save the card reports.\n followUpSharing('companies', [id])\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n followUpSharing,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n picklists.lists,\n readParent,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n {/*\n Grouped as Salesforce's Account page groups them (AGL-3514):\n what the company is, where it is, and what the team knows.\n */}\n <Typography variant=\"subtitle2\">{'Account information'}</Typography>\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <CompanyPicker\n label=\"Parent company\"\n options={parentOptions}\n value={draft.parentCompanyId || null}\n onChange={(id) => patch('parentCompanyId', id ?? '')}\n ready={companyOptions.ready}\n truncated={companyOptions.truncated}\n disabled={busy}\n helperText=\"The company this one belongs to, such as a subsidiary's group.\"\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Fax\"\n placeholder=\"+1 512 555 0124\"\n value={draft.fax}\n onChange={(event) => patch('fax', event.target.value)}\n />\n {CRM_COMPANY_PICKLIST_FIELDS.map(({ field, picklistId }) => {\n const list = picklists.lists[picklistId]\n return list ? (\n <CrmPicklistSelect\n key={field}\n picklistId={picklistId}\n picklist={list}\n label={COMPANY_PICKLIST_LABELS[field]}\n value={draft[field]}\n stored={company ? String(company[field] ?? '') : undefined}\n onChange={(label) => patch(field, label)}\n disabled={busy}\n />\n ) : null\n })}\n <TextField\n size=\"small\"\n label=\"Account number\"\n value={draft.accountNumber}\n onChange={(event) => patch('accountNumber', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.accountNumber } }}\n />\n <TextField\n size=\"small\"\n label=\"Account site\"\n placeholder=\"Headquarters\"\n helperText=\"Which of the company's locations this record is.\"\n value={draft.site}\n onChange={(event) => patch('site', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.site } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Ticker symbol\"\n value={draft.tickerSymbol}\n onChange={(event) => patch('tickerSymbol', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.tickerSymbol } }}\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n label=\"SIC code\"\n value={draft.sicCode}\n onChange={(event) => patch('sicCode', event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_COMPANY_TEXT_MAX.sicCode } }}\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n size=\"small\"\n label=\"Employees\"\n value={draft.numberOfEmployees}\n onChange={(event) => patch('numberOfEmployees', event.target.value)}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Annual revenue\"\n placeholder=\"0.00\"\n value={draft.annualRevenue}\n onChange={(event) => patch('annualRevenue', event.target.value)}\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n sx={{ flex: 1 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Currency\"\n value={draft.currency}\n onChange={(event) => patch('currency', event.target.value)}\n sx={{ width: 120 }}\n >\n {/* A stored code outside the short list stays selectable as itself. */}\n {[...new Set<string>([...DEAL_CURRENCIES, draft.currency])].map((code) => (\n <MenuItem key={code} value={code}>\n {code.toUpperCase()}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address information'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Billing address'}\n </Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={`address.${field.key}`}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress('address', field.key, event.target.value)}\n />\n ))}\n <Stack direction=\"row\" sx={{ alignItems: 'center', justifyContent: 'space-between' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Shipping address'}\n </Typography>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() =>\n setDraft((current) => ({ ...current, shippingAddress: { ...current.address } }))\n }\n >\n {'Copy billing address'}\n </Button>\n </Stack>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={`shippingAddress.${field.key}`}\n size=\"small\"\n label={field.label}\n value={draft.shippingAddress[field.key] ?? ''}\n onChange={(event) => patchAddress('shippingAddress', field.key, event.target.value)}\n />\n ))}\n <Typography variant=\"subtitle2\">{'Description information'}</Typography>\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","CRM_COMPANY_PICKLIST_FIELDS","CRM_COMPANY_TEXT_MAX","crmCompanyParentId","crmCompanyParentRefusal","crmNewRecordListFields","crmPicklistDefaultLabel","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","getDoc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useRef","useState","useCompanyPicklists","DEAL_CURRENCIES","CompanyPicker","useCompanyOptions","CrmPicklistSelect","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","CRM_CLIENT_SORT_FIELDS","crmClientListFields","COMPANY_CLIENT_LIST_FIELDS","flatMap","entry","keyField","useCrmSharingFollowUp","COMPANY_PICKLIST_LABELS","type","industry","rating","ownership","accountSource","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","followUpSharing","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","picklists","seededDefaults","companyOptions","enabled","parentOptions","options","filter","option","id","$id","companyId","ownerUid","uid","current","next","field","picklistId","list","lists","fallback","patch","value","patchAddress","which","readParent","snapshot","companies","exists","parentCompanyId","get","handleSave","result","ok","parentId","set","refusal","missing","active","length","join","customDocument","ref","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","updatedAt","message","variant","persist","visibleTo","createdByUid","createdAt","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","truncated","placeholder","domain","website","phone","fax","picklist","String","undefined","accountNumber","slotProps","htmlInput","maxLength","site","direction","tickerSymbol","flex","sicCode","numberOfEmployees","inputMode","annualRevenue","select","currency","width","Set","code","toUpperCase","some","labelFor","tags","address","alignItems","justifyContent","shippingAddress","notes","multiline","minRows","definition","displayName"],"mappings":"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"}
@@ -46,7 +46,7 @@ import { CrmSendEmailButton } from "./crm-send-email-button.js";
46
46
  import { ContactDealsCard } from "./contact-deals-card.js";
47
47
  import { CrmCallButton } from "./crm-call-actions.js";
48
48
  import { CrmEmailStateChip } from "./crm-email-state-chip.js";
49
- import { CrmEmailGatewayChip } from "./crm-email-check.js";
49
+ import { CrmAccountLockChip, CrmEmailGatewayChip } from "./crm-email-check.js";
50
50
  import { CrmRecordChip, CrmRecordHeader } from "./crm-record-header.js";
51
51
  import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
52
52
  import { CrmSuiteNotice, crmSuiteIncluded, crmSuiteLockedReason } from "./crm-suite-lock.js";
@@ -390,6 +390,11 @@ const contactDocsHelp = pluginDocsHelp('contacts', {
390
390
  emailState: record.emailState,
391
391
  enabled: suiteIncluded
392
392
  }),
393
+ /*#__PURE__*/ _jsx(CrmAccountLockChip, {
394
+ hostId: siteHostId,
395
+ email: record.email,
396
+ enabled: suiteIncluded
397
+ }),
393
398
  /*#__PURE__*/ _jsx(CrmRecordChip, {
394
399
  label: "Stage",
395
400
  value: record.lifecycleStage ? CONTACT_LIFECYCLE_STAGE_LABELS[record.lifecycleStage] : undefined
@@ -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 { 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 <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","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('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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"}
@@ -22,9 +22,10 @@ import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
22
22
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
23
23
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
24
24
  import ListQueryNotices from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
25
- import { CONTACT_LEAD_SOURCE_COLUMN, CONTACT_LIST_DECLARATION, CONTACT_LIST_FILTER_FIELDS, CONTACT_LIST_FILTER_HEADERS, CONTACT_PREFIX_SEARCH, contactClauseImpliesScope, contactSoloClause, contactCustomFilterFields, contactCustomFilterHeaders, contactQueryClause } from "../constants/contact-filters.js";
25
+ import { CONTACT_LEAD_SOURCE_COLUMN, CONTACT_LIST_DECLARATION, CONTACT_LIST_FILTER_FIELDS, CONTACT_LIST_FILTER_HEADERS, CONTACT_LIST_SORTS, CONTACT_PREFIX_SEARCH, contactClauseImpliesScope, contactSoloClause, contactCustomFilterFields, contactCustomFilterHeaders, contactQueryClause } from "../constants/contact-filters.js";
26
26
  import { crmAskClauses, crmQueryRefusals } from "../model/crm-list-query.js";
27
27
  import { useCrmFoldsScope, useCrmListQuery } from "../hooks/use-crm-list-query.js";
28
+ import { crmViewQuerySort, useCrmColumnSort } from "../hooks/use-crm-column-sort.js";
28
29
  import { contactPrimaryGroup, contactRecordFromDoc } from "../model/contact-record.js";
29
30
  import { useCrmOrgMount } from "../hooks/use-crm-org-mount.js";
30
31
  import { useCrmSharingRules } from "../hooks/use-crm-sharing.js";
@@ -68,6 +69,12 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
68
69
  * The map lives beside the `ContactSource` union so the dynamic-list rule
69
70
  * editor and this filter cannot disagree about what `order` is called.
70
71
  */ const SOURCE_LABELS = CONTACT_SOURCE_LABELS;
72
+ /**
73
+ * The page sorts a column needs other than its cell's text (AGL-3680): the
74
+ * stage by its place in the lifecycle, not its label's spelling.
75
+ */ const CONTACT_PAGE_SORTS = {
76
+ lifecycleStage: (row)=>row.lifecycleStage ? CONTACT_LIFECYCLE_STAGES.indexOf(row.lifecycleStage) : null
77
+ };
71
78
  /**
72
79
  * What the list keeps out of sight until a view says otherwise: the
73
80
  * filter-only columns, which never show, and the optional columns
@@ -278,6 +285,10 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
278
285
  ]);
279
286
  // The quick search is the grid's box, answered by the same query.
280
287
  const [searchWords, setSearchWords] = useState([]);
288
+ // The header order the view asks (AGL-3680), read before the query it orders.
289
+ const contactSort = useMemo(()=>crmViewQuerySort(CONTACT_LIST_SORTS, views.state.sort, CONTACT_LIST_SORTS[0]), [
290
+ views.state.sort
291
+ ]);
281
292
  const paged = useCrmListQuery({
282
293
  scope: dataScope,
283
294
  collection: 'contacts',
@@ -288,7 +299,8 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
288
299
  search: searchWords,
289
300
  impliesScope,
290
301
  soloClause: contactSoloClause,
291
- prefixSearch: CONTACT_PREFIX_SEARCH
302
+ prefixSearch: CONTACT_PREFIX_SEARCH,
303
+ sort: contactSort.sort
292
304
  });
293
305
  const contactDocs = paged.rows;
294
306
  const contactsStatus = paged.status;
@@ -591,6 +603,20 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
591
603
  });
592
604
  const grid = useCrmViewGrid(views, filterColumns, HIDDEN_COLUMNS);
593
605
  /*
606
+ * EVERY HEADER SORTS (AGL-3680): the stored ones on the query
607
+ * (`CONTACT_LIST_SORTS`), the holder's facet values and the joined names
608
+ * over the page, saying so — the stage by its place in the lifecycle.
609
+ */ const columnSort = useCrmColumnSort({
610
+ views,
611
+ sorts: CONTACT_LIST_SORTS,
612
+ defaultSort: CONTACT_LIST_SORTS[0],
613
+ asked: contactSort,
614
+ orderBy: paged.plan.orderBy,
615
+ rows: contacts,
616
+ columns: grid.columns,
617
+ pageSorts: CONTACT_PAGE_SORTS
618
+ });
619
+ /*
594
620
  * A SEGMENT IS A VIEW'S TAG AND SOURCE CLAUSES, kept where a campaign
595
621
  * can reach them (AGL-2617).
596
622
  *
@@ -860,7 +886,10 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
860
886
  }) : null,
861
887
  /*#__PURE__*/ _jsx(ListQueryNotices, {
862
888
  refused: refused,
863
- notices: paged.plan.notices
889
+ notices: [
890
+ ...paged.plan.notices,
891
+ ...columnSort.notices
892
+ ]
864
893
  }),
865
894
  contacts.length === 0 && !views.state.filters.length && !searchWords.some((word)=>word.trim()) ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
866
895
  label: contactsStatus === 'loading' ? 'Loading contacts…' : 'No contacts yet',
@@ -916,7 +945,8 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
916
945
  /*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
917
946
  value: grid.columnOrder,
918
947
  children: /*#__PURE__*/ _jsx(ListTable, {
919
- rows: contacts,
948
+ rows: columnSort.rows,
949
+ columnSort: columnSort,
920
950
  columns: grid.columns,
921
951
  slots: CRM_LIST_SLOTS,
922
952
  selectable: {
@@ -930,8 +960,9 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
930
960
  * grid must not filter again itself. Opening the panel is
931
961
  * what reads the roster and the companies its pickers name.
932
962
  * Columns are the view's, controlled so a saved arrangement
933
- * is what the grid shows; the query orders the rows —
934
- * newest change first — so the grid sorts nothing itself.
963
+ * is what the grid shows. The headers sort through
964
+ * `columnSort` (AGL-3680): a stored column re-asks the
965
+ * query, any other sorts the page and says so.
935
966
  */ filterMode: "server",
936
967
  filterModel: gridFilter.filterModel,
937
968
  onFilterModelChange: gridFilter.onFilterModelChange,
@@ -942,8 +973,6 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
942
973
  noRowsLabel: contactsStatus === 'loading' ? 'Loading…' : 'No contacts match these filters',
943
974
  columnVisibilityModel: grid.columnVisibilityModel,
944
975
  onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
945
- sortingMode: "server",
946
- disableColumnSorting: true,
947
976
  loading: contactsStatus === 'loading',
948
977
  // Paged by the footer below, so the grid must not also slice.
949
978
  hideFooter: true