@aglyn/plugins-crm 1.0.0-beta.234 → 1.0.0-beta.235

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 (29) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/company-detail-page.js +3 -5
  3. package/src/lib/components/company-detail-page.js.map +1 -1
  4. package/src/lib/components/company-properties-card.d.ts +11 -0
  5. package/src/lib/components/company-properties-card.js +7 -3
  6. package/src/lib/components/company-properties-card.js.map +1 -1
  7. package/src/lib/components/contact-detail-page.js +2 -3
  8. package/src/lib/components/contact-detail-page.js.map +1 -1
  9. package/src/lib/components/contact-known-by-card.js +1 -1
  10. package/src/lib/components/contact-known-by-card.js.map +1 -1
  11. package/src/lib/components/contact-properties-card.js +1 -1
  12. package/src/lib/components/contact-properties-card.js.map +1 -1
  13. package/src/lib/components/deal-properties-card.js +1 -1
  14. package/src/lib/components/deal-properties-card.js.map +1 -1
  15. package/src/lib/components/lead-campaigns-card.js +1 -1
  16. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  17. package/src/lib/components/lead-detail-page.js +4 -8
  18. package/src/lib/components/lead-detail-page.js.map +1 -1
  19. package/src/lib/components/lead-history-card.js +1 -1
  20. package/src/lib/components/lead-history-card.js.map +1 -1
  21. package/src/lib/components/lead-properties-card.d.ts +10 -0
  22. package/src/lib/components/lead-properties-card.js +8 -4
  23. package/src/lib/components/lead-properties-card.js.map +1 -1
  24. package/src/lib/components/linked-deals-card.js +1 -1
  25. package/src/lib/components/linked-deals-card.js.map +1 -1
  26. package/src/lib/components/record-sharing-card.js +3 -1
  27. package/src/lib/components/record-sharing-card.js.map +1 -1
  28. package/src/lib/components/settings-section.js +3 -1
  29. package/src/lib/components/settings-section.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n type AglynOrgBilling,\n composeContactName,\n CONTACT_BIRTHDATE_REFUSAL,\n CONTACT_EXTRA_PHONE_FIELDS,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_NAME_PART_MAX,\n CONTACT_PHONE_FIELD_LABELS,\n type ConsentGroup,\n type ContactLifecycleStage,\n CRM_SALUTATION_PICKLIST,\n crmTelHref,\n normalizeContactBirthdate,\n normalizePhone,\n} from '@aglyn/aglyn'\nimport { mdiPhoneOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n FormControlLabel,\n FormHelperText,\n Grid,\n IconButton,\n InputAdornment,\n MenuItem,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useContactUpdate } from '../hooks/use-contact-update'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type ContactRecord, parseContactTags } from '../model/contact-record'\nimport type { ContactUpdateFields } from '../model/contact-update'\nimport { setContactStage } from '../model/crm-api'\nimport {\n type CrmCustomDraft,\n crmCustomDraftChanges,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n} from '../model/crm-custom-draft'\nimport {\n CompanyPicker,\n useCompanyOptions,\n useCreateCompany,\n} from './company-picker'\nimport {\n addressDraftFrom,\n ContactAddressFields,\n type AddressDraft,\n} from './contact-address-fields'\nimport { ContactReportsToField } from './contact-reports-to-field'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { crmSuiteLockedReason } from './crm-suite-lock'\nimport { LeadSourceSelect } from './lead-source-select'\nimport type { OrgMembers } from './use-org-members'\n\nexport interface ContactPropertiesCardProps {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n /** The org document the shell passed, for the company picker's scope. */\n org?: Partial<AglynOrgBilling> | null\n /** The row, flattened through the viewing group's facet. */\n record: ContactRecord\n /**\n * The org the record belongs to, as the page resolved it — for the org's\n * lead source values (AGL-3298) and its custom contact fields (AGL-2601).\n * Null while it settles, when the select offers the starter list, no\n * custom field is drawn yet, and the save still sends only what changed.\n */\n orgId?: string | null\n /** The controller whose facet the edits are saved into. */\n consentGroup: ConsentGroup\n /**\n * The listener's verdict on the row the drafts were seeded from, for the\n * guard that refuses a save over an unconfirmed read.\n */\n seed: { status: 'loading' | 'success' | 'error'; fromCache: boolean }\n /** The team, for the owner picker and the owner's name. */\n members: OrgMembers\n /**\n * The org's plan lacks the CRM (AGL-2788), which the shell mounts no CRM\n * page for (AGL-2851). The owner, the lifecycle stage and the company\n * show, locked, and are left out of a save; `crm/contact-update` refuses\n * such a plan the rest of the profile, the tags and the notes too. The\n * custom fields are the CRM's as well, and are not drawn.\n */\n suiteLocked?: boolean\n}\n\n/**\n * THE RECORD ITSELF: every field a team keeps on a person, in one card with\n * one Save in its header (AGL-2596).\n *\n * ## Custom fields sit with the built-in ones\n *\n * The org's own contact fields (AGL-2601) are drawn under **More fields**,\n * after the built-in properties, the way Salesforce keeps custom fields in\n * the record's Details beside the standard ones — and they save with them.\n * An org with no active field draws no subsection at all.\n *\n * A custom value is THIS holder's (`facets.{group}.custom.{key}`), so the\n * draft holds only the keys the reader touched and the save sends only the\n * keys that changed: the route writes each at its own dotted path, and a\n * `custom` map sent whole would take every key this card did not touch out\n * with it. A cleared control sends `null`, the explicit \"cleared\" the model\n * keeps the key present with, and a required field left empty is refused\n * before anything is sent.\n *\n * ## One save, one request, one guard\n *\n * A field-at-a-time save would be nine requests and nine chances for the\n * stale-seed guard to refuse one of them — so the card holds a draft of the\n * whole profile and sends it once. The guard WRAPS the send: a row seeded\n * from the cache or from a failed read is refused with the message the guard\n * chooses, and what was typed stays in the fields, because a refusal that\n * also emptied the form would read as a save that worked.\n *\n * ## The server writes the facet (AGL-2804)\n *\n * Every field here lives in THIS holder's facet, and the Firestore rules\n * cannot tell one field of a facet from another, so the card writes nothing\n * itself. The draft goes to `crm/contact-update`, which writes each field by\n * dotted path into the holder's facet — never a nested object, which would\n * take every other holder's records with it — clears a blank field rather\n * than storing it empty, keeps the phone's and the company's search echoes\n * at the top of the document, and refuses every field to a plan without\n * the CRM. A refusal is shown in the route's own words.\n *\n * ## The stage goes to its own route\n *\n * The lifecycle stage is the one field an automation listens for:\n * `contactStageChanged` (AGL-2605) is emitted by `crm/contact-stage` after it\n * performs the write, and by nothing else. So when the stage the reader\n * picked differs from the one the record holds, it is sent to that route\n * once the rest of the profile has landed — the stage picked, or `null` for\n * \"Not placed yet\", which clears it and announces nothing. A refusal from\n * that route is reported on its own, with its sentence, after the fields that\n * did save.\n *\n * ## The name is an override\n *\n * The canonical name is shared by every site holding the person, so this\n * card never edits it. What it saves is this holder's own name for them,\n * and the helper says what a blank falls back to, so nobody clears the\n * field expecting the record to go nameless.\n *\n * ## Salesforce's sections (AGL-3515)\n *\n * The fields are grouped the way Salesforce's contact Details groups them —\n * Contact information, Phones, Addresses, Additional information — and every\n * one is still one draft and one Save. While the holder keeps a first or\n * last name the Name field shows their composition and is read-only; the\n * save sends the parts, and the route composes the name from them. The\n * salutation and the reports-to are sent only when they change, for the\n * lead source's reason: the route judges a sent one. Do not call is a hint\n * on every phone's call link, never a block.\n *\n * ## Company is a record, with its name kept beside it\n *\n * The Company field is the picker (AGL-2613): the link is `companyId` in\n * this holder's facet, mirrored into the top-level `companyIds` for the\n * company page's query, and the company's contacts count moves with it —\n * all planned by the route from the stored document, so this card never\n * reasons about another holder's link. The name is sent too, from the picked\n * company, because the list column and the global search read the name and\n * not the id; a record that carries a name with no link — an import, a save\n * from before the picker — keeps it as the label, and the picker offers it\n * as the company to link or create.\n */\n/** The phone fields the card edits, in the order the Phones section lists them. */\nconst PHONE_FIELDS = ['phone', ...CONTACT_EXTRA_PHONE_FIELDS] as const\ntype PhoneField = (typeof PHONE_FIELDS)[number]\ntype PhoneDrafts = Record<PhoneField, string>\n\nconst phoneDraftsFrom = (record: ContactRecord): PhoneDrafts =>\n Object.fromEntries(\n PHONE_FIELDS.map((field) => [field, String(record[field] ?? '')]),\n ) as PhoneDrafts\n\n/** The phones as the route stores them, or the fields that cannot be read as numbers. */\nfunction readPhones(\n drafts: PhoneDrafts,\n): { ok: true; phones: PhoneDrafts } | { ok: false; errors: Partial<PhoneDrafts> } {\n const phones = {} as PhoneDrafts\n const errors: Partial<PhoneDrafts> = {}\n for (const field of PHONE_FIELDS) {\n const typed = drafts[field].trim()\n const normalized = typed ? normalizePhone(typed) : ''\n if (normalized === null) errors[field] = PHONE_HELP_ERROR\n else phones[field] = normalized\n }\n return Object.keys(errors).length ? { ok: false, errors } : { ok: true, phones }\n}\n\nconst PHONE_HELP = 'With the country code, like +1 512 555 0107'\nconst PHONE_HELP_ERROR = 'Enter it with its country code, like +1 512 555 0107.'\n\n/** A section heading inside the card — Salesforce's Details groups its fields the same way. */\nfunction SectionHeading(props: { children: string }) {\n return (\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"subtitle2\" color=\"text.secondary\">\n {props.children}\n </Typography>\n </Grid>\n )\n}\n\nexport function ContactPropertiesCard(props: ContactPropertiesCardProps) {\n const {\n hostId,\n org,\n record,\n orgId = null,\n consentGroup,\n seed,\n members,\n suiteLocked = false,\n } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const contactUpdate = useContactUpdate(hostId)\n // The site a stage move is routed through: the mounted one, or at the\n // organization level the holder's own (AGL-2630).\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 companies = useCompanyOptions({ hostId, org })\n const createCompany = useCreateCompany({ hostId, org })\n\n const [nameOverride, setNameOverride] = useState(record.nameOverride)\n const [salutation, setSalutation] = useState(record.salutation ?? '')\n const salutations = useCrmPicklist(CRM_SALUTATION_PICKLIST, orgId)\n const [firstName, setFirstName] = useState(record.firstName ?? '')\n const [lastName, setLastName] = useState(record.lastName ?? '')\n const [phones, setPhones] = useState<PhoneDrafts>(() => phoneDraftsFrom(record))\n const [doNotCall, setDoNotCall] = useState(record.doNotCall === true)\n const [jobTitle, setJobTitle] = useState(record.jobTitle)\n const [department, setDepartment] = useState(record.department ?? '')\n const [birthdate, setBirthdate] = useState(record.birthdate ?? '')\n const [assistantName, setAssistantName] = useState(record.assistantName ?? '')\n const [reportsTo, setReportsTo] = useState(record.reportsToContactId ?? '')\n // Salesforce's Lead Source (AGL-3298), carried from the lead on conversion.\n const [leadSource, setLeadSource] = useState(record.leadSource)\n const leadSources = useLeadSourcePicklist(orgId)\n const [companyName, setCompanyName] = useState(record.companyName)\n const [companyId, setCompanyId] = useState<string | null>(\n record.companyId || null,\n )\n const [ownerUid, setOwnerUid] = useState(record.ownerUid)\n const [lifecycleStage, setLifecycleStage] = useState<ContactLifecycleStage | ''>(\n record.lifecycleStage,\n )\n const [tags, setTags] = useState(record.tags.join(', '))\n const [notes, setNotes] = useState(record.notes)\n const [address, setAddress] = useState<AddressDraft>(\n addressDraftFrom(record.address),\n )\n const [otherAddress, setOtherAddress] = useState<AddressDraft>(\n addressDraftFrom(record.otherAddress),\n )\n const [phoneErrors, setPhoneErrors] = useState<Partial<PhoneDrafts>>({})\n const [birthdateError, setBirthdateError] = useState('')\n const [saving, setSaving] = useState(false)\n /** The custom values the reader touched; every other key reads from the record. */\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /** Something was edited since the card was seeded or last saved. */\n const [edited, setEdited] = useState(false)\n const fields = useContactFieldDefinitions(suiteLocked ? null : orgId)\n // The custom fields are the CRM's; a plan without it draws and sends none.\n const customFields = suiteLocked ? [] : fields.active\n const storedCustom = useMemo(() => record.custom ?? {}, [record.custom])\n const customChanges = useMemo(\n () => crmCustomDraftChanges(storedCustom, custom),\n [storedCustom, custom],\n )\n const clearingRequired =\n crmCustomDraftMissingRequired(customFields, storedCustom, custom, 'edit').length > 0\n /*\n * The name the first and last names make (AGL-3515). While there is one,\n * it IS the name — the Name field shows it, read-only, and the save sends\n * the parts rather than a name the route would have to reconcile.\n */\n const composedName = composeContactName(firstName, lastName)\n\n /** One field's edit: the value, and the card now has something to save. */\n const change = useCallback(<T,>(set: (value: T) => void, value: T) => {\n set(value)\n setEdited(true)\n }, [])\n\n /** Every draft back to what the record holds — the seed, and Discard. */\n const reseed = () => {\n setNameOverride(record.nameOverride)\n setSalutation(record.salutation ?? '')\n setFirstName(record.firstName ?? '')\n setLastName(record.lastName ?? '')\n setPhones(phoneDraftsFrom(record))\n setDoNotCall(record.doNotCall === true)\n setJobTitle(record.jobTitle)\n setDepartment(record.department ?? '')\n setBirthdate(record.birthdate ?? '')\n setAssistantName(record.assistantName ?? '')\n setReportsTo(record.reportsToContactId ?? '')\n setLeadSource(record.leadSource)\n setCompanyName(record.companyName)\n setCompanyId(record.companyId || null)\n setOwnerUid(record.ownerUid)\n setLifecycleStage(record.lifecycleStage)\n setTags(record.tags.join(', '))\n setNotes(record.notes)\n setAddress(addressDraftFrom(record.address))\n setOtherAddress(addressDraftFrom(record.otherAddress))\n setPhoneErrors({})\n setBirthdateError('')\n setCustom({})\n setEdited(false)\n }\n\n /*\n * Re-seeded when the RECORD changes, not when it re-renders. The listener\n * delivers a fresh row on every snapshot, including the one this card's\n * own save produces; re-seeding on each would overwrite what somebody is\n * in the middle of typing with what the server last confirmed.\n */\n const recordId = record.$id\n useEffect(() => {\n reseed()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [recordId])\n\n const handleSave = useCallback(async () => {\n if (clearingRequired) return\n const readPhonesResult = readPhones(phones)\n const storedBirthdate = normalizeContactBirthdate(birthdate)\n setPhoneErrors('errors' in readPhonesResult ? readPhonesResult.errors : {})\n setBirthdateError(storedBirthdate === null ? CONTACT_BIRTHDATE_REFUSAL : '')\n if ('errors' in readPhonesResult || storedBirthdate === null) return\n // A stage that differs from the record's is the stage route's to write —\n // see the note above.\n const stageChanged = !suiteLocked && lifecycleStage !== record.lifecycleStage\n const set: ContactUpdateFields = {\n // The name is the parts' while there are parts — see the note above.\n ...(composedName ? {} : { name: nameOverride.trim().slice(0, 120) }),\n firstName: firstName.trim(),\n lastName: lastName.trim(),\n ...readPhonesResult.phones,\n // Sent only when it moves: the route stores the flag as a fact about\n // the person, and an untouched card should not re-assert it.\n ...(doNotCall !== (record.doNotCall === true) ? { doNotCall } : {}),\n jobTitle: jobTitle.trim().slice(0, 120),\n department: department.trim().slice(0, 120),\n birthdate: storedBirthdate,\n assistantName: assistantName.trim().slice(0, 120),\n // Sent only when changed: the route judges a sent value against the\n // org's list, and an untouched one needs no judging. The reports-to\n // likewise, because the route checks a sent one for a loop.\n ...(leadSource !== record.leadSource ? { leadSource } : {}),\n ...(salutation !== (record.salutation ?? '') ? { salutation } : {}),\n ...(reportsTo !== (record.reportsToContactId ?? '')\n ? { reportsToContactId: reportsTo || null }\n : {}),\n address,\n otherAddress,\n tags: parseContactTags(tags),\n notes: notes.slice(0, 2000),\n // The owner and the company, sent only on a plan that carries the CRM.\n ...(suiteLocked\n ? {}\n : { ownerUid, companyId, companyName: companyName.trim().slice(0, 120) }),\n // Only the custom keys that changed — see the note above.\n ...(!suiteLocked && customChanges.length\n ? { custom: Object.fromEntries(customChanges) }\n : {}),\n }\n setSaving(true)\n try {\n const verdict = await writeGuardedBySeed(\n {\n subject: 'contact',\n unreadable: seed.status === 'error',\n fromCache: seed.fromCache,\n },\n () => contactUpdate.updateOne(record.$id, set),\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n logActivity('Updated contact', {\n type: 'contact',\n id: record.$id,\n name: record.name || record.email,\n })\n // The custom values are stored; the controls read the record again.\n setCustom({})\n if (stageChanged && siteHostId) {\n try {\n await setContactStage(user, siteHostId, record.$id, lifecycleStage || null)\n } catch (error) {\n // The rest of the profile is saved; only the stage was refused, and\n // the route's own sentence says why.\n return void enqueueSnackbar(\n `Saved, but the stage could not be changed: ${\n error instanceof Error ? error.message : 'the request failed'\n }`,\n { variant: 'warning', persist: false },\n )\n }\n }\n setEdited(false)\n enqueueSnackbar('Contact saved', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n } finally {\n setSaving(false)\n }\n }, [\n address,\n assistantName,\n birthdate,\n clearingRequired,\n companyId,\n companyName,\n composedName,\n contactUpdate,\n customChanges,\n department,\n doNotCall,\n enqueueSnackbar,\n firstName,\n siteHostId,\n jobTitle,\n lastName,\n leadSource,\n lifecycleStage,\n logActivity,\n nameOverride,\n notes,\n otherAddress,\n ownerUid,\n phones,\n record.$id,\n record.doNotCall,\n record.email,\n record.leadSource,\n record.lifecycleStage,\n record.name,\n record.reportsToContactId,\n record.salutation,\n reportsTo,\n salutation,\n seed.fromCache,\n seed.status,\n suiteLocked,\n tags,\n user,\n ])\n\n /*\n * An owner the roster no longer lists — somebody who left the team — is\n * still offered as the current value, named by their uid, so the select is\n * never handed a value it has no option for and the reader can see who it\n * was before reassigning.\n */\n const ownerKnown = members.options.some((option) => option.uid === ownerUid)\n\n /** One phone field, with click-to-call off what is IN the box (AGL-2661). */\n const phoneField = (field: PhoneField) => {\n const value = phones[field]\n const telHref = crmTelHref(value)\n const callLabel = doNotCall\n ? `Call ${value.trim()} — marked do not call`\n : `Call ${value.trim()}`\n return (\n <Grid key={field} size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label={CONTACT_PHONE_FIELD_LABELS[field]}\n value={value}\n onChange={(event) => change(setPhones, { ...phones, [field]: event.target.value })}\n error={Boolean(phoneErrors[field])}\n helperText={phoneErrors[field] || (field === 'phone' ? PHONE_HELP : undefined)}\n fullWidth\n slotProps={{\n input: {\n /*\n * The number on screen is the one a reader means to ring, and\n * `crmTelHref` withholds the link from anything half-typed. A\n * do-not-call person keeps the link — the rep decides — with\n * the request said in the tooltip and the icon's color.\n */\n endAdornment:\n telHref && field !== 'fax' ? (\n <InputAdornment position=\"end\">\n <Tooltip title={callLabel}>\n <IconButton\n component=\"a\"\n href={telHref}\n size=\"small\"\n edge=\"end\"\n color={doNotCall ? 'warning' : 'default'}\n aria-label={callLabel}\n >\n <MdiIcon path={mdiPhoneOutline.path} size={0.8} />\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ) : null,\n },\n }}\n />\n </Grid>\n )\n }\n\n return (\n <CardDisplay\n header={'Properties'}\n help={Aglyn.pluginDocsHelp('contactRecord', { anchor: '#the-record-page' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n {edited ? (\n <Button size=\"small\" disabled={saving} onClick={reseed}>\n {'Discard changes'}\n </Button>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={saving || !edited || clearingRequired}\n onClick={() => void handleSave()}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n ),\n }}\n >\n <Grid container spacing={2}>\n <SectionHeading>{'Contact information'}</SectionHeading>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Email\"\n value={record.email}\n slotProps={{ input: { readOnly: true } }}\n helperText=\"The identity every site shares — it cannot be edited here.\"\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <CrmPicklistSelect\n picklistId={CRM_SALUTATION_PICKLIST}\n picklist={salutations.picklist}\n value={salutation}\n stored={record.salutation ?? ''}\n onChange={(value) => change(setSalutation, value)}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"First name\"\n value={firstName}\n onChange={(event) => change(setFirstName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: CONTACT_NAME_PART_MAX } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Last name\"\n value={lastName}\n onChange={(event) => change(setLastName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: CONTACT_NAME_PART_MAX } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Name\"\n value={composedName || nameOverride}\n onChange={(event) => change(setNameOverride, event.target.value)}\n slotProps={{\n htmlInput: { maxLength: 120 },\n input: { readOnly: Boolean(composedName) },\n }}\n helperText={\n composedName\n ? 'Made of the first and last name.'\n : record.canonicalName\n ? `Your own name for this person. Blank shows the name they gave: ${record.canonicalName}.`\n : 'Your own name for this person.'\n }\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={jobTitle}\n onChange={(event) => change(setJobTitle, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Department\"\n value={department}\n onChange={(event) => change(setDepartment, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <CompanyPicker\n options={companies.options}\n ready={companies.ready}\n truncated={companies.truncated}\n value={companyId}\n onChange={(id, company) => {\n change(setCompanyId, id)\n // The label follows the link: the picked name, or nothing\n // once the link is cleared. A company the list cannot name\n // keeps whatever label the record already had.\n setCompanyName(company ? company.name : id ? companyName : '')\n }}\n onCreate={createCompany}\n email={record.email}\n fallbackName={companyName}\n disabled={saving || suiteLocked}\n helperText={suiteLocked ? crmSuiteLockedReason() : undefined}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <LeadSourceSelect\n picklist={leadSources.picklist}\n value={leadSource}\n stored={record.leadSource}\n onChange={(value) => change(setLeadSource, value)}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n select\n size=\"small\"\n label=\"Lifecycle stage\"\n value={lifecycleStage}\n onChange={(event) =>\n change(setLifecycleStage, event.target.value as ContactLifecycleStage | '')\n }\n disabled={suiteLocked}\n helperText={suiteLocked ? crmSuiteLockedReason() : undefined}\n fullWidth\n >\n <MenuItem value=\"\">{'Not placed yet'}</MenuItem>\n {CONTACT_LIFECYCLE_STAGES.map((stage) => (\n <MenuItem key={stage} value={stage}>\n {CONTACT_LIFECYCLE_STAGE_LABELS[stage]}\n </MenuItem>\n ))}\n </TextField>\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={ownerUid}\n onChange={(event) => change(setOwnerUid, event.target.value)}\n disabled={suiteLocked}\n helperText={\n suiteLocked\n ? crmSuiteLockedReason()\n : members.ready && !members.options.length\n ? 'The team roster could not be read, so nobody can be picked yet.'\n : 'The team member responsible for this relationship'\n }\n fullWidth\n >\n <MenuItem value=\"\">{'Unassigned'}</MenuItem>\n {ownerUid && !ownerKnown ? (\n <MenuItem value={ownerUid}>{members.memberName(ownerUid)}</MenuItem>\n ) : null}\n {members.options.map((owner) => (\n <MenuItem key={owner.uid} value={owner.uid}>\n {Aglyn.crmMemberPickerLabel(owner)}\n </MenuItem>\n ))}\n </TextField>\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"vip, beta\"\n helperText=\"Comma-separated\"\n value={tags}\n onChange={(event) => change(setTags, event.target.value)}\n fullWidth\n />\n </Grid>\n\n <SectionHeading>{'Phones'}</SectionHeading>\n {PHONE_FIELDS.filter((field) => field !== 'assistantPhone').map(phoneField)}\n <Grid size={{ xs: 12, sm: 6 }}>\n <FormControlLabel\n control={\n <Checkbox\n checked={doNotCall}\n onChange={(event) => change(setDoNotCall, event.target.checked)}\n />\n }\n label=\"Do not call\"\n />\n <FormHelperText>\n {'They asked not to be phoned. Every number still dials; the Call button says so.'}\n </FormHelperText>\n </Grid>\n\n <SectionHeading>{'Addresses'}</SectionHeading>\n <Grid size={{ xs: 12 }}>\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Mailing address'}</Typography>\n <ContactAddressFields\n value={address}\n onChange={(value) => change(setAddress, value)}\n />\n </Stack>\n </Grid>\n <Grid size={{ xs: 12 }}>\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Other address'}</Typography>\n <ContactAddressFields\n value={otherAddress}\n onChange={(value) => change(setOtherAddress, value)}\n />\n </Stack>\n </Grid>\n\n <SectionHeading>{'Additional information'}</SectionHeading>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"Birthdate\"\n value={birthdate}\n onChange={(event) => change(setBirthdate, event.target.value)}\n error={Boolean(birthdateError)}\n helperText={birthdateError || undefined}\n slotProps={{ inputLabel: { shrink: true } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <ContactReportsToField\n hostId={hostId}\n org={org}\n contactId={record.$id}\n groupId={consentGroup.groupId}\n value={reportsTo}\n onChange={(value) => change(setReportsTo, value)}\n disabled={saving}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Assistant\"\n value={assistantName}\n onChange={(event) => change(setAssistantName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n {phoneField('assistantPhone')}\n <Grid size={{ xs: 12 }}>\n <TextField\n size=\"small\"\n label=\"About\"\n value={notes}\n onChange={(event) => change(setNotes, event.target.value)}\n helperText=\"Notes for your team. Nobody outside this site's group can read them.\"\n multiline\n minRows={3}\n fullWidth\n />\n </Grid>\n {/*\n The org's own fields (AGL-2601), after the built-in ones and saved\n with them. Absent, not an empty box, while the org defines none.\n */}\n {customFields.length ? (\n <>\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"subtitle2\">{'More fields'}</Typography>\n </Grid>\n {customFields.map((definition) => (\n <Grid key={definition.$id} size={{ xs: 12, sm: 6 }}>\n <CrmCustomFieldControl\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n change(setCustom, { ...custom, [definition.key]: value })\n }\n disabled={saving}\n />\n </Grid>\n ))}\n {clearingRequired ? (\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"caption\" color=\"error\">\n {'A required field cannot be left empty.'}\n </Typography>\n </Grid>\n ) : null}\n </>\n ) : null}\n </Grid>\n </CardDisplay>\n )\n}\nContactPropertiesCard.displayName = 'ContactPropertiesCard'\n\nexport default ContactPropertiesCard\n"],"names":["Aglyn","composeContactName","CONTACT_BIRTHDATE_REFUSAL","CONTACT_EXTRA_PHONE_FIELDS","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_LIFECYCLE_STAGES","CONTACT_NAME_PART_MAX","CONTACT_PHONE_FIELD_LABELS","CRM_SALUTATION_PICKLIST","crmTelHref","normalizeContactBirthdate","normalizePhone","mdiPhoneOutline","CardDisplay","MdiIcon","useSnackbar","useUser","writeGuardedBySeed","Button","Checkbox","FormControlLabel","FormHelperText","Grid","IconButton","InputAdornment","MenuItem","Stack","TextField","Tooltip","Typography","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useContactUpdate","useCrmActivityLogger","useCrmPicklist","useLeadSourcePicklist","parseContactTags","setContactStage","crmCustomDraftChanges","crmCustomDraftMissingRequired","crmCustomDraftValue","CompanyPicker","useCompanyOptions","useCreateCompany","addressDraftFrom","ContactAddressFields","ContactReportsToField","CrmCustomFieldControl","CrmPicklistSelect","crmSuiteLockedReason","LeadSourceSelect","PHONE_FIELDS","phoneDraftsFrom","record","Object","fromEntries","map","field","String","readPhones","drafts","phones","errors","typed","trim","normalized","PHONE_HELP_ERROR","keys","length","ok","PHONE_HELP","SectionHeading","props","size","xs","variant","color","children","ContactPropertiesCard","hostId","org","orgId","consentGroup","seed","members","suiteLocked","data","user","enqueueSnackbar","contactUpdate","siteHostId","logActivity","companies","createCompany","nameOverride","setNameOverride","salutation","setSalutation","salutations","firstName","setFirstName","lastName","setLastName","setPhones","doNotCall","setDoNotCall","jobTitle","setJobTitle","department","setDepartment","birthdate","setBirthdate","assistantName","setAssistantName","reportsTo","setReportsTo","reportsToContactId","leadSource","setLeadSource","leadSources","companyName","setCompanyName","companyId","setCompanyId","ownerUid","setOwnerUid","lifecycleStage","setLifecycleStage","tags","setTags","join","notes","setNotes","address","setAddress","otherAddress","setOtherAddress","phoneErrors","setPhoneErrors","birthdateError","setBirthdateError","saving","setSaving","custom","setCustom","edited","setEdited","fields","customFields","active","storedCustom","customChanges","clearingRequired","composedName","change","set","value","reseed","recordId","$id","handleSave","readPhonesResult","storedBirthdate","stageChanged","name","slice","verdict","subject","unreadable","status","fromCache","updateOne","message","persist","type","id","email","error","Error","console","allowDuplicate","ownerKnown","options","some","option","uid","phoneField","telHref","callLabel","sm","label","onChange","event","target","Boolean","helperText","undefined","fullWidth","slotProps","input","endAdornment","position","title","component","href","edge","aria-label","path","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","disabled","onClick","container","readOnly","picklistId","picklist","stored","htmlInput","maxLength","canonicalName","ready","truncated","company","onCreate","fallbackName","select","stage","memberName","owner","crmMemberPickerLabel","placeholder","filter","control","checked","inputLabel","shrink","contactId","groupId","multiline","minRows","definition","key","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n type AglynOrgBilling,\n composeContactName,\n CONTACT_BIRTHDATE_REFUSAL,\n CONTACT_EXTRA_PHONE_FIELDS,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_NAME_PART_MAX,\n CONTACT_PHONE_FIELD_LABELS,\n type ConsentGroup,\n type ContactLifecycleStage,\n CRM_SALUTATION_PICKLIST,\n crmTelHref,\n normalizeContactBirthdate,\n normalizePhone,\n} from '@aglyn/aglyn'\nimport { mdiPhoneOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n FormControlLabel,\n FormHelperText,\n Grid,\n IconButton,\n InputAdornment,\n MenuItem,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useContactUpdate } from '../hooks/use-contact-update'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type ContactRecord, parseContactTags } from '../model/contact-record'\nimport type { ContactUpdateFields } from '../model/contact-update'\nimport { setContactStage } from '../model/crm-api'\nimport {\n type CrmCustomDraft,\n crmCustomDraftChanges,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n} from '../model/crm-custom-draft'\nimport {\n CompanyPicker,\n useCompanyOptions,\n useCreateCompany,\n} from './company-picker'\nimport {\n addressDraftFrom,\n ContactAddressFields,\n type AddressDraft,\n} from './contact-address-fields'\nimport { ContactReportsToField } from './contact-reports-to-field'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { crmSuiteLockedReason } from './crm-suite-lock'\nimport { LeadSourceSelect } from './lead-source-select'\nimport type { OrgMembers } from './use-org-members'\n\nexport interface ContactPropertiesCardProps {\n /** The site the record is read under, or `null` at the organization level. */\n hostId: string | null\n /** The org document the shell passed, for the company picker's scope. */\n org?: Partial<AglynOrgBilling> | null\n /** The row, flattened through the viewing group's facet. */\n record: ContactRecord\n /**\n * The org the record belongs to, as the page resolved it — for the org's\n * lead source values (AGL-3298) and its custom contact fields (AGL-2601).\n * Null while it settles, when the select offers the starter list, no\n * custom field is drawn yet, and the save still sends only what changed.\n */\n orgId?: string | null\n /** The controller whose facet the edits are saved into. */\n consentGroup: ConsentGroup\n /**\n * The listener's verdict on the row the drafts were seeded from, for the\n * guard that refuses a save over an unconfirmed read.\n */\n seed: { status: 'loading' | 'success' | 'error'; fromCache: boolean }\n /** The team, for the owner picker and the owner's name. */\n members: OrgMembers\n /**\n * The org's plan lacks the CRM (AGL-2788), which the shell mounts no CRM\n * page for (AGL-2851). The owner, the lifecycle stage and the company\n * show, locked, and are left out of a save; `crm/contact-update` refuses\n * such a plan the rest of the profile, the tags and the notes too. The\n * custom fields are the CRM's as well, and are not drawn.\n */\n suiteLocked?: boolean\n}\n\n/**\n * THE RECORD ITSELF: every field a team keeps on a person, in one card with\n * one Save in its header (AGL-2596).\n *\n * ## Custom fields sit with the built-in ones\n *\n * The org's own contact fields (AGL-2601) are drawn under **More fields**,\n * after the built-in properties, the way Salesforce keeps custom fields in\n * the record's Details beside the standard ones — and they save with them.\n * An org with no active field draws no subsection at all.\n *\n * A custom value is THIS holder's (`facets.{group}.custom.{key}`), so the\n * draft holds only the keys the reader touched and the save sends only the\n * keys that changed: the route writes each at its own dotted path, and a\n * `custom` map sent whole would take every key this card did not touch out\n * with it. A cleared control sends `null`, the explicit \"cleared\" the model\n * keeps the key present with, and a required field left empty is refused\n * before anything is sent.\n *\n * ## One save, one request, one guard\n *\n * A field-at-a-time save would be nine requests and nine chances for the\n * stale-seed guard to refuse one of them — so the card holds a draft of the\n * whole profile and sends it once. The guard WRAPS the send: a row seeded\n * from the cache or from a failed read is refused with the message the guard\n * chooses, and what was typed stays in the fields, because a refusal that\n * also emptied the form would read as a save that worked.\n *\n * ## The server writes the facet (AGL-2804)\n *\n * Every field here lives in THIS holder's facet, and the Firestore rules\n * cannot tell one field of a facet from another, so the card writes nothing\n * itself. The draft goes to `crm/contact-update`, which writes each field by\n * dotted path into the holder's facet — never a nested object, which would\n * take every other holder's records with it — clears a blank field rather\n * than storing it empty, keeps the phone's and the company's search echoes\n * at the top of the document, and refuses every field to a plan without\n * the CRM. A refusal is shown in the route's own words.\n *\n * ## The stage goes to its own route\n *\n * The lifecycle stage is the one field an automation listens for:\n * `contactStageChanged` (AGL-2605) is emitted by `crm/contact-stage` after it\n * performs the write, and by nothing else. So when the stage the reader\n * picked differs from the one the record holds, it is sent to that route\n * once the rest of the profile has landed — the stage picked, or `null` for\n * \"Not placed yet\", which clears it and announces nothing. A refusal from\n * that route is reported on its own, with its sentence, after the fields that\n * did save.\n *\n * ## The name is an override\n *\n * The canonical name is shared by every site holding the person, so this\n * card never edits it. What it saves is this holder's own name for them,\n * and the helper says what a blank falls back to, so nobody clears the\n * field expecting the record to go nameless.\n *\n * ## Salesforce's sections (AGL-3515)\n *\n * The fields are grouped the way Salesforce's contact Details groups them —\n * Contact information, Phones, Addresses, Additional information — and every\n * one is still one draft and one Save. While the holder keeps a first or\n * last name the Name field shows their composition and is read-only; the\n * save sends the parts, and the route composes the name from them. The\n * salutation and the reports-to are sent only when they change, for the\n * lead source's reason: the route judges a sent one. Do not call is a hint\n * on every phone's call link, never a block.\n *\n * ## Company is a record, with its name kept beside it\n *\n * The Company field is the picker (AGL-2613): the link is `companyId` in\n * this holder's facet, mirrored into the top-level `companyIds` for the\n * company page's query, and the company's contacts count moves with it —\n * all planned by the route from the stored document, so this card never\n * reasons about another holder's link. The name is sent too, from the picked\n * company, because the list column and the global search read the name and\n * not the id; a record that carries a name with no link — an import, a save\n * from before the picker — keeps it as the label, and the picker offers it\n * as the company to link or create.\n */\n/** The phone fields the card edits, in the order the Phones section lists them. */\nconst PHONE_FIELDS = ['phone', ...CONTACT_EXTRA_PHONE_FIELDS] as const\ntype PhoneField = (typeof PHONE_FIELDS)[number]\ntype PhoneDrafts = Record<PhoneField, string>\n\nconst phoneDraftsFrom = (record: ContactRecord): PhoneDrafts =>\n Object.fromEntries(\n PHONE_FIELDS.map((field) => [field, String(record[field] ?? '')]),\n ) as PhoneDrafts\n\n/** The phones as the route stores them, or the fields that cannot be read as numbers. */\nfunction readPhones(\n drafts: PhoneDrafts,\n): { ok: true; phones: PhoneDrafts } | { ok: false; errors: Partial<PhoneDrafts> } {\n const phones = {} as PhoneDrafts\n const errors: Partial<PhoneDrafts> = {}\n for (const field of PHONE_FIELDS) {\n const typed = drafts[field].trim()\n const normalized = typed ? normalizePhone(typed) : ''\n if (normalized === null) errors[field] = PHONE_HELP_ERROR\n else phones[field] = normalized\n }\n return Object.keys(errors).length ? { ok: false, errors } : { ok: true, phones }\n}\n\nconst PHONE_HELP = 'With the country code, like +1 512 555 0107'\nconst PHONE_HELP_ERROR = 'Enter it with its country code, like +1 512 555 0107.'\n\n/** A section heading inside the card — Salesforce's Details groups its fields the same way. */\nfunction SectionHeading(props: { children: string }) {\n return (\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"subtitle2\" color=\"text.secondary\">\n {props.children}\n </Typography>\n </Grid>\n )\n}\n\nexport function ContactPropertiesCard(props: ContactPropertiesCardProps) {\n const {\n hostId,\n org,\n record,\n orgId = null,\n consentGroup,\n seed,\n members,\n suiteLocked = false,\n } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const contactUpdate = useContactUpdate(hostId)\n // The site a stage move is routed through: the mounted one, or at the\n // organization level the holder's own (AGL-2630).\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 companies = useCompanyOptions({ hostId, org })\n const createCompany = useCreateCompany({ hostId, org })\n\n const [nameOverride, setNameOverride] = useState(record.nameOverride)\n const [salutation, setSalutation] = useState(record.salutation ?? '')\n const salutations = useCrmPicklist(CRM_SALUTATION_PICKLIST, orgId)\n const [firstName, setFirstName] = useState(record.firstName ?? '')\n const [lastName, setLastName] = useState(record.lastName ?? '')\n const [phones, setPhones] = useState<PhoneDrafts>(() => phoneDraftsFrom(record))\n const [doNotCall, setDoNotCall] = useState(record.doNotCall === true)\n const [jobTitle, setJobTitle] = useState(record.jobTitle)\n const [department, setDepartment] = useState(record.department ?? '')\n const [birthdate, setBirthdate] = useState(record.birthdate ?? '')\n const [assistantName, setAssistantName] = useState(record.assistantName ?? '')\n const [reportsTo, setReportsTo] = useState(record.reportsToContactId ?? '')\n // Salesforce's Lead Source (AGL-3298), carried from the lead on conversion.\n const [leadSource, setLeadSource] = useState(record.leadSource)\n const leadSources = useLeadSourcePicklist(orgId)\n const [companyName, setCompanyName] = useState(record.companyName)\n const [companyId, setCompanyId] = useState<string | null>(\n record.companyId || null,\n )\n const [ownerUid, setOwnerUid] = useState(record.ownerUid)\n const [lifecycleStage, setLifecycleStage] = useState<ContactLifecycleStage | ''>(\n record.lifecycleStage,\n )\n const [tags, setTags] = useState(record.tags.join(', '))\n const [notes, setNotes] = useState(record.notes)\n const [address, setAddress] = useState<AddressDraft>(\n addressDraftFrom(record.address),\n )\n const [otherAddress, setOtherAddress] = useState<AddressDraft>(\n addressDraftFrom(record.otherAddress),\n )\n const [phoneErrors, setPhoneErrors] = useState<Partial<PhoneDrafts>>({})\n const [birthdateError, setBirthdateError] = useState('')\n const [saving, setSaving] = useState(false)\n /** The custom values the reader touched; every other key reads from the record. */\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /** Something was edited since the card was seeded or last saved. */\n const [edited, setEdited] = useState(false)\n const fields = useContactFieldDefinitions(suiteLocked ? null : orgId)\n // The custom fields are the CRM's; a plan without it draws and sends none.\n const customFields = suiteLocked ? [] : fields.active\n const storedCustom = useMemo(() => record.custom ?? {}, [record.custom])\n const customChanges = useMemo(\n () => crmCustomDraftChanges(storedCustom, custom),\n [storedCustom, custom],\n )\n const clearingRequired =\n crmCustomDraftMissingRequired(customFields, storedCustom, custom, 'edit').length > 0\n /*\n * The name the first and last names make (AGL-3515). While there is one,\n * it IS the name — the Name field shows it, read-only, and the save sends\n * the parts rather than a name the route would have to reconcile.\n */\n const composedName = composeContactName(firstName, lastName)\n\n /** One field's edit: the value, and the card now has something to save. */\n const change = useCallback(<T,>(set: (value: T) => void, value: T) => {\n set(value)\n setEdited(true)\n }, [])\n\n /** Every draft back to what the record holds — the seed, and Discard. */\n const reseed = () => {\n setNameOverride(record.nameOverride)\n setSalutation(record.salutation ?? '')\n setFirstName(record.firstName ?? '')\n setLastName(record.lastName ?? '')\n setPhones(phoneDraftsFrom(record))\n setDoNotCall(record.doNotCall === true)\n setJobTitle(record.jobTitle)\n setDepartment(record.department ?? '')\n setBirthdate(record.birthdate ?? '')\n setAssistantName(record.assistantName ?? '')\n setReportsTo(record.reportsToContactId ?? '')\n setLeadSource(record.leadSource)\n setCompanyName(record.companyName)\n setCompanyId(record.companyId || null)\n setOwnerUid(record.ownerUid)\n setLifecycleStage(record.lifecycleStage)\n setTags(record.tags.join(', '))\n setNotes(record.notes)\n setAddress(addressDraftFrom(record.address))\n setOtherAddress(addressDraftFrom(record.otherAddress))\n setPhoneErrors({})\n setBirthdateError('')\n setCustom({})\n setEdited(false)\n }\n\n /*\n * Re-seeded when the RECORD changes, not when it re-renders. The listener\n * delivers a fresh row on every snapshot, including the one this card's\n * own save produces; re-seeding on each would overwrite what somebody is\n * in the middle of typing with what the server last confirmed.\n */\n const recordId = record.$id\n useEffect(() => {\n reseed()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [recordId])\n\n const handleSave = useCallback(async () => {\n if (clearingRequired) return\n const readPhonesResult = readPhones(phones)\n const storedBirthdate = normalizeContactBirthdate(birthdate)\n setPhoneErrors('errors' in readPhonesResult ? readPhonesResult.errors : {})\n setBirthdateError(storedBirthdate === null ? CONTACT_BIRTHDATE_REFUSAL : '')\n if ('errors' in readPhonesResult || storedBirthdate === null) return\n // A stage that differs from the record's is the stage route's to write —\n // see the note above.\n const stageChanged = !suiteLocked && lifecycleStage !== record.lifecycleStage\n const set: ContactUpdateFields = {\n // The name is the parts' while there are parts — see the note above.\n ...(composedName ? {} : { name: nameOverride.trim().slice(0, 120) }),\n firstName: firstName.trim(),\n lastName: lastName.trim(),\n ...readPhonesResult.phones,\n // Sent only when it moves: the route stores the flag as a fact about\n // the person, and an untouched card should not re-assert it.\n ...(doNotCall !== (record.doNotCall === true) ? { doNotCall } : {}),\n jobTitle: jobTitle.trim().slice(0, 120),\n department: department.trim().slice(0, 120),\n birthdate: storedBirthdate,\n assistantName: assistantName.trim().slice(0, 120),\n // Sent only when changed: the route judges a sent value against the\n // org's list, and an untouched one needs no judging. The reports-to\n // likewise, because the route checks a sent one for a loop.\n ...(leadSource !== record.leadSource ? { leadSource } : {}),\n ...(salutation !== (record.salutation ?? '') ? { salutation } : {}),\n ...(reportsTo !== (record.reportsToContactId ?? '')\n ? { reportsToContactId: reportsTo || null }\n : {}),\n address,\n otherAddress,\n tags: parseContactTags(tags),\n notes: notes.slice(0, 2000),\n // The owner and the company, sent only on a plan that carries the CRM.\n ...(suiteLocked\n ? {}\n : { ownerUid, companyId, companyName: companyName.trim().slice(0, 120) }),\n // Only the custom keys that changed — see the note above.\n ...(!suiteLocked && customChanges.length\n ? { custom: Object.fromEntries(customChanges) }\n : {}),\n }\n setSaving(true)\n try {\n const verdict = await writeGuardedBySeed(\n {\n subject: 'contact',\n unreadable: seed.status === 'error',\n fromCache: seed.fromCache,\n },\n () => contactUpdate.updateOne(record.$id, set),\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n logActivity('Updated contact', {\n type: 'contact',\n id: record.$id,\n name: record.name || record.email,\n })\n // The custom values are stored; the controls read the record again.\n setCustom({})\n if (stageChanged && siteHostId) {\n try {\n await setContactStage(user, siteHostId, record.$id, lifecycleStage || null)\n } catch (error) {\n // The rest of the profile is saved; only the stage was refused, and\n // the route's own sentence says why.\n return void enqueueSnackbar(\n `Saved, but the stage could not be changed: ${\n error instanceof Error ? error.message : 'the request failed'\n }`,\n { variant: 'warning', persist: false },\n )\n }\n }\n setEdited(false)\n enqueueSnackbar('Contact saved', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n } finally {\n setSaving(false)\n }\n }, [\n address,\n assistantName,\n birthdate,\n clearingRequired,\n companyId,\n companyName,\n composedName,\n contactUpdate,\n customChanges,\n department,\n doNotCall,\n enqueueSnackbar,\n firstName,\n siteHostId,\n jobTitle,\n lastName,\n leadSource,\n lifecycleStage,\n logActivity,\n nameOverride,\n notes,\n otherAddress,\n ownerUid,\n phones,\n record.$id,\n record.doNotCall,\n record.email,\n record.leadSource,\n record.lifecycleStage,\n record.name,\n record.reportsToContactId,\n record.salutation,\n reportsTo,\n salutation,\n seed.fromCache,\n seed.status,\n suiteLocked,\n tags,\n user,\n ])\n\n /*\n * An owner the roster no longer lists — somebody who left the team — is\n * still offered as the current value, named by their uid, so the select is\n * never handed a value it has no option for and the reader can see who it\n * was before reassigning.\n */\n const ownerKnown = members.options.some((option) => option.uid === ownerUid)\n\n /** One phone field, with click-to-call off what is IN the box (AGL-2661). */\n const phoneField = (field: PhoneField) => {\n const value = phones[field]\n const telHref = crmTelHref(value)\n const callLabel = doNotCall\n ? `Call ${value.trim()} — marked do not call`\n : `Call ${value.trim()}`\n return (\n <Grid key={field} size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label={CONTACT_PHONE_FIELD_LABELS[field]}\n value={value}\n onChange={(event) => change(setPhones, { ...phones, [field]: event.target.value })}\n error={Boolean(phoneErrors[field])}\n helperText={phoneErrors[field] || (field === 'phone' ? PHONE_HELP : undefined)}\n fullWidth\n slotProps={{\n input: {\n /*\n * The number on screen is the one a reader means to ring, and\n * `crmTelHref` withholds the link from anything half-typed. A\n * do-not-call person keeps the link — the rep decides — with\n * the request said in the tooltip and the icon's color.\n */\n endAdornment:\n telHref && field !== 'fax' ? (\n <InputAdornment position=\"end\">\n <Tooltip title={callLabel}>\n <IconButton\n component=\"a\"\n href={telHref}\n size=\"small\"\n edge=\"end\"\n color={doNotCall ? 'warning' : 'default'}\n aria-label={callLabel}\n >\n <MdiIcon path={mdiPhoneOutline.path} size={0.8} />\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ) : null,\n },\n }}\n />\n </Grid>\n )\n }\n\n return (\n <CardDisplay\n header={'Properties'}\n help={Aglyn.pluginDocsHelp('contactRecord', { anchor: '#contact-properties' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n {edited ? (\n <Button size=\"small\" disabled={saving} onClick={reseed}>\n {'Discard changes'}\n </Button>\n ) : null}\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={saving || !edited || clearingRequired}\n onClick={() => void handleSave()}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n ),\n }}\n >\n <Grid container spacing={2}>\n <SectionHeading>{'Contact information'}</SectionHeading>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Email\"\n value={record.email}\n slotProps={{ input: { readOnly: true } }}\n helperText=\"The identity every site shares — it cannot be edited here.\"\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <CrmPicklistSelect\n picklistId={CRM_SALUTATION_PICKLIST}\n picklist={salutations.picklist}\n value={salutation}\n stored={record.salutation ?? ''}\n onChange={(value) => change(setSalutation, value)}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"First name\"\n value={firstName}\n onChange={(event) => change(setFirstName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: CONTACT_NAME_PART_MAX } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Last name\"\n value={lastName}\n onChange={(event) => change(setLastName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: CONTACT_NAME_PART_MAX } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Name\"\n value={composedName || nameOverride}\n onChange={(event) => change(setNameOverride, event.target.value)}\n slotProps={{\n htmlInput: { maxLength: 120 },\n input: { readOnly: Boolean(composedName) },\n }}\n helperText={\n composedName\n ? 'Made of the first and last name.'\n : record.canonicalName\n ? `Your own name for this person. Blank shows the name they gave: ${record.canonicalName}.`\n : 'Your own name for this person.'\n }\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={jobTitle}\n onChange={(event) => change(setJobTitle, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Department\"\n value={department}\n onChange={(event) => change(setDepartment, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <CompanyPicker\n options={companies.options}\n ready={companies.ready}\n truncated={companies.truncated}\n value={companyId}\n onChange={(id, company) => {\n change(setCompanyId, id)\n // The label follows the link: the picked name, or nothing\n // once the link is cleared. A company the list cannot name\n // keeps whatever label the record already had.\n setCompanyName(company ? company.name : id ? companyName : '')\n }}\n onCreate={createCompany}\n email={record.email}\n fallbackName={companyName}\n disabled={saving || suiteLocked}\n helperText={suiteLocked ? crmSuiteLockedReason() : undefined}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <LeadSourceSelect\n picklist={leadSources.picklist}\n value={leadSource}\n stored={record.leadSource}\n onChange={(value) => change(setLeadSource, value)}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n select\n size=\"small\"\n label=\"Lifecycle stage\"\n value={lifecycleStage}\n onChange={(event) =>\n change(setLifecycleStage, event.target.value as ContactLifecycleStage | '')\n }\n disabled={suiteLocked}\n helperText={suiteLocked ? crmSuiteLockedReason() : undefined}\n fullWidth\n >\n <MenuItem value=\"\">{'Not placed yet'}</MenuItem>\n {CONTACT_LIFECYCLE_STAGES.map((stage) => (\n <MenuItem key={stage} value={stage}>\n {CONTACT_LIFECYCLE_STAGE_LABELS[stage]}\n </MenuItem>\n ))}\n </TextField>\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={ownerUid}\n onChange={(event) => change(setOwnerUid, event.target.value)}\n disabled={suiteLocked}\n helperText={\n suiteLocked\n ? crmSuiteLockedReason()\n : members.ready && !members.options.length\n ? 'The team roster could not be read, so nobody can be picked yet.'\n : 'The team member responsible for this relationship'\n }\n fullWidth\n >\n <MenuItem value=\"\">{'Unassigned'}</MenuItem>\n {ownerUid && !ownerKnown ? (\n <MenuItem value={ownerUid}>{members.memberName(ownerUid)}</MenuItem>\n ) : null}\n {members.options.map((owner) => (\n <MenuItem key={owner.uid} value={owner.uid}>\n {Aglyn.crmMemberPickerLabel(owner)}\n </MenuItem>\n ))}\n </TextField>\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"vip, beta\"\n helperText=\"Comma-separated\"\n value={tags}\n onChange={(event) => change(setTags, event.target.value)}\n fullWidth\n />\n </Grid>\n\n <SectionHeading>{'Phones'}</SectionHeading>\n {PHONE_FIELDS.filter((field) => field !== 'assistantPhone').map(phoneField)}\n <Grid size={{ xs: 12, sm: 6 }}>\n <FormControlLabel\n control={\n <Checkbox\n checked={doNotCall}\n onChange={(event) => change(setDoNotCall, event.target.checked)}\n />\n }\n label=\"Do not call\"\n />\n <FormHelperText>\n {'They asked not to be phoned. Every number still dials; the Call button says so.'}\n </FormHelperText>\n </Grid>\n\n <SectionHeading>{'Addresses'}</SectionHeading>\n <Grid size={{ xs: 12 }}>\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Mailing address'}</Typography>\n <ContactAddressFields\n value={address}\n onChange={(value) => change(setAddress, value)}\n />\n </Stack>\n </Grid>\n <Grid size={{ xs: 12 }}>\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Other address'}</Typography>\n <ContactAddressFields\n value={otherAddress}\n onChange={(value) => change(setOtherAddress, value)}\n />\n </Stack>\n </Grid>\n\n <SectionHeading>{'Additional information'}</SectionHeading>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"Birthdate\"\n value={birthdate}\n onChange={(event) => change(setBirthdate, event.target.value)}\n error={Boolean(birthdateError)}\n helperText={birthdateError || undefined}\n slotProps={{ inputLabel: { shrink: true } }}\n fullWidth\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <ContactReportsToField\n hostId={hostId}\n org={org}\n contactId={record.$id}\n groupId={consentGroup.groupId}\n value={reportsTo}\n onChange={(value) => change(setReportsTo, value)}\n disabled={saving}\n />\n </Grid>\n <Grid size={{ xs: 12, sm: 6 }}>\n <TextField\n size=\"small\"\n label=\"Assistant\"\n value={assistantName}\n onChange={(event) => change(setAssistantName, event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n fullWidth\n />\n </Grid>\n {phoneField('assistantPhone')}\n <Grid size={{ xs: 12 }}>\n <TextField\n size=\"small\"\n label=\"About\"\n value={notes}\n onChange={(event) => change(setNotes, event.target.value)}\n helperText=\"Notes for your team. Nobody outside this site's group can read them.\"\n multiline\n minRows={3}\n fullWidth\n />\n </Grid>\n {/*\n The org's own fields (AGL-2601), after the built-in ones and saved\n with them. Absent, not an empty box, while the org defines none.\n */}\n {customFields.length ? (\n <>\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"subtitle2\">{'More fields'}</Typography>\n </Grid>\n {customFields.map((definition) => (\n <Grid key={definition.$id} size={{ xs: 12, sm: 6 }}>\n <CrmCustomFieldControl\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n change(setCustom, { ...custom, [definition.key]: value })\n }\n disabled={saving}\n />\n </Grid>\n ))}\n {clearingRequired ? (\n <Grid size={{ xs: 12 }}>\n <Typography variant=\"caption\" color=\"error\">\n {'A required field cannot be left empty.'}\n </Typography>\n </Grid>\n ) : null}\n </>\n ) : null}\n </Grid>\n </CardDisplay>\n )\n}\nContactPropertiesCard.displayName = 'ContactPropertiesCard'\n\nexport default ContactPropertiesCard\n"],"names":["Aglyn","composeContactName","CONTACT_BIRTHDATE_REFUSAL","CONTACT_EXTRA_PHONE_FIELDS","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_LIFECYCLE_STAGES","CONTACT_NAME_PART_MAX","CONTACT_PHONE_FIELD_LABELS","CRM_SALUTATION_PICKLIST","crmTelHref","normalizeContactBirthdate","normalizePhone","mdiPhoneOutline","CardDisplay","MdiIcon","useSnackbar","useUser","writeGuardedBySeed","Button","Checkbox","FormControlLabel","FormHelperText","Grid","IconButton","InputAdornment","MenuItem","Stack","TextField","Tooltip","Typography","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useContactUpdate","useCrmActivityLogger","useCrmPicklist","useLeadSourcePicklist","parseContactTags","setContactStage","crmCustomDraftChanges","crmCustomDraftMissingRequired","crmCustomDraftValue","CompanyPicker","useCompanyOptions","useCreateCompany","addressDraftFrom","ContactAddressFields","ContactReportsToField","CrmCustomFieldControl","CrmPicklistSelect","crmSuiteLockedReason","LeadSourceSelect","PHONE_FIELDS","phoneDraftsFrom","record","Object","fromEntries","map","field","String","readPhones","drafts","phones","errors","typed","trim","normalized","PHONE_HELP_ERROR","keys","length","ok","PHONE_HELP","SectionHeading","props","size","xs","variant","color","children","ContactPropertiesCard","hostId","org","orgId","consentGroup","seed","members","suiteLocked","data","user","enqueueSnackbar","contactUpdate","siteHostId","logActivity","companies","createCompany","nameOverride","setNameOverride","salutation","setSalutation","salutations","firstName","setFirstName","lastName","setLastName","setPhones","doNotCall","setDoNotCall","jobTitle","setJobTitle","department","setDepartment","birthdate","setBirthdate","assistantName","setAssistantName","reportsTo","setReportsTo","reportsToContactId","leadSource","setLeadSource","leadSources","companyName","setCompanyName","companyId","setCompanyId","ownerUid","setOwnerUid","lifecycleStage","setLifecycleStage","tags","setTags","join","notes","setNotes","address","setAddress","otherAddress","setOtherAddress","phoneErrors","setPhoneErrors","birthdateError","setBirthdateError","saving","setSaving","custom","setCustom","edited","setEdited","fields","customFields","active","storedCustom","customChanges","clearingRequired","composedName","change","set","value","reseed","recordId","$id","handleSave","readPhonesResult","storedBirthdate","stageChanged","name","slice","verdict","subject","unreadable","status","fromCache","updateOne","message","persist","type","id","email","error","Error","console","allowDuplicate","ownerKnown","options","some","option","uid","phoneField","telHref","callLabel","sm","label","onChange","event","target","Boolean","helperText","undefined","fullWidth","slotProps","input","endAdornment","position","title","component","href","edge","aria-label","path","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","disabled","onClick","container","readOnly","picklistId","picklist","stored","htmlInput","maxLength","canonicalName","ready","truncated","company","onCreate","fallbackName","select","stage","memberName","owner","crmMemberPickerLabel","placeholder","filter","control","checked","inputLabel","shrink","contactId","groupId","multiline","minRows","definition","key","displayName"],"mappings":"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"}
@@ -73,7 +73,7 @@ function Row(props) {
73
73
  return /*#__PURE__*/ _jsx(CardDisplay, {
74
74
  header: 'Properties',
75
75
  help: pluginDocsHelp('deals', {
76
- anchor: '#a-deals-page'
76
+ anchor: '#deal-properties'
77
77
  }),
78
78
  contentGutterX: true,
79
79
  contentGutterY: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ContactFieldDefinition,\n CRM_FORECAST_CATEGORY_LABELS,\n dealForecastCategory,\n dealProbability,\n dealStageById,\n pluginDocsHelp,\n readDealProbability,\n weightedDealAmountCents,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Link, Stack, Typography } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport { formatContactCustomValue } from './contact-custom-columns'\nimport {\n type DealDoc,\n formatMoney,\n type PipelineDoc,\n timestampMs,\n} from '../model/deal-board-model'\nimport type { CrmRoutes } from '../model/crm-routes'\n\nexport interface DealPropertiesCardProps {\n deal: DealDoc\n pipeline: PipelineDoc | null\n ownerLabel: string\n routes: CrmRoutes\n /**\n * The org's ACTIVE deal field definitions (AGL-2661), one row each under\n * the fixed properties. Handed in by the page, which resolves the org\n * once for every card on it.\n */\n customFields?: readonly ContactFieldDefinition[]\n /** The deal's campaign by name, resolved by the page; absent when it names none. */\n campaignName?: string\n}\n\nfunction Row(props: { label: string; children: ReactNode }) {\n return (\n <Stack direction=\"row\" spacing={2} sx={{ alignItems: 'baseline' }}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ width: 120, flexShrink: 0 }}\n >\n {props.label}\n </Typography>\n <Typography variant=\"body2\" component=\"div\" sx={{ flex: 1, minWidth: 0 }}>\n {props.children}\n </Typography>\n </Stack>\n )\n}\n\n/**\n * What the deal is worth and who it is with (AGL-2598).\n *\n * The weighted value is computed here from the live pipeline rather than\n * stored, because the stage's probability is the merchant's to change and\n * a stored figure would go stale the moment they did. The contact and the\n * company are links into their own pages — the id is the link; the name on\n * the deal is a caption that may lag a rename.\n *\n * Salesforce's Opportunity fields (AGL-3516) follow: the Type, the Lead\n * source, the Next step, the probability — the deal's own override, with\n * its stage's beside it, or the stage's — the forecast category and the\n * campaign. Edit changes them; a stage move re-stamps the last two.\n */\nexport function DealPropertiesCard(props: DealPropertiesCardProps) {\n const { deal, pipeline, ownerLabel, routes, customFields = [], campaignName } = props\n const stage = dealStageById(pipeline, deal.stageId)\n const weighted = weightedDealAmountCents(deal, stage)\n const probability = dealProbability(deal, stage)\n const overridden = typeof readDealProbability(deal.probability) === 'number'\n const createdMs = timestampMs(deal.createdAt)\n return (\n <CardDisplay\n header={'Properties'}\n help={pluginDocsHelp('deals', { anchor: '#a-deals-page' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.25}>\n <Row label=\"Amount\">\n {typeof deal.amountCents === 'number'\n ? formatMoney(deal.amountCents, deal.currency)\n : 'Not set'}\n </Row>\n <Row label=\"Weighted value\">\n {`${formatMoney(weighted, deal.currency)}` +\n (deal.status === 'open' && stage && probability !== null ? ` at ${probability}%` : '')}\n </Row>\n <Row label=\"Probability\">\n {probability === null\n ? 'Not set'\n : overridden && stage\n ? `${probability}% · from stage: ${stage.probability}%`\n : `${probability}%`}\n </Row>\n <Row label=\"Forecast category\">\n {CRM_FORECAST_CATEGORY_LABELS[dealForecastCategory(deal, stage)]}\n </Row>\n <Row label=\"Expected close\">\n {typeof deal.expectedCloseAtMs === 'number'\n ? new Date(deal.expectedCloseAtMs).toLocaleDateString()\n : 'Not set'}\n </Row>\n <Row label=\"Type\">{deal.type || 'Not set'}</Row>\n <Row label=\"Lead source\">{deal.leadSource || 'Not set'}</Row>\n <Row label=\"Next step\">{deal.nextStep || 'Not set'}</Row>\n <Row label=\"Owner\">{ownerLabel || 'Nobody yet'}</Row>\n <Row label=\"Contact\">\n {deal.contactId ? (\n <AppLink href={routes.contact(deal.contactId)}>\n {deal.contactName || 'Open contact'}\n </AppLink>\n ) : (\n 'None'\n )}\n </Row>\n <Row label=\"Company\">\n {deal.companyId ? (\n <AppLink href={routes.company(deal.companyId)}>\n {deal.companyName || 'Open company'}\n </AppLink>\n ) : (\n 'None'\n )}\n </Row>\n <Row label=\"Campaign\">{campaignName || 'None'}</Row>\n <Row label=\"Pipeline\">{pipeline?.name ?? deal.pipelineId}</Row>\n <Row label=\"Created\">\n {createdMs ? new Date(createdMs).toLocaleDateString() : '—'}\n </Row>\n {deal.notes ? (\n <Row label=\"Notes\">\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {deal.notes}\n </Typography>\n </Row>\n ) : null}\n {/*\n The org's own deal fields (AGL-2661), after the built-in ones under\n \"More fields\", as a record's Details keep them; none defined, none\n drawn.\n */}\n {customFields.length ? (\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'More fields'}\n </Typography>\n ) : null}\n {customFields.map((definition) => {\n const text = formatContactCustomValue(definition, deal.custom?.[definition.key])\n return (\n <Row key={definition.key} label={definition.label || definition.key}>\n {text && definition.type === 'url' ? (\n <Link href={text} target=\"_blank\" rel=\"noreferrer noopener\">\n {text}\n </Link>\n ) : (\n text || 'Not set'\n )}\n </Row>\n )\n })}\n </Stack>\n </CardDisplay>\n )\n}\nDealPropertiesCard.displayName = 'DealPropertiesCard'\n\nexport default DealPropertiesCard\n"],"names":["CRM_FORECAST_CATEGORY_LABELS","dealForecastCategory","dealProbability","dealStageById","pluginDocsHelp","readDealProbability","weightedDealAmountCents","AppLink","CardDisplay","Link","Stack","Typography","formatContactCustomValue","formatMoney","timestampMs","Row","props","direction","spacing","sx","alignItems","variant","color","width","flexShrink","label","component","flex","minWidth","children","DealPropertiesCard","deal","pipeline","ownerLabel","routes","customFields","campaignName","stage","stageId","weighted","probability","overridden","createdMs","createdAt","header","help","anchor","contentGutterX","contentGutterY","amountCents","currency","status","expectedCloseAtMs","Date","toLocaleDateString","type","leadSource","nextStep","contactId","href","contact","contactName","companyId","company","companyName","name","pipelineId","notes","whiteSpace","length","pt","map","definition","text","custom","key","target","rel","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-properties-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ContactFieldDefinition,\n CRM_FORECAST_CATEGORY_LABELS,\n dealForecastCategory,\n dealProbability,\n dealStageById,\n pluginDocsHelp,\n readDealProbability,\n weightedDealAmountCents,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Link, Stack, Typography } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport { formatContactCustomValue } from './contact-custom-columns'\nimport {\n type DealDoc,\n formatMoney,\n type PipelineDoc,\n timestampMs,\n} from '../model/deal-board-model'\nimport type { CrmRoutes } from '../model/crm-routes'\n\nexport interface DealPropertiesCardProps {\n deal: DealDoc\n pipeline: PipelineDoc | null\n ownerLabel: string\n routes: CrmRoutes\n /**\n * The org's ACTIVE deal field definitions (AGL-2661), one row each under\n * the fixed properties. Handed in by the page, which resolves the org\n * once for every card on it.\n */\n customFields?: readonly ContactFieldDefinition[]\n /** The deal's campaign by name, resolved by the page; absent when it names none. */\n campaignName?: string\n}\n\nfunction Row(props: { label: string; children: ReactNode }) {\n return (\n <Stack direction=\"row\" spacing={2} sx={{ alignItems: 'baseline' }}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ width: 120, flexShrink: 0 }}\n >\n {props.label}\n </Typography>\n <Typography variant=\"body2\" component=\"div\" sx={{ flex: 1, minWidth: 0 }}>\n {props.children}\n </Typography>\n </Stack>\n )\n}\n\n/**\n * What the deal is worth and who it is with (AGL-2598).\n *\n * The weighted value is computed here from the live pipeline rather than\n * stored, because the stage's probability is the merchant's to change and\n * a stored figure would go stale the moment they did. The contact and the\n * company are links into their own pages — the id is the link; the name on\n * the deal is a caption that may lag a rename.\n *\n * Salesforce's Opportunity fields (AGL-3516) follow: the Type, the Lead\n * source, the Next step, the probability — the deal's own override, with\n * its stage's beside it, or the stage's — the forecast category and the\n * campaign. Edit changes them; a stage move re-stamps the last two.\n */\nexport function DealPropertiesCard(props: DealPropertiesCardProps) {\n const { deal, pipeline, ownerLabel, routes, customFields = [], campaignName } = props\n const stage = dealStageById(pipeline, deal.stageId)\n const weighted = weightedDealAmountCents(deal, stage)\n const probability = dealProbability(deal, stage)\n const overridden = typeof readDealProbability(deal.probability) === 'number'\n const createdMs = timestampMs(deal.createdAt)\n return (\n <CardDisplay\n header={'Properties'}\n help={pluginDocsHelp('deals', { anchor: '#deal-properties' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.25}>\n <Row label=\"Amount\">\n {typeof deal.amountCents === 'number'\n ? formatMoney(deal.amountCents, deal.currency)\n : 'Not set'}\n </Row>\n <Row label=\"Weighted value\">\n {`${formatMoney(weighted, deal.currency)}` +\n (deal.status === 'open' && stage && probability !== null ? ` at ${probability}%` : '')}\n </Row>\n <Row label=\"Probability\">\n {probability === null\n ? 'Not set'\n : overridden && stage\n ? `${probability}% · from stage: ${stage.probability}%`\n : `${probability}%`}\n </Row>\n <Row label=\"Forecast category\">\n {CRM_FORECAST_CATEGORY_LABELS[dealForecastCategory(deal, stage)]}\n </Row>\n <Row label=\"Expected close\">\n {typeof deal.expectedCloseAtMs === 'number'\n ? new Date(deal.expectedCloseAtMs).toLocaleDateString()\n : 'Not set'}\n </Row>\n <Row label=\"Type\">{deal.type || 'Not set'}</Row>\n <Row label=\"Lead source\">{deal.leadSource || 'Not set'}</Row>\n <Row label=\"Next step\">{deal.nextStep || 'Not set'}</Row>\n <Row label=\"Owner\">{ownerLabel || 'Nobody yet'}</Row>\n <Row label=\"Contact\">\n {deal.contactId ? (\n <AppLink href={routes.contact(deal.contactId)}>\n {deal.contactName || 'Open contact'}\n </AppLink>\n ) : (\n 'None'\n )}\n </Row>\n <Row label=\"Company\">\n {deal.companyId ? (\n <AppLink href={routes.company(deal.companyId)}>\n {deal.companyName || 'Open company'}\n </AppLink>\n ) : (\n 'None'\n )}\n </Row>\n <Row label=\"Campaign\">{campaignName || 'None'}</Row>\n <Row label=\"Pipeline\">{pipeline?.name ?? deal.pipelineId}</Row>\n <Row label=\"Created\">\n {createdMs ? new Date(createdMs).toLocaleDateString() : '—'}\n </Row>\n {deal.notes ? (\n <Row label=\"Notes\">\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {deal.notes}\n </Typography>\n </Row>\n ) : null}\n {/*\n The org's own deal fields (AGL-2661), after the built-in ones under\n \"More fields\", as a record's Details keep them; none defined, none\n drawn.\n */}\n {customFields.length ? (\n <Typography variant=\"subtitle2\" sx={{ pt: 1 }}>\n {'More fields'}\n </Typography>\n ) : null}\n {customFields.map((definition) => {\n const text = formatContactCustomValue(definition, deal.custom?.[definition.key])\n return (\n <Row key={definition.key} label={definition.label || definition.key}>\n {text && definition.type === 'url' ? (\n <Link href={text} target=\"_blank\" rel=\"noreferrer noopener\">\n {text}\n </Link>\n ) : (\n text || 'Not set'\n )}\n </Row>\n )\n })}\n </Stack>\n </CardDisplay>\n )\n}\nDealPropertiesCard.displayName = 'DealPropertiesCard'\n\nexport default DealPropertiesCard\n"],"names":["CRM_FORECAST_CATEGORY_LABELS","dealForecastCategory","dealProbability","dealStageById","pluginDocsHelp","readDealProbability","weightedDealAmountCents","AppLink","CardDisplay","Link","Stack","Typography","formatContactCustomValue","formatMoney","timestampMs","Row","props","direction","spacing","sx","alignItems","variant","color","width","flexShrink","label","component","flex","minWidth","children","DealPropertiesCard","deal","pipeline","ownerLabel","routes","customFields","campaignName","stage","stageId","weighted","probability","overridden","createdMs","createdAt","header","help","anchor","contentGutterX","contentGutterY","amountCents","currency","status","expectedCloseAtMs","Date","toLocaleDateString","type","leadSource","nextStep","contactId","href","contact","contactName","companyId","company","companyName","name","pipelineId","notes","whiteSpace","length","pt","map","definition","text","custom","key","target","rel","displayName"],"mappings":"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"}
@@ -140,7 +140,7 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
140
140
  return /*#__PURE__*/ _jsx(CardDisplay, {
141
141
  header: 'Campaigns',
142
142
  help: pluginDocsHelp('crmLeads', {
143
- anchor: '#a-leads-page'
143
+ anchor: '#lead-campaigns'
144
144
  }),
145
145
  contentGutterX: true,
146
146
  contentGutterY: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-campaigns-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n containerMembershipUnchanged,\n containerMembershipValue,\n pluginDocsHelp,\n readContainerIds,\n} from '@aglyn/aglyn'\nimport ContainerPicker, {\n type ContainerPickerOption,\n} from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Stack } from '@mui/material'\nimport { doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The list field a campaign filing moves (AGL-3321). */\nconst LEAD_CAMPAIGN_LIST_FIELDS = ['scopedCampaignIds'] as const\n\n/** The helper line under the picker — the contact card's, word for word. */\nexport const LEAD_CAMPAIGNS_HELPER_TEXT =\n 'Your own filing. It never adds anyone to a send — a campaign mails its lists.'\n\n/**\n * The campaigns a lead is filed under, as NAMES for the record header.\n *\n * Resolved from the site's containers; an id with no live container — a\n * campaign the console deleted, or one still loading — is left out rather\n * than shown, because a document id on a chip tells a reader nothing and\n * looks like a fault. The picker below keeps such an id visible as itself,\n * so the filing is never silently lost; the header only ever names.\n */\nexport function leadCampaignNames(\n lead: Record<string, unknown> | null | undefined,\n options: readonly ContainerPickerOption[],\n): string[] {\n const labels = new Map(options.map((option) => [option.value, option.label]))\n return readContainerIds(lead, 'campaign')\n .map((id) => labels.get(id))\n .filter((name): name is string => Boolean(name))\n}\n\nexport interface LeadCampaignsCardProps {\n /**\n * The lead's own site, or `null` at the organization level for a lead no\n * site captured (AGL-3278). Read for the org root alone — the containers\n * themselves are handed down as `options` — so the card serves both\n * levels; the org-level mount answers the same org without a site.\n */\n hostId: string | null\n leadId: string\n lead: Record<string, unknown>\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n /** The site's campaigns, read once by the page for this card and the header. */\n options: readonly ContainerPickerOption[]\n /** The campaign read has answered — false while it settles. */\n optionsReady: boolean\n /**\n * What a saved filing changed, once it has landed: the ids added to and\n * removed from the lead's membership. The page files the activity\n * entries (AGL-3274); the card only knows the document.\n */\n onFiled?: (change: { added: string[]; removed: string[] }) => void\n}\n\n/**\n * WHICH CAMPAIGNS THIS LEAD IS FILED UNDER (AGL-3274).\n *\n * The contact's Relationship card has carried this picker since AGL-2596;\n * the lead's page had none, so a lead filed under a campaign by the New\n * lead drawer, the bulk bar or a sequence's enroll showed nothing of it —\n * the one field on the record the reader had no way to see or change.\n *\n * The same control, the same value helper, the same helper line. The save\n * is the lead's own client door — a one-field `update` on\n * `hosts/{hostId}/leads`, as the properties card writes status and notes —\n * guarded by the seed like every draft edited over a cached read, and\n * `containerMembershipUnchanged` keeps Save quiet while the picker only\n * reorders what is stored. **Save filing** sits in the card header.\n *\n * It is FILING, not audience: the helper says so, in the sentence the\n * contact card uses, because a reader who finds a campaign picker on a\n * person's page assumes the opposite.\n */\nexport function LeadCampaignsCard(props: LeadCampaignsCardProps) {\n const { hostId, leadId, lead, leadStatus, fromCache, options, optionsReady, onFiled } = props\n // The lead's collection is the org's now (AGL-3275); the site still names\n // the surface, and the scope hook resolves the org from it.\n const { orgId } = useCrmScope({ hostId })\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const stored = readContainerIds(lead, 'campaign')\n const [selected, setSelected] = useState<string[]>(stored)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n // A newer membership from the server replaces an UNEDITED selection — a\n // sequence's enroll or a bulk add lands while the page is open — and an\n // edited one is the reader's, decided by the guard on save.\n const storedKey = stored.join(',')\n useEffect(() => {\n if (!dirty) setSelected(stored)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storedKey, dirty])\n\n const unchanged = containerMembershipUnchanged(stored, selected)\n const empty = optionsReady && !options.length\n\n const save = async () => {\n if (unchanged) return\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n setSaving(true)\n const next = containerMembershipValue(selected)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), {\n campaignIds: next,\n // The Leads list's Campaign filter under a site reads these\n // (AGL-3321), from the lead with its new campaigns.\n ...crmClientListFields('leads', lead, { campaignIds: next }, LEAD_CAMPAIGN_LIST_FIELDS),\n updatedAt: serverTimestamp(),\n })\n },\n )\n setSaving(false)\n // A rule may share by campaign (AGL-3336).\n if (verdict.ok) followUpSharing('leads', [leadId])\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The filing could not be saved.', { variant: 'warning' })\n return\n }\n setDirty(false)\n enqueueSnackbar('Filing saved', { variant: 'success', persist: false })\n onFiled?.({\n added: next.filter((id) => !stored.includes(id)),\n removed: stored.filter((id) => !next.includes(id)),\n })\n }\n\n return (\n <CardDisplay\n header={'Campaigns'}\n help={pluginDocsHelp('crmLeads', { anchor: '#a-leads-page' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: empty ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={saving || unchanged}\n onClick={() => void save().catch((error: unknown) => {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n setSaving(false)\n })}\n >\n {saving ? 'Saving…' : 'Save filing'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <ContainerPicker\n kind=\"campaign\"\n options={options}\n value={selected}\n onChange={(next) => {\n setSelected(next)\n setDirty(true)\n }}\n label=\"Filed under campaigns\"\n helperText={LEAD_CAMPAIGNS_HELPER_TEXT}\n disabled={saving}\n empty={empty}\n emptyText=\"There are no campaigns to file this lead under yet. Create one from Marketing.\"\n />\n </Stack>\n </CardDisplay>\n )\n}\nLeadCampaignsCard.displayName = 'LeadCampaignsCard'\n\nexport default LeadCampaignsCard\n"],"names":["containerMembershipUnchanged","containerMembershipValue","pluginDocsHelp","readContainerIds","ContainerPicker","CardDisplay","useSnackbar","useFirestore","writeGuardedBySeed","Button","Stack","doc","serverTimestamp","updateDoc","useEffect","useState","useCrmScope","crmClientListFields","useCrmSharingFollowUp","LEAD_CAMPAIGN_LIST_FIELDS","LEAD_CAMPAIGNS_HELPER_TEXT","leadCampaignNames","lead","options","labels","Map","map","option","value","label","id","get","filter","name","Boolean","LeadCampaignsCard","props","hostId","leadId","leadStatus","fromCache","optionsReady","onFiled","orgId","followUpSharing","firestore","enqueueSnackbar","stored","selected","setSelected","dirty","setDirty","saving","setSaving","storedKey","join","unchanged","empty","length","save","variant","persist","next","verdict","subject","unreadable","campaignIds","updatedAt","ok","message","added","includes","removed","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","disabled","onClick","catch","error","console","Error","allowDuplicate","spacing","kind","onChange","helperText","emptyText","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-campaigns-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n containerMembershipUnchanged,\n containerMembershipValue,\n pluginDocsHelp,\n readContainerIds,\n} from '@aglyn/aglyn'\nimport ContainerPicker, {\n type ContainerPickerOption,\n} from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Stack } from '@mui/material'\nimport { doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The list field a campaign filing moves (AGL-3321). */\nconst LEAD_CAMPAIGN_LIST_FIELDS = ['scopedCampaignIds'] as const\n\n/** The helper line under the picker — the contact card's, word for word. */\nexport const LEAD_CAMPAIGNS_HELPER_TEXT =\n 'Your own filing. It never adds anyone to a send — a campaign mails its lists.'\n\n/**\n * The campaigns a lead is filed under, as NAMES for the record header.\n *\n * Resolved from the site's containers; an id with no live container — a\n * campaign the console deleted, or one still loading — is left out rather\n * than shown, because a document id on a chip tells a reader nothing and\n * looks like a fault. The picker below keeps such an id visible as itself,\n * so the filing is never silently lost; the header only ever names.\n */\nexport function leadCampaignNames(\n lead: Record<string, unknown> | null | undefined,\n options: readonly ContainerPickerOption[],\n): string[] {\n const labels = new Map(options.map((option) => [option.value, option.label]))\n return readContainerIds(lead, 'campaign')\n .map((id) => labels.get(id))\n .filter((name): name is string => Boolean(name))\n}\n\nexport interface LeadCampaignsCardProps {\n /**\n * The lead's own site, or `null` at the organization level for a lead no\n * site captured (AGL-3278). Read for the org root alone — the containers\n * themselves are handed down as `options` — so the card serves both\n * levels; the org-level mount answers the same org without a site.\n */\n hostId: string | null\n leadId: string\n lead: Record<string, unknown>\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n /** The site's campaigns, read once by the page for this card and the header. */\n options: readonly ContainerPickerOption[]\n /** The campaign read has answered — false while it settles. */\n optionsReady: boolean\n /**\n * What a saved filing changed, once it has landed: the ids added to and\n * removed from the lead's membership. The page files the activity\n * entries (AGL-3274); the card only knows the document.\n */\n onFiled?: (change: { added: string[]; removed: string[] }) => void\n}\n\n/**\n * WHICH CAMPAIGNS THIS LEAD IS FILED UNDER (AGL-3274).\n *\n * The contact's Relationship card has carried this picker since AGL-2596;\n * the lead's page had none, so a lead filed under a campaign by the New\n * lead drawer, the bulk bar or a sequence's enroll showed nothing of it —\n * the one field on the record the reader had no way to see or change.\n *\n * The same control, the same value helper, the same helper line. The save\n * is the lead's own client door — a one-field `update` on\n * `hosts/{hostId}/leads`, as the properties card writes status and notes —\n * guarded by the seed like every draft edited over a cached read, and\n * `containerMembershipUnchanged` keeps Save quiet while the picker only\n * reorders what is stored. **Save filing** sits in the card header.\n *\n * It is FILING, not audience: the helper says so, in the sentence the\n * contact card uses, because a reader who finds a campaign picker on a\n * person's page assumes the opposite.\n */\nexport function LeadCampaignsCard(props: LeadCampaignsCardProps) {\n const { hostId, leadId, lead, leadStatus, fromCache, options, optionsReady, onFiled } = props\n // The lead's collection is the org's now (AGL-3275); the site still names\n // the surface, and the scope hook resolves the org from it.\n const { orgId } = useCrmScope({ hostId })\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const stored = readContainerIds(lead, 'campaign')\n const [selected, setSelected] = useState<string[]>(stored)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n // A newer membership from the server replaces an UNEDITED selection — a\n // sequence's enroll or a bulk add lands while the page is open — and an\n // edited one is the reader's, decided by the guard on save.\n const storedKey = stored.join(',')\n useEffect(() => {\n if (!dirty) setSelected(stored)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storedKey, dirty])\n\n const unchanged = containerMembershipUnchanged(stored, selected)\n const empty = optionsReady && !options.length\n\n const save = async () => {\n if (unchanged) return\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n setSaving(true)\n const next = containerMembershipValue(selected)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), {\n campaignIds: next,\n // The Leads list's Campaign filter under a site reads these\n // (AGL-3321), from the lead with its new campaigns.\n ...crmClientListFields('leads', lead, { campaignIds: next }, LEAD_CAMPAIGN_LIST_FIELDS),\n updatedAt: serverTimestamp(),\n })\n },\n )\n setSaving(false)\n // A rule may share by campaign (AGL-3336).\n if (verdict.ok) followUpSharing('leads', [leadId])\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The filing could not be saved.', { variant: 'warning' })\n return\n }\n setDirty(false)\n enqueueSnackbar('Filing saved', { variant: 'success', persist: false })\n onFiled?.({\n added: next.filter((id) => !stored.includes(id)),\n removed: stored.filter((id) => !next.includes(id)),\n })\n }\n\n return (\n <CardDisplay\n header={'Campaigns'}\n help={pluginDocsHelp('crmLeads', { anchor: '#lead-campaigns' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: empty ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={saving || unchanged}\n onClick={() => void save().catch((error: unknown) => {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n setSaving(false)\n })}\n >\n {saving ? 'Saving…' : 'Save filing'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <ContainerPicker\n kind=\"campaign\"\n options={options}\n value={selected}\n onChange={(next) => {\n setSelected(next)\n setDirty(true)\n }}\n label=\"Filed under campaigns\"\n helperText={LEAD_CAMPAIGNS_HELPER_TEXT}\n disabled={saving}\n empty={empty}\n emptyText=\"There are no campaigns to file this lead under yet. Create one from Marketing.\"\n />\n </Stack>\n </CardDisplay>\n )\n}\nLeadCampaignsCard.displayName = 'LeadCampaignsCard'\n\nexport default LeadCampaignsCard\n"],"names":["containerMembershipUnchanged","containerMembershipValue","pluginDocsHelp","readContainerIds","ContainerPicker","CardDisplay","useSnackbar","useFirestore","writeGuardedBySeed","Button","Stack","doc","serverTimestamp","updateDoc","useEffect","useState","useCrmScope","crmClientListFields","useCrmSharingFollowUp","LEAD_CAMPAIGN_LIST_FIELDS","LEAD_CAMPAIGNS_HELPER_TEXT","leadCampaignNames","lead","options","labels","Map","map","option","value","label","id","get","filter","name","Boolean","LeadCampaignsCard","props","hostId","leadId","leadStatus","fromCache","optionsReady","onFiled","orgId","followUpSharing","firestore","enqueueSnackbar","stored","selected","setSelected","dirty","setDirty","saving","setSaving","storedKey","join","unchanged","empty","length","save","variant","persist","next","verdict","subject","unreadable","campaignIds","updatedAt","ok","message","added","includes","removed","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","disabled","onClick","catch","error","console","Error","allowDuplicate","spacing","kind","onChange","helperText","emptyText","displayName"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
- import { pluginDocsHelp, normalizeContactEmail, readErasureRequestedAtMs } from "@aglyn/aglyn";
18
+ import { normalizeContactEmail, readErasureRequestedAtMs } from "@aglyn/aglyn";
19
19
  import { useFirestore, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
20
20
  import { Stack, Typography } from "@mui/material";
21
21
  import { doc } from "firebase/firestore";
@@ -32,7 +32,7 @@ import { useErasePersonAction } from "./erase-person-action.js";
32
32
  import { LeadCampaignsCard, leadCampaignNames } from "./lead-campaigns-card.js";
33
33
  import { LeadConvertDialog } from "./lead-convert-dialog.js";
34
34
  import { LeadHistoryCard } from "./lead-history-card.js";
35
- import { LeadPropertiesCard } from "./lead-properties-card.js";
35
+ import { LEAD_PAGE_HELP, LeadPropertiesCard } from "./lead-properties-card.js";
36
36
  import { LeadUnqualifyDialog } from "./lead-unqualify-dialog.js";
37
37
  import { RecordActivityCard } from "./record-activity-card.js";
38
38
  import { CrmShareChipView, RecordSharingCard } from "./record-sharing-card.js";
@@ -141,9 +141,7 @@ import { leadPrimaryGroup } from "../model/contact-holder.js";
141
141
  return /*#__PURE__*/ _jsx(CrmRecordHeader, {
142
142
  kind: "Lead",
143
143
  title: undefined,
144
- help: pluginDocsHelp('crmLeads', {
145
- anchor: '#a-leads-page'
146
- }),
144
+ help: LEAD_PAGE_HELP,
147
145
  backHref: routes.section('leads'),
148
146
  backLabel: "Back to leads",
149
147
  children: /*#__PURE__*/ _jsx(Typography, {
@@ -157,9 +155,7 @@ import { leadPrimaryGroup } from "../model/contact-holder.js";
157
155
  return /*#__PURE__*/ _jsx(CrmRecordHeader, {
158
156
  kind: "Lead",
159
157
  title: undefined,
160
- help: pluginDocsHelp('crmLeads', {
161
- anchor: '#a-leads-page'
162
- }),
158
+ help: LEAD_PAGE_HELP,
163
159
  backHref: routes.section('leads'),
164
160
  backLabel: "Back to leads",
165
161
  loading: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-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 {\n pluginDocsHelp,\n type CrmLeadFields,\n normalizeContactEmail,\n readErasureRequestedAtMs,\n} from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport { useCampaignFilingLog } from '../hooks/use-campaign-filing-log'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { useErasePersonAction } from './erase-person-action'\nimport { LeadCampaignsCard, leadCampaignNames } from './lead-campaigns-card'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport { LeadHistoryCard } from './lead-history-card'\nimport { LeadPropertiesCard } from './lead-properties-card'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport { RecordActivityCard } from './record-activity-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { leadPrimaryGroup } from '../model/contact-holder'\n\ntype LeadDocument = Record<string, unknown> & CrmLeadFields\n\n/**\n * `/crm/leads/{leadId}` — one lead (AGL-2608).\n *\n * The record behind a row of the Leads section: what the person did on the\n * site, who is working them, and the conversion that turns them into a\n * contact, a company and a deal. One document listen and one roster request\n * for the owner picker; the convert dialog's reads open only when it does.\n *\n * ## The two levels, and the site a lead is acted on as (AGL-3278)\n *\n * The org root comes from `useCrmScope`, as it does on every other CRM\n * record page: under a site it is looked up from that site, and at the\n * organization level it is the mount's, known at once. It used to come\n * from `useOrgDataScope({ hostId })` — correct only under a site, because\n * an absent host settles at `orgId: null`, and a null org builds no\n * reference, opens no listener and leaves the page loading forever.\n *\n * Several surfaces still need ONE site: the feed an activity is filed in, the booking door, the consent basis, the\n * conversion. Under a site that is the mounted one. At the organization\n * level it is the lead's own first capturing site — `leadPrimaryGroup` —\n * the same answer `contactPrimaryGroup` gives the contact page, and the\n * same one the Leads list already converts a row as.\n */\nexport function LeadDetailPage(props: CrmDetailPageProps) {\n const { id, hostId, org, basePath } = props\n const firestore = useFirestore()\n const routes = crmRoutes(basePath)\n /*\n * Resolved BEFORE the read that needs it (AGL-3275). A lead is an org row\n * now, so the org has to be known to address one — and `scope` is null\n * while the host-index lookup settles. Handing that null to `doc()` is a\n * thrown `TypeError`, which the page renders as a 500 rather than as a\n * record still loading.\n */\n const { scope, orgId, ready, consentGroup: viewingGroup } = useCrmScope({\n hostId,\n org,\n })\n const {\n data: lead,\n status,\n fromCache,\n } = useFirestoreDoc<LeadDocument>(\n () => (scope ? doc(firestore, scope[0], scope[1], 'leads', id) : null),\n [firestore, scope, id],\n )\n /*\n * The group this lead is READ through: the mounted site's under a site,\n * and the lead's own first capturing site at the organization level\n * (AGL-3278). The consent basis is read against it — a refusal recorded\n * for any sibling in a declared group stands against every one of them,\n * so a group of one would report a person as opted in who had\n * unsubscribed from the brand beside this one.\n */\n const leadGroup = useMemo(\n () => viewingGroup ?? leadPrimaryGroup(lead, org as Record<string, unknown>),\n [viewingGroup, lead, org],\n )\n /** The one site this page acts as, or null for a lead no site captured. */\n const siteHostId = hostId ?? (leadGroup.hostId || null)\n const roster = useOrgMemberOptions(orgId)\n /*\n * The campaigns, read once for the page (AGL-3274): the header names the\n * ones the lead is filed under and the Campaigns card offers them. One\n * listener, not one per surface. Under a site, the campaigns placed on\n * it; at the organization level the lead is the org's record and so is\n * every campaign it may be filed under.\n */\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n // A saved filing is written on the lead's Activity too (AGL-3274), one\n // entry per campaign added or removed, by the member who saved it.\n const logFiling = useCampaignFilingLog({\n orgId,\n hostId: siteHostId,\n org: org as Record<string, unknown> | undefined,\n })\n const [converting, setConverting] = useState(false)\n const [unqualifying, setUnqualifying] = useState(false)\n // The Unqualified value the status select picked, if it named one (AGL-3512).\n const [unqualifyAs, setUnqualifyAs] = useState<string | null>(null)\n // The privacy erasure (AGL-2623), offered from the lead as from the\n // contact: the same request, filed by the lead's address.\n const leadEmail = lead ? normalizeContactEmail(lead['email']) : null\n const erase = useErasePersonAction({\n hostId: siteHostId,\n orgId,\n subject: lead && leadEmail ? { kind: 'lead', id, email: leadEmail } : null,\n requestedAtMs: readErasureRequestedAtMs(lead),\n })\n\n const label = lead ? String(lead['name'] || lead['email'] || id) : undefined\n\n /*\n * A SETTLED lookup with no org is an answer, not a wait (AGL-3278). The\n * spinner below is for a read in flight; standing on it once `ready` has\n * said there is no org to read from is the endless \"Loading…\" this page\n * shipped with.\n */\n const noOrg = ready && !scope\n if (noOrg || status === 'error' || (status === 'success' && !lead)) {\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={undefined}\n help={pluginDocsHelp('crmLeads', { anchor: '#a-leads-page' })}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {noOrg || status === 'error'\n ? 'This lead could not be read.'\n : 'This lead no longer exists — it may have been removed from the Inbox.'}\n </Typography>\n </CrmRecordHeader>\n )\n }\n if (!lead) {\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={undefined}\n help={pluginDocsHelp('crmLeads', { anchor: '#a-leads-page' })}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n loading\n />\n )\n }\n\n return (\n /* A task or an activity created from this page defaults to the lead's\n own site, not to the site the reader last picked in a list's drawer\n (AGL-2630) — as on every other record page. */\n <CrmCreateSiteDefault hostId={siteHostId}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the history card under it says what\n it holds rather than repeating the name. */}\n <Stack spacing={3}>\n <LeadPropertiesCard\n hostId={siteHostId}\n consentGroup={leadGroup}\n orgId={orgId}\n leadId={id}\n lead={lead}\n leadStatus={status}\n fromCache={fromCache}\n basePath={basePath}\n roster={roster}\n onConvert={() => setConverting(true)}\n onUnqualify={(statusLabel) => {\n setUnqualifyAs(statusLabel ?? null)\n setUnqualifying(true)\n }}\n extraMenuItems={erase.menuItems}\n banner={erase.banner}\n erasurePending={erase.pendingSinceMs !== null}\n org={org}\n // The campaigns the lead is filed under (AGL-3274), by name\n // beside the status. An id no container answers for draws no\n // chip: the card below keeps it, the header only names.\n extraChips={[\n // Seen here only through a share (AGL-3336): who shared it.\n <CrmShareChipView\n key=\"shared\"\n chip={crmShareChipFor(lead, viewingGroup?.hostIds ?? [])}\n org={org}\n />,\n ...leadCampaignNames(lead, campaigns.options).map((name) => (\n <CrmRecordChip key={name} label=\"Campaign\" value={name} />\n )),\n ]}\n />\n {/* What an assistant says about where the lead stands (AGL-2917): read on its own site. */}\n <CrmRecordInsightsZone\n hostId={siteHostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"lead\"\n recordId={id}\n name={label ?? ''}\n />\n <LeadCampaignsCard\n hostId={siteHostId}\n leadId={id}\n lead={lead}\n leadStatus={status}\n fromCache={fromCache}\n options={campaigns.options}\n optionsReady={campaigns.ready}\n onFiled={({ added, removed }) => {\n const named = (ids: string[]) =>\n ids.map((campaignId) => ({\n id: campaignId,\n name: campaigns.options.find((option) => option.value === campaignId)?.label ?? '',\n }))\n void logFiling({ leadId: id }, { filed: named(added), removed: named(removed) })\n }}\n />\n <LeadHistoryCard hostId={siteHostId} leadId={id} lead={lead} />\n {/* Where the lead is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"leads\"\n id={id}\n record={lead}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={viewingGroup?.hostIds}\n />\n <RecordActivityCard hostId={siteHostId} org={org} leadId={id} />\n </Stack>\n <LeadConvertDialog\n open={converting}\n onClose={() => setConverting(false)}\n hostId={siteHostId}\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={id}\n lead={lead}\n basePath={basePath}\n roster={roster}\n />\n <LeadUnqualifyDialog\n open={unqualifying}\n onClose={() => setUnqualifying(false)}\n hostId={siteHostId}\n leadId={id}\n leadLabel={label ?? id}\n statusLabel={unqualifyAs}\n />\n {erase.dialog}\n </CrmCreateSiteDefault>\n )\n}\nLeadDetailPage.displayName = 'LeadDetailPage'\n\nexport default LeadDetailPage\n"],"names":["pluginDocsHelp","normalizeContactEmail","readErasureRequestedAtMs","useFirestore","useFirestoreDoc","Stack","Typography","doc","useMemo","useState","useCampaignFilingLog","useCrmCampaigns","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","useErasePersonAction","LeadCampaignsCard","leadCampaignNames","LeadConvertDialog","LeadHistoryCard","LeadPropertiesCard","LeadUnqualifyDialog","RecordActivityCard","CrmShareChipView","RecordSharingCard","crmShareChipFor","leadPrimaryGroup","LeadDetailPage","props","id","hostId","org","basePath","firestore","routes","scope","orgId","ready","consentGroup","viewingGroup","data","lead","status","fromCache","leadGroup","siteHostId","roster","campaigns","enabled","logFiling","converting","setConverting","unqualifying","setUnqualifying","unqualifyAs","setUnqualifyAs","leadEmail","erase","subject","kind","email","requestedAtMs","label","String","undefined","noOrg","title","help","anchor","backHref","section","backLabel","variant","color","loading","spacing","leadId","leadStatus","onConvert","onUnqualify","statusLabel","extraMenuItems","menuItems","banner","erasurePending","pendingSinceMs","extraChips","chip","hostIds","options","map","name","value","recordId","optionsReady","onFiled","added","removed","named","ids","campaignId","find","option","filed","object","record","viewingHostIds","open","onClose","leadLabel","dialog","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-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 {\n type CrmLeadFields,\n normalizeContactEmail,\n readErasureRequestedAtMs,\n} from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport { useCampaignFilingLog } from '../hooks/use-campaign-filing-log'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { useErasePersonAction } from './erase-person-action'\nimport { LeadCampaignsCard, leadCampaignNames } from './lead-campaigns-card'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport { LeadHistoryCard } from './lead-history-card'\nimport { LEAD_PAGE_HELP, LeadPropertiesCard } from './lead-properties-card'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport { RecordActivityCard } from './record-activity-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { leadPrimaryGroup } from '../model/contact-holder'\n\ntype LeadDocument = Record<string, unknown> & CrmLeadFields\n\n/**\n * `/crm/leads/{leadId}` — one lead (AGL-2608).\n *\n * The record behind a row of the Leads section: what the person did on the\n * site, who is working them, and the conversion that turns them into a\n * contact, a company and a deal. One document listen and one roster request\n * for the owner picker; the convert dialog's reads open only when it does.\n *\n * ## The two levels, and the site a lead is acted on as (AGL-3278)\n *\n * The org root comes from `useCrmScope`, as it does on every other CRM\n * record page: under a site it is looked up from that site, and at the\n * organization level it is the mount's, known at once. It used to come\n * from `useOrgDataScope({ hostId })` — correct only under a site, because\n * an absent host settles at `orgId: null`, and a null org builds no\n * reference, opens no listener and leaves the page loading forever.\n *\n * Several surfaces still need ONE site: the feed an activity is filed in, the booking door, the consent basis, the\n * conversion. Under a site that is the mounted one. At the organization\n * level it is the lead's own first capturing site — `leadPrimaryGroup` —\n * the same answer `contactPrimaryGroup` gives the contact page, and the\n * same one the Leads list already converts a row as.\n */\nexport function LeadDetailPage(props: CrmDetailPageProps) {\n const { id, hostId, org, basePath } = props\n const firestore = useFirestore()\n const routes = crmRoutes(basePath)\n /*\n * Resolved BEFORE the read that needs it (AGL-3275). A lead is an org row\n * now, so the org has to be known to address one — and `scope` is null\n * while the host-index lookup settles. Handing that null to `doc()` is a\n * thrown `TypeError`, which the page renders as a 500 rather than as a\n * record still loading.\n */\n const { scope, orgId, ready, consentGroup: viewingGroup } = useCrmScope({\n hostId,\n org,\n })\n const {\n data: lead,\n status,\n fromCache,\n } = useFirestoreDoc<LeadDocument>(\n () => (scope ? doc(firestore, scope[0], scope[1], 'leads', id) : null),\n [firestore, scope, id],\n )\n /*\n * The group this lead is READ through: the mounted site's under a site,\n * and the lead's own first capturing site at the organization level\n * (AGL-3278). The consent basis is read against it — a refusal recorded\n * for any sibling in a declared group stands against every one of them,\n * so a group of one would report a person as opted in who had\n * unsubscribed from the brand beside this one.\n */\n const leadGroup = useMemo(\n () => viewingGroup ?? leadPrimaryGroup(lead, org as Record<string, unknown>),\n [viewingGroup, lead, org],\n )\n /** The one site this page acts as, or null for a lead no site captured. */\n const siteHostId = hostId ?? (leadGroup.hostId || null)\n const roster = useOrgMemberOptions(orgId)\n /*\n * The campaigns, read once for the page (AGL-3274): the header names the\n * ones the lead is filed under and the Campaigns card offers them. One\n * listener, not one per surface. Under a site, the campaigns placed on\n * it; at the organization level the lead is the org's record and so is\n * every campaign it may be filed under.\n */\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n // A saved filing is written on the lead's Activity too (AGL-3274), one\n // entry per campaign added or removed, by the member who saved it.\n const logFiling = useCampaignFilingLog({\n orgId,\n hostId: siteHostId,\n org: org as Record<string, unknown> | undefined,\n })\n const [converting, setConverting] = useState(false)\n const [unqualifying, setUnqualifying] = useState(false)\n // The Unqualified value the status select picked, if it named one (AGL-3512).\n const [unqualifyAs, setUnqualifyAs] = useState<string | null>(null)\n // The privacy erasure (AGL-2623), offered from the lead as from the\n // contact: the same request, filed by the lead's address.\n const leadEmail = lead ? normalizeContactEmail(lead['email']) : null\n const erase = useErasePersonAction({\n hostId: siteHostId,\n orgId,\n subject: lead && leadEmail ? { kind: 'lead', id, email: leadEmail } : null,\n requestedAtMs: readErasureRequestedAtMs(lead),\n })\n\n const label = lead ? String(lead['name'] || lead['email'] || id) : undefined\n\n /*\n * A SETTLED lookup with no org is an answer, not a wait (AGL-3278). The\n * spinner below is for a read in flight; standing on it once `ready` has\n * said there is no org to read from is the endless \"Loading…\" this page\n * shipped with.\n */\n const noOrg = ready && !scope\n if (noOrg || status === 'error' || (status === 'success' && !lead)) {\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={undefined}\n help={LEAD_PAGE_HELP}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {noOrg || status === 'error'\n ? 'This lead could not be read.'\n : 'This lead no longer exists — it may have been removed from the Inbox.'}\n </Typography>\n </CrmRecordHeader>\n )\n }\n if (!lead) {\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={undefined}\n help={LEAD_PAGE_HELP}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n loading\n />\n )\n }\n\n return (\n /* A task or an activity created from this page defaults to the lead's\n own site, not to the site the reader last picked in a list's drawer\n (AGL-2630) — as on every other record page. */\n <CrmCreateSiteDefault hostId={siteHostId}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the history card under it says what\n it holds rather than repeating the name. */}\n <Stack spacing={3}>\n <LeadPropertiesCard\n hostId={siteHostId}\n consentGroup={leadGroup}\n orgId={orgId}\n leadId={id}\n lead={lead}\n leadStatus={status}\n fromCache={fromCache}\n basePath={basePath}\n roster={roster}\n onConvert={() => setConverting(true)}\n onUnqualify={(statusLabel) => {\n setUnqualifyAs(statusLabel ?? null)\n setUnqualifying(true)\n }}\n extraMenuItems={erase.menuItems}\n banner={erase.banner}\n erasurePending={erase.pendingSinceMs !== null}\n org={org}\n // The campaigns the lead is filed under (AGL-3274), by name\n // beside the status. An id no container answers for draws no\n // chip: the card below keeps it, the header only names.\n extraChips={[\n // Seen here only through a share (AGL-3336): who shared it.\n <CrmShareChipView\n key=\"shared\"\n chip={crmShareChipFor(lead, viewingGroup?.hostIds ?? [])}\n org={org}\n />,\n ...leadCampaignNames(lead, campaigns.options).map((name) => (\n <CrmRecordChip key={name} label=\"Campaign\" value={name} />\n )),\n ]}\n />\n {/* What an assistant says about where the lead stands (AGL-2917): read on its own site. */}\n <CrmRecordInsightsZone\n hostId={siteHostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"lead\"\n recordId={id}\n name={label ?? ''}\n />\n <LeadCampaignsCard\n hostId={siteHostId}\n leadId={id}\n lead={lead}\n leadStatus={status}\n fromCache={fromCache}\n options={campaigns.options}\n optionsReady={campaigns.ready}\n onFiled={({ added, removed }) => {\n const named = (ids: string[]) =>\n ids.map((campaignId) => ({\n id: campaignId,\n name: campaigns.options.find((option) => option.value === campaignId)?.label ?? '',\n }))\n void logFiling({ leadId: id }, { filed: named(added), removed: named(removed) })\n }}\n />\n <LeadHistoryCard hostId={siteHostId} leadId={id} lead={lead} />\n {/* Where the lead is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"leads\"\n id={id}\n record={lead}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={viewingGroup?.hostIds}\n />\n <RecordActivityCard hostId={siteHostId} org={org} leadId={id} />\n </Stack>\n <LeadConvertDialog\n open={converting}\n onClose={() => setConverting(false)}\n hostId={siteHostId}\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={id}\n lead={lead}\n basePath={basePath}\n roster={roster}\n />\n <LeadUnqualifyDialog\n open={unqualifying}\n onClose={() => setUnqualifying(false)}\n hostId={siteHostId}\n leadId={id}\n leadLabel={label ?? id}\n statusLabel={unqualifyAs}\n />\n {erase.dialog}\n </CrmCreateSiteDefault>\n )\n}\nLeadDetailPage.displayName = 'LeadDetailPage'\n\nexport default LeadDetailPage\n"],"names":["normalizeContactEmail","readErasureRequestedAtMs","useFirestore","useFirestoreDoc","Stack","Typography","doc","useMemo","useState","useCampaignFilingLog","useCrmCampaigns","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","useErasePersonAction","LeadCampaignsCard","leadCampaignNames","LeadConvertDialog","LeadHistoryCard","LEAD_PAGE_HELP","LeadPropertiesCard","LeadUnqualifyDialog","RecordActivityCard","CrmShareChipView","RecordSharingCard","crmShareChipFor","leadPrimaryGroup","LeadDetailPage","props","id","hostId","org","basePath","firestore","routes","scope","orgId","ready","consentGroup","viewingGroup","data","lead","status","fromCache","leadGroup","siteHostId","roster","campaigns","enabled","logFiling","converting","setConverting","unqualifying","setUnqualifying","unqualifyAs","setUnqualifyAs","leadEmail","erase","subject","kind","email","requestedAtMs","label","String","undefined","noOrg","title","help","backHref","section","backLabel","variant","color","loading","spacing","leadId","leadStatus","onConvert","onUnqualify","statusLabel","extraMenuItems","menuItems","banner","erasurePending","pendingSinceMs","extraChips","chip","hostIds","options","map","name","value","recordId","optionsReady","onFiled","added","removed","named","ids","campaignId","find","option","filed","object","record","viewingHostIds","open","onClose","leadLabel","dialog","displayName"],"mappings":"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"}
@@ -100,7 +100,7 @@ function Fact(props) {
100
100
  return /*#__PURE__*/ _jsx(CardDisplay, {
101
101
  header: 'Captured history',
102
102
  help: pluginDocsHelp('crmLeads', {
103
- anchor: '#a-leads-page'
103
+ anchor: '#captured-history'
104
104
  }),
105
105
  contentGutterX: true,
106
106
  contentGutterY: true,