@aglyn/plugins-crm 1.0.0-beta.160 → 1.0.0-beta.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/contact-detail-page.js +5 -0
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.js +24 -2
- package/src/lib/components/contact-list-columns.js.map +1 -1
- package/src/lib/components/contacts-section.js +5 -0
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-email-state-chip.d.ts +17 -0
- package/src/lib/components/crm-email-state-chip.js +61 -0
- package/src/lib/components/crm-email-state-chip.js.map +1 -0
- package/src/lib/components/crm-send-email-button.d.ts +7 -1
- package/src/lib/components/crm-send-email-button.js +7 -3
- package/src/lib/components/crm-send-email-button.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +8 -1
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/leads-section.js +143 -23
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/constants/contact-filters.js +19 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +6 -0
- package/src/lib/model/contact-record.js +1 -0
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +22 -1
- package/src/lib/model/lead-filters.js +48 -1
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/server/email-send.js +10 -1
- package/src/lib/server/email-send.js.map +1 -1
- package/src/lib/server/record-email-state.d.ts +52 -0
- package/src/lib/server/record-email-state.js +118 -0
- package/src/lib/server/record-email-state.js.map +1 -0
- package/src/lib/server/record-timeline.d.ts +5 -1
- package/src/lib/server/record-timeline.js +26 -1
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server.js +5 -0
- package/src/lib/server.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-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 type {\n AglynOrgBilling,\n CrmLeadFields,\n CrmLeadProfilePatch,\n CrmLeadStatus,\n} from '@aglyn/aglyn'\nimport { mdiAccountCancelOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n FormControl,\n InputLabel,\n MenuItem,\n Select,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useId, useState } from 'react'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n addressDraftFrom,\n ContactAddressFields,\n type AddressDraft,\n} from './contact-address-fields'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { LeadStatusChip } from './lead-status-chip'\n\nconst NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX\nconst TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX\n\n/** The profile as the form holds it: every field a string, the address a draft. */\ninterface ProfileDraft {\n company: string\n jobTitle: string\n phone: string\n website: string\n leadSource: string\n tags: string\n address: AddressDraft\n}\n\n/** The stored profile as a draft the fields can edit. */\nfunction profileDraftFrom(lead: Record<string, unknown> & CrmLeadFields): ProfileDraft {\n return {\n company: String(lead.company ?? ''),\n jobTitle: String(lead.jobTitle ?? ''),\n phone: String(lead.phone ?? lead['phone'] ?? ''),\n website: String(lead.website ?? ''),\n leadSource: String(lead.leadSource ?? ''),\n tags: (lead.tags ?? []).join(', '),\n address: addressDraftFrom(lead.address ?? null),\n }\n}\n\n/** The patch as the document takes it: a cleared field is deleted, not blanked. */\nfunction profileWrite(patch: CrmLeadProfilePatch): Record<string, unknown> {\n const write: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue\n write[key] = value === null ? deleteField() : value\n }\n return write\n}\n\n/**\n * Why Convert is refused while an erasure waits on the person — the same\n * sentence shape the overflow's items carry, so the two read as one state.\n */\nexport const CONVERT_PENDING_ERASURE_REASON = 'An erasure is pending for this person'\n\n/** A label over a value — the record page's one row shape. */\nfunction Fact(props: { label: string; children: React.ReactNode }) {\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"body2\" component=\"div\">\n {props.children}\n </Typography>\n </Stack>\n )\n}\n\nexport interface LeadPropertiesCardProps {\n hostId: string\n leadId: string\n lead: Record<string, unknown> & CrmLeadFields\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n basePath: string\n roster: OrgMemberOptions\n onConvert: () => void\n onUnqualify: () => void\n /**\n * Items the page adds to the overflow beside Unqualify — the privacy\n * erasure (AGL-2623) lives on the page, because it needs the workspace\n * role and the API, and this card owns the header it must appear in.\n */\n extraMenuItems?: RowActionsMenuItem[]\n /** What the page shows above the facts — the erasure-pending state. */\n banner?: React.ReactNode\n /**\n * An erasure request is waiting on this person (AGL-2623). Convert stays\n * on the page but is refused with the reason, the way the overflow's items\n * are: a conversion filed now would reach the capture door only to be\n * refused there, and the lead itself goes when the request runs.\n */\n erasurePending?: boolean\n /**\n * The org the shell passed: the booking door reads whether the lead's site\n * runs Bookings and whether the plan is entitled to it (AGL-2660), and a\n * logged call reads the activity scope it belongs in (AGL-2661).\n */\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * What the team knows and decides about a lead: status, owner, notes, and\n * the identity and consent the capture recorded (AGL-2608).\n *\n * Status and owner are single-field client writes — the rules let a site\n * admin, editor or author update `hosts/{hostId}/leads`, and a one-field\n * `update` cannot roll anything else back. Notes are a text field seeded\n * from the document, so that save goes through `writeGuardedBySeed`: a draft\n * edited over a cached read would otherwise overwrite a newer note with an\n * older one plus a sentence.\n *\n * Converted leads are read-only here. Their status is the conversion, and\n * the actions become links to what the conversion made.\n */\nexport function LeadPropertiesCard(props: LeadPropertiesCardProps) {\n const {\n hostId,\n leadId,\n lead,\n leadStatus,\n fromCache,\n basePath,\n roster,\n onConvert,\n onUnqualify,\n extraMenuItems = [],\n banner,\n erasurePending = false,\n org,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const routes = crmRoutes(basePath)\n const ref = doc(firestore, 'hosts', hostId, 'leads', leadId)\n const status = Aglyn.crmLeadStatus(lead)\n const converted = Boolean(lead.convertedContactId)\n const open = Aglyn.isCrmLeadOpen(lead) && !converted\n /** What a capture that took a number left on the document (AGL-2661). */\n const leadPhone = String(lead['phone'] ?? '').trim()\n\n const [notes, setNotes] = useState(String(lead.notes ?? ''))\n // The label's id, so the status combobox is named \"Status\" rather than\n // after the status it shows — see `LeadOwnerSelect`.\n const statusLabelId = useId()\n const [notesDirty, setNotesDirty] = useState(false)\n const [savingNotes, setSavingNotes] = useState(false)\n /*\n * THE LEAD'S OWN PROFILE (AGL-3231) — company, title, phone, website,\n * address, tags, lead source — edited as one block with one Save, the\n * way the contact's Properties card saves. Seeded from the document and\n * guarded on save like the notes: a draft edited over a cached read must\n * not overwrite a newer profile with an older one.\n */\n const [profile, setProfile] = useState<ProfileDraft>(() => profileDraftFrom(lead))\n const [profileDirty, setProfileDirty] = useState(false)\n const [savingProfile, setSavingProfile] = useState(false)\n const [profileErrors, setProfileErrors] = useState<Record<string, string>>({})\n useEffect(() => {\n if (!profileDirty) setProfile(profileDraftFrom(lead))\n // The draft follows the document until it is edited; the fields are\n // read one by one so a change to any of them reseeds.\n }, [\n profileDirty,\n lead.company,\n lead.jobTitle,\n lead.phone,\n lead.website,\n lead.leadSource,\n lead.tags,\n lead.address,\n ])\n const editProfile = <K extends keyof ProfileDraft>(key: K, value: ProfileDraft[K]) => {\n setProfile((current) => ({ ...current, [key]: value }))\n setProfileDirty(true)\n }\n // A newer note from the server replaces an UNEDITED draft; an edited one is\n // the reader's, and the guard on save decides whether it may land.\n useEffect(() => {\n if (!notesDirty) setNotes(String(lead.notes ?? ''))\n }, [lead.notes, notesDirty])\n\n const write = async (fields: Record<string, unknown>, done: string) => {\n try {\n await updateDoc(ref, { ...fields, updatedAt: serverTimestamp() })\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n }\n\n const saveNotes = async () => {\n setSavingNotes(true)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n () => write({ notes: notes.trim().slice(0, NOTES_MAX) }, 'Notes saved'),\n )\n setSavingNotes(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The notes could not be saved.', {\n variant: 'warning',\n })\n return\n }\n setNotesDirty(false)\n }\n\n const saveProfile = async () => {\n const { patch, errors } = Aglyn.normalizeCrmLeadProfile({\n company: profile.company,\n jobTitle: profile.jobTitle,\n phone: profile.phone,\n website: profile.website,\n leadSource: profile.leadSource,\n tags: profile.tags,\n address: profile.address,\n })\n setProfileErrors(errors)\n if (Object.keys(errors).length) return\n setSavingProfile(true)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n () => write(profileWrite(patch), 'Lead saved'),\n )\n setSavingProfile(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The lead could not be saved.', {\n variant: 'warning',\n })\n return\n }\n setProfileDirty(false)\n }\n\n const consent = Aglyn.readMarketingBasis(lead, Aglyn.soloConsentGroup(hostId))\n const consentLine =\n consent.basis === 'granted'\n ? `Opted in to marketing${\n consent.basisAtMs ? ` on ${new Date(consent.basisAtMs).toLocaleDateString()}` : ''\n }`\n : consent.basis === 'declined'\n ? 'Declined marketing'\n : 'No marketing consent recorded — this lead cannot be emailed marketing'\n\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={String(lead['name'] || lead['email'] || leadId)}\n // The name is the heading; the address is the one line under it,\n // unless the address IS the name, in which case there is no second fact.\n subtitle={lead['name'] ? String(lead['email'] ?? '') : undefined}\n help={Aglyn.pluginDocsHelp('crmLeads', { anchor: '#working-a-lead-from-the-row' })}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n // The booking door (AGL-2660), while the lead is still the record\n // being worked: once converted, the contact is where a meeting is\n // booked from, and the links below lead there.\n booking={converted ? undefined : { hostId, org, kind: 'lead', recordId: leadId }}\n actions={\n <>\n {converted ? null : erasurePending ? (\n <Tooltip title={CONVERT_PENDING_ERASURE_REASON}>\n {/* A disabled button receives no pointer events, so the\n tooltip anchors on the span around it. */}\n <span>\n <Button size=\"small\" variant=\"contained\" disabled>\n {'Convert'}\n </Button>\n </span>\n </Tooltip>\n ) : (\n <Button size=\"small\" variant=\"contained\" onClick={onConvert}>\n {'Convert'}\n </Button>\n )}\n {/* Dial the number the capture carried, and log the call (AGL-2661). */}\n <CrmCallButton\n hostId={hostId}\n org={org}\n link={{ leadId }}\n phone={leadPhone}\n />\n <CrmSendEmailButton\n hostId={hostId}\n leadId={leadId}\n email={String(lead['email'] ?? '')}\n name={String(lead['name'] ?? '')}\n />\n </>\n }\n menuItems={[\n ...(open\n ? [\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />,\n destructive: true,\n onClick: onUnqualify,\n } satisfies RowActionsMenuItem,\n ]\n : []),\n ...extraMenuItems,\n ]}\n chips={\n <>\n <LeadStatusChip lead={lead} />\n <CrmRecordChip\n label=\"Owner\"\n value={lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined}\n />\n </>\n }\n >\n <Stack spacing={3}>\n {banner}\n {/*\n Only when the capture carried one (AGL-2661): the sign-up and\n booking doors write no phone, so a row for every lead would be a\n permanent blank. A form that captures one fills this.\n */}\n {leadPhone ? (\n <Fact label=\"Phone\">\n <CrmPhoneLink phone={leadPhone} />\n </Fact>\n ) : null}\n <Fact label=\"Marketing consent\">{consentLine}</Fact>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n {converted ? (\n <Fact label=\"Status\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <LeadStatusChip lead={lead} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {lead.convertedAtMs\n ? `Converted ${new Date(lead.convertedAtMs).toLocaleString()}`\n : 'Converted'}\n </Typography>\n </Stack>\n </Fact>\n ) : (\n <FormControl size=\"small\" sx={{ minWidth: 200 }}>\n <InputLabel id={statusLabelId}>{'Status'}</InputLabel>\n <Select\n labelId={statusLabelId}\n label=\"Status\"\n value={status === 'unqualified' ? 'unqualified' : status}\n onChange={(event) => {\n const next = String(event.target.value) as CrmLeadStatus\n if (next === 'unqualified') {\n onUnqualify()\n return\n }\n // Reopening drops the reason with the closed state: a lead\n // being worked again is not \"unqualified because …\".\n void write(\n {\n status: next,\n ...(status === 'unqualified' ? { unqualifiedReason: deleteField() } : {}),\n },\n 'Status updated',\n )\n }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">{Aglyn.CRM_LEAD_STATUS_LABELS.working}</MenuItem>\n <MenuItem value=\"unqualified\">\n {`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}\n </MenuItem>\n </Select>\n </FormControl>\n )}\n <LeadOwnerSelect\n value={lead.ownerUid}\n roster={roster}\n fullWidth={false}\n onChange={(uid) =>\n void write({ ownerUid: uid || deleteField() }, uid ? 'Owner assigned' : 'Owner cleared')\n }\n />\n </Stack>\n {status === 'unqualified' && lead.unqualifiedReason ? (\n <Alert severity=\"info\">{`Unqualified: ${lead.unqualifiedReason}`}</Alert>\n ) : null}\n {converted ? (\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.contact(String(lead.convertedContactId))}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open contact'}\n </Button>\n {lead.companyId ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.company(lead.companyId)}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open company'}\n </Button>\n ) : null}\n {lead.dealId ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.deal(lead.dealId)}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open deal'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n {/*\n The profile (AGL-3231): what Salesforce keeps on a lead and hands\n to the contact and the account on convert. Read-only once\n converted — the contact is the record then, and the links above\n lead there.\n */}\n <Stack spacing={2}>\n <Typography variant=\"subtitle2\">{'Profile'}</Typography>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Company\"\n value={profile.company}\n onChange={(event) => editProfile('company', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={profile.jobTitle}\n onChange={(event) => editProfile('jobTitle', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n </Stack>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Phone\"\n value={profile.phone}\n onChange={(event) => editProfile('phone', event.target.value)}\n disabled={converted}\n error={Boolean(profileErrors['phone'])}\n helperText={profileErrors['phone'] || 'With the country code, like +1 512 555 0107'}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n value={profile.website}\n onChange={(event) => editProfile('website', event.target.value)}\n disabled={converted}\n error={Boolean(profileErrors['website'])}\n helperText={profileErrors['website'] || 'Like acme.com'}\n fullWidth\n />\n </Stack>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Lead source\"\n value={profile.leadSource}\n onChange={(event) => editProfile('leadSource', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tags\"\n value={profile.tags}\n onChange={(event) => editProfile('tags', event.target.value)}\n disabled={converted}\n helperText=\"Comma-separated\"\n fullWidth\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Address'}\n </Typography>\n <ContactAddressFields\n value={profile.address}\n onChange={(next) => editProfile('address', next)}\n disabled={converted}\n />\n {converted ? null : (\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveProfile()}\n disabled={!profileDirty || savingProfile}\n >\n {'Save'}\n </Button>\n </Stack>\n )}\n </Stack>\n <Stack spacing={1}>\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={notes}\n onChange={(event) => {\n setNotes(event.target.value)\n setNotesDirty(true)\n }}\n multiline\n minRows={3}\n fullWidth\n slotProps={{ htmlInput: { maxLength: NOTES_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveNotes()}\n disabled={!notesDirty || savingNotes}\n >\n {'Save notes'}\n </Button>\n </Stack>\n </Stack>\n </Stack>\n </CrmRecordHeader>\n )\n}\nLeadPropertiesCard.displayName = 'LeadPropertiesCard'\n\nexport default LeadPropertiesCard\n"],"names":["Aglyn","mdiAccountCancelOutline","AppLink","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Alert","Button","FormControl","InputLabel","MenuItem","Select","Stack","TextField","Tooltip","Typography","deleteField","doc","serverTimestamp","updateDoc","useEffect","useId","useState","crmRoutes","addressDraftFrom","ContactAddressFields","CrmCallButton","CrmPhoneLink","CrmRecordChip","CrmRecordHeader","CrmSendEmailButton","LeadOwnerSelect","LeadStatusChip","NOTES_MAX","CRM_LEAD_NOTES_MAX","TEXT_MAX","CRM_LEAD_TEXT_MAX","profileDraftFrom","lead","company","String","jobTitle","phone","website","leadSource","tags","join","address","profileWrite","patch","write","key","value","Object","entries","undefined","CONVERT_PENDING_ERASURE_REASON","Fact","props","spacing","variant","color","label","component","children","LeadPropertiesCard","hostId","leadId","leadStatus","fromCache","basePath","roster","onConvert","onUnqualify","extraMenuItems","banner","erasurePending","org","firestore","enqueueSnackbar","routes","ref","status","crmLeadStatus","converted","Boolean","convertedContactId","open","isCrmLeadOpen","leadPhone","trim","notes","setNotes","statusLabelId","notesDirty","setNotesDirty","savingNotes","setSavingNotes","profile","setProfile","profileDirty","setProfileDirty","savingProfile","setSavingProfile","profileErrors","setProfileErrors","editProfile","current","fields","done","updatedAt","persist","error","Error","message","saveNotes","verdict","subject","unreadable","slice","ok","saveProfile","errors","normalizeCrmLeadProfile","keys","length","consent","readMarketingBasis","soloConsentGroup","consentLine","basis","basisAtMs","Date","toLocaleDateString","kind","title","subtitle","help","pluginDocsHelp","anchor","backHref","section","backLabel","booking","recordId","actions","span","size","disabled","onClick","link","email","name","menuItems","icon","path","destructive","chips","ownerUid","labelFor","direction","xs","md","sx","alignItems","convertedAtMs","toLocaleString","minWidth","id","labelId","onChange","event","next","target","unqualifiedReason","CRM_LEAD_STATUS_LABELS","new","working","unqualified","fullWidth","uid","severity","flexWrap","rowGap","componentVariant","nativeButton","href","contact","companyId","dealId","deal","slotProps","htmlInput","maxLength","helperText","justifyContent","multiline","minRows","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-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 type {\n AglynOrgBilling,\n CrmLeadFields,\n CrmLeadProfilePatch,\n CrmLeadStatus,\n} from '@aglyn/aglyn'\nimport { mdiAccountCancelOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n FormControl,\n InputLabel,\n MenuItem,\n Select,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useId, useState } from 'react'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n addressDraftFrom,\n ContactAddressFields,\n type AddressDraft,\n} from './contact-address-fields'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { LeadStatusChip } from './lead-status-chip'\n\nconst NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX\nconst TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX\n\n/** The profile as the form holds it: every field a string, the address a draft. */\ninterface ProfileDraft {\n company: string\n jobTitle: string\n phone: string\n website: string\n leadSource: string\n tags: string\n address: AddressDraft\n}\n\n/** The stored profile as a draft the fields can edit. */\nfunction profileDraftFrom(lead: Record<string, unknown> & CrmLeadFields): ProfileDraft {\n return {\n company: String(lead.company ?? ''),\n jobTitle: String(lead.jobTitle ?? ''),\n phone: String(lead.phone ?? lead['phone'] ?? ''),\n website: String(lead.website ?? ''),\n leadSource: String(lead.leadSource ?? ''),\n tags: (lead.tags ?? []).join(', '),\n address: addressDraftFrom(lead.address ?? null),\n }\n}\n\n/** The patch as the document takes it: a cleared field is deleted, not blanked. */\nfunction profileWrite(patch: CrmLeadProfilePatch): Record<string, unknown> {\n const write: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue\n write[key] = value === null ? deleteField() : value\n }\n return write\n}\n\n/**\n * Why Convert is refused while an erasure waits on the person — the same\n * sentence shape the overflow's items carry, so the two read as one state.\n */\nexport const CONVERT_PENDING_ERASURE_REASON = 'An erasure is pending for this person'\n\n/** A label over a value — the record page's one row shape. */\nfunction Fact(props: { label: string; children: React.ReactNode }) {\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"body2\" component=\"div\">\n {props.children}\n </Typography>\n </Stack>\n )\n}\n\nexport interface LeadPropertiesCardProps {\n hostId: string\n leadId: string\n lead: Record<string, unknown> & CrmLeadFields\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n basePath: string\n roster: OrgMemberOptions\n onConvert: () => void\n onUnqualify: () => void\n /**\n * Items the page adds to the overflow beside Unqualify — the privacy\n * erasure (AGL-2623) lives on the page, because it needs the workspace\n * role and the API, and this card owns the header it must appear in.\n */\n extraMenuItems?: RowActionsMenuItem[]\n /** What the page shows above the facts — the erasure-pending state. */\n banner?: React.ReactNode\n /**\n * An erasure request is waiting on this person (AGL-2623). Convert stays\n * on the page but is refused with the reason, the way the overflow's items\n * are: a conversion filed now would reach the capture door only to be\n * refused there, and the lead itself goes when the request runs.\n */\n erasurePending?: boolean\n /**\n * The org the shell passed: the booking door reads whether the lead's site\n * runs Bookings and whether the plan is entitled to it (AGL-2660), and a\n * logged call reads the activity scope it belongs in (AGL-2661).\n */\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * What the team knows and decides about a lead: status, owner, notes, and\n * the identity and consent the capture recorded (AGL-2608).\n *\n * Status and owner are single-field client writes — the rules let a site\n * admin, editor or author update `hosts/{hostId}/leads`, and a one-field\n * `update` cannot roll anything else back. Notes are a text field seeded\n * from the document, so that save goes through `writeGuardedBySeed`: a draft\n * edited over a cached read would otherwise overwrite a newer note with an\n * older one plus a sentence.\n *\n * Converted leads are read-only here. Their status is the conversion, and\n * the actions become links to what the conversion made.\n */\nexport function LeadPropertiesCard(props: LeadPropertiesCardProps) {\n const {\n hostId,\n leadId,\n lead,\n leadStatus,\n fromCache,\n basePath,\n roster,\n onConvert,\n onUnqualify,\n extraMenuItems = [],\n banner,\n erasurePending = false,\n org,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const routes = crmRoutes(basePath)\n const ref = doc(firestore, 'hosts', hostId, 'leads', leadId)\n const status = Aglyn.crmLeadStatus(lead)\n const converted = Boolean(lead.convertedContactId)\n const open = Aglyn.isCrmLeadOpen(lead) && !converted\n // The last verdict on the address (AGL-3245), as the platform stamped it.\n const emailState = Aglyn.readEmailState(lead)\n /** What a capture that took a number left on the document (AGL-2661). */\n const leadPhone = String(lead['phone'] ?? '').trim()\n\n const [notes, setNotes] = useState(String(lead.notes ?? ''))\n // The label's id, so the status combobox is named \"Status\" rather than\n // after the status it shows — see `LeadOwnerSelect`.\n const statusLabelId = useId()\n const [notesDirty, setNotesDirty] = useState(false)\n const [savingNotes, setSavingNotes] = useState(false)\n /*\n * THE LEAD'S OWN PROFILE (AGL-3231) — company, title, phone, website,\n * address, tags, lead source — edited as one block with one Save, the\n * way the contact's Properties card saves. Seeded from the document and\n * guarded on save like the notes: a draft edited over a cached read must\n * not overwrite a newer profile with an older one.\n */\n const [profile, setProfile] = useState<ProfileDraft>(() => profileDraftFrom(lead))\n const [profileDirty, setProfileDirty] = useState(false)\n const [savingProfile, setSavingProfile] = useState(false)\n const [profileErrors, setProfileErrors] = useState<Record<string, string>>({})\n useEffect(() => {\n if (!profileDirty) setProfile(profileDraftFrom(lead))\n // The draft follows the document until it is edited; the fields are\n // read one by one so a change to any of them reseeds.\n }, [\n profileDirty,\n lead.company,\n lead.jobTitle,\n lead.phone,\n lead.website,\n lead.leadSource,\n lead.tags,\n lead.address,\n ])\n const editProfile = <K extends keyof ProfileDraft>(key: K, value: ProfileDraft[K]) => {\n setProfile((current) => ({ ...current, [key]: value }))\n setProfileDirty(true)\n }\n // A newer note from the server replaces an UNEDITED draft; an edited one is\n // the reader's, and the guard on save decides whether it may land.\n useEffect(() => {\n if (!notesDirty) setNotes(String(lead.notes ?? ''))\n }, [lead.notes, notesDirty])\n\n const write = async (fields: Record<string, unknown>, done: string) => {\n try {\n await updateDoc(ref, { ...fields, updatedAt: serverTimestamp() })\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n }\n\n const saveNotes = async () => {\n setSavingNotes(true)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n () => write({ notes: notes.trim().slice(0, NOTES_MAX) }, 'Notes saved'),\n )\n setSavingNotes(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The notes could not be saved.', {\n variant: 'warning',\n })\n return\n }\n setNotesDirty(false)\n }\n\n const saveProfile = async () => {\n const { patch, errors } = Aglyn.normalizeCrmLeadProfile({\n company: profile.company,\n jobTitle: profile.jobTitle,\n phone: profile.phone,\n website: profile.website,\n leadSource: profile.leadSource,\n tags: profile.tags,\n address: profile.address,\n })\n setProfileErrors(errors)\n if (Object.keys(errors).length) return\n setSavingProfile(true)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n () => write(profileWrite(patch), 'Lead saved'),\n )\n setSavingProfile(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The lead could not be saved.', {\n variant: 'warning',\n })\n return\n }\n setProfileDirty(false)\n }\n\n const consent = Aglyn.readMarketingBasis(lead, Aglyn.soloConsentGroup(hostId))\n const consentLine =\n consent.basis === 'granted'\n ? `Opted in to marketing${\n consent.basisAtMs ? ` on ${new Date(consent.basisAtMs).toLocaleDateString()}` : ''\n }`\n : consent.basis === 'declined'\n ? 'Declined marketing'\n : 'No marketing consent recorded — this lead cannot be emailed marketing'\n\n return (\n <CrmRecordHeader\n kind=\"Lead\"\n title={String(lead['name'] || lead['email'] || leadId)}\n // The name is the heading; the address is the one line under it,\n // unless the address IS the name, in which case there is no second fact.\n subtitle={lead['name'] ? String(lead['email'] ?? '') : undefined}\n help={Aglyn.pluginDocsHelp('crmLeads', { anchor: '#working-a-lead-from-the-row' })}\n backHref={routes.section('leads')}\n backLabel=\"Back to leads\"\n // The booking door (AGL-2660), while the lead is still the record\n // being worked: once converted, the contact is where a meeting is\n // booked from, and the links below lead there.\n booking={converted ? undefined : { hostId, org, kind: 'lead', recordId: leadId }}\n actions={\n <>\n {converted ? null : erasurePending ? (\n <Tooltip title={CONVERT_PENDING_ERASURE_REASON}>\n {/* A disabled button receives no pointer events, so the\n tooltip anchors on the span around it. */}\n <span>\n <Button size=\"small\" variant=\"contained\" disabled>\n {'Convert'}\n </Button>\n </span>\n </Tooltip>\n ) : (\n <Button size=\"small\" variant=\"contained\" onClick={onConvert}>\n {'Convert'}\n </Button>\n )}\n {/* Dial the number the capture carried, and log the call (AGL-2661). */}\n <CrmCallButton\n hostId={hostId}\n org={org}\n link={{ leadId }}\n phone={leadPhone}\n />\n <CrmSendEmailButton\n hostId={hostId}\n leadId={leadId}\n email={String(lead['email'] ?? '')}\n name={String(lead['name'] ?? '')}\n emailState={emailState}\n />\n </>\n }\n menuItems={[\n ...(open\n ? [\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />,\n destructive: true,\n onClick: onUnqualify,\n } satisfies RowActionsMenuItem,\n ]\n : []),\n ...extraMenuItems,\n ]}\n chips={\n <>\n <LeadStatusChip lead={lead} />\n {/* The verdict on the address (AGL-3245), beside the status: a\n bounce does not move New or Working, but it is the first\n thing a person deciding whether to write must see. */}\n <CrmEmailStateChip state={emailState} />\n <CrmRecordChip\n label=\"Owner\"\n value={lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined}\n />\n </>\n }\n >\n <Stack spacing={3}>\n {banner}\n {/*\n Only when the capture carried one (AGL-2661): the sign-up and\n booking doors write no phone, so a row for every lead would be a\n permanent blank. A form that captures one fills this.\n */}\n {leadPhone ? (\n <Fact label=\"Phone\">\n <CrmPhoneLink phone={leadPhone} />\n </Fact>\n ) : null}\n <Fact label=\"Marketing consent\">{consentLine}</Fact>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n {converted ? (\n <Fact label=\"Status\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <LeadStatusChip lead={lead} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {lead.convertedAtMs\n ? `Converted ${new Date(lead.convertedAtMs).toLocaleString()}`\n : 'Converted'}\n </Typography>\n </Stack>\n </Fact>\n ) : (\n <FormControl size=\"small\" sx={{ minWidth: 200 }}>\n <InputLabel id={statusLabelId}>{'Status'}</InputLabel>\n <Select\n labelId={statusLabelId}\n label=\"Status\"\n value={status === 'unqualified' ? 'unqualified' : status}\n onChange={(event) => {\n const next = String(event.target.value) as CrmLeadStatus\n if (next === 'unqualified') {\n onUnqualify()\n return\n }\n // Reopening drops the reason with the closed state: a lead\n // being worked again is not \"unqualified because …\".\n void write(\n {\n status: next,\n ...(status === 'unqualified' ? { unqualifiedReason: deleteField() } : {}),\n },\n 'Status updated',\n )\n }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">{Aglyn.CRM_LEAD_STATUS_LABELS.working}</MenuItem>\n <MenuItem value=\"unqualified\">\n {`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}\n </MenuItem>\n </Select>\n </FormControl>\n )}\n <LeadOwnerSelect\n value={lead.ownerUid}\n roster={roster}\n fullWidth={false}\n onChange={(uid) =>\n void write({ ownerUid: uid || deleteField() }, uid ? 'Owner assigned' : 'Owner cleared')\n }\n />\n </Stack>\n {status === 'unqualified' && lead.unqualifiedReason ? (\n <Alert severity=\"info\">{`Unqualified: ${lead.unqualifiedReason}`}</Alert>\n ) : null}\n {converted ? (\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.contact(String(lead.convertedContactId))}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open contact'}\n </Button>\n {lead.companyId ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.company(lead.companyId)}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open company'}\n </Button>\n ) : null}\n {lead.dealId ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.deal(lead.dealId)}\n size=\"small\"\n variant=\"outlined\"\n >\n {'Open deal'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n {/*\n The profile (AGL-3231): what Salesforce keeps on a lead and hands\n to the contact and the account on convert. Read-only once\n converted — the contact is the record then, and the links above\n lead there.\n */}\n <Stack spacing={2}>\n <Typography variant=\"subtitle2\">{'Profile'}</Typography>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Company\"\n value={profile.company}\n onChange={(event) => editProfile('company', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={profile.jobTitle}\n onChange={(event) => editProfile('jobTitle', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n </Stack>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Phone\"\n value={profile.phone}\n onChange={(event) => editProfile('phone', event.target.value)}\n disabled={converted}\n error={Boolean(profileErrors['phone'])}\n helperText={profileErrors['phone'] || 'With the country code, like +1 512 555 0107'}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n value={profile.website}\n onChange={(event) => editProfile('website', event.target.value)}\n disabled={converted}\n error={Boolean(profileErrors['website'])}\n helperText={profileErrors['website'] || 'Like acme.com'}\n fullWidth\n />\n </Stack>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>\n <TextField\n size=\"small\"\n label=\"Lead source\"\n value={profile.leadSource}\n onChange={(event) => editProfile('leadSource', event.target.value)}\n disabled={converted}\n slotProps={{ htmlInput: { maxLength: TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tags\"\n value={profile.tags}\n onChange={(event) => editProfile('tags', event.target.value)}\n disabled={converted}\n helperText=\"Comma-separated\"\n fullWidth\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Address'}\n </Typography>\n <ContactAddressFields\n value={profile.address}\n onChange={(next) => editProfile('address', next)}\n disabled={converted}\n />\n {converted ? null : (\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveProfile()}\n disabled={!profileDirty || savingProfile}\n >\n {'Save'}\n </Button>\n </Stack>\n )}\n </Stack>\n <Stack spacing={1}>\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={notes}\n onChange={(event) => {\n setNotes(event.target.value)\n setNotesDirty(true)\n }}\n multiline\n minRows={3}\n fullWidth\n slotProps={{ htmlInput: { maxLength: NOTES_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveNotes()}\n disabled={!notesDirty || savingNotes}\n >\n {'Save notes'}\n </Button>\n </Stack>\n </Stack>\n </Stack>\n </CrmRecordHeader>\n )\n}\nLeadPropertiesCard.displayName = 'LeadPropertiesCard'\n\nexport default LeadPropertiesCard\n"],"names":["Aglyn","mdiAccountCancelOutline","AppLink","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Alert","Button","FormControl","InputLabel","MenuItem","Select","Stack","TextField","Tooltip","Typography","deleteField","doc","serverTimestamp","updateDoc","useEffect","useId","useState","crmRoutes","addressDraftFrom","ContactAddressFields","CrmCallButton","CrmPhoneLink","CrmEmailStateChip","CrmRecordChip","CrmRecordHeader","CrmSendEmailButton","LeadOwnerSelect","LeadStatusChip","NOTES_MAX","CRM_LEAD_NOTES_MAX","TEXT_MAX","CRM_LEAD_TEXT_MAX","profileDraftFrom","lead","company","String","jobTitle","phone","website","leadSource","tags","join","address","profileWrite","patch","write","key","value","Object","entries","undefined","CONVERT_PENDING_ERASURE_REASON","Fact","props","spacing","variant","color","label","component","children","LeadPropertiesCard","hostId","leadId","leadStatus","fromCache","basePath","roster","onConvert","onUnqualify","extraMenuItems","banner","erasurePending","org","firestore","enqueueSnackbar","routes","ref","status","crmLeadStatus","converted","Boolean","convertedContactId","open","isCrmLeadOpen","emailState","readEmailState","leadPhone","trim","notes","setNotes","statusLabelId","notesDirty","setNotesDirty","savingNotes","setSavingNotes","profile","setProfile","profileDirty","setProfileDirty","savingProfile","setSavingProfile","profileErrors","setProfileErrors","editProfile","current","fields","done","updatedAt","persist","error","Error","message","saveNotes","verdict","subject","unreadable","slice","ok","saveProfile","errors","normalizeCrmLeadProfile","keys","length","consent","readMarketingBasis","soloConsentGroup","consentLine","basis","basisAtMs","Date","toLocaleDateString","kind","title","subtitle","help","pluginDocsHelp","anchor","backHref","section","backLabel","booking","recordId","actions","span","size","disabled","onClick","link","email","name","menuItems","icon","path","destructive","chips","state","ownerUid","labelFor","direction","xs","md","sx","alignItems","convertedAtMs","toLocaleString","minWidth","id","labelId","onChange","event","next","target","unqualifiedReason","CRM_LEAD_STATUS_LABELS","new","working","unqualified","fullWidth","uid","severity","flexWrap","rowGap","componentVariant","nativeButton","href","contact","companyId","dealId","deal","slotProps","htmlInput","maxLength","helperText","justifyContent","multiline","minRows","displayName"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
-
import { mdiAccountArrowRight, mdiAccountCancelOutline, mdiAccountConvertOutline, mdiAccountTieOutline } from "@aglyn/shared-data-mdi";
|
|
20
|
+
import { mdiAccountArrowRight, mdiAccountCancelOutline, mdiAccountConvertOutline, mdiAccountTieOutline, mdiMagnify } from "@aglyn/shared-data-mdi";
|
|
21
21
|
import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
22
22
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
23
23
|
import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
@@ -34,13 +34,13 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
|
|
|
34
34
|
import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
|
|
35
35
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
36
36
|
import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
37
|
-
import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, InputLabel, MenuItem, Select, Stack, Typography } from "@mui/material";
|
|
37
|
+
import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, InputAdornment, InputLabel, MenuItem, Select, Stack, TextField, Typography } from "@mui/material";
|
|
38
38
|
import { collection, deleteField, doc, limit, orderBy, query, serverTimestamp, updateDoc } from "firebase/firestore";
|
|
39
39
|
import { useRouter } from "next/navigation";
|
|
40
40
|
import { useCallback, useEffect, useId, useMemo, useState } from "react";
|
|
41
41
|
import { downloadTextFile } from "../model/contacts-csv.js";
|
|
42
42
|
import { crmRoutes } from "../model/crm-routes.js";
|
|
43
|
-
import { LEAD_FILTER_LABELS, LEAD_FILTERS, leadMatchesFilter } from "../model/lead-filters.js";
|
|
43
|
+
import { LEAD_FILTER_LABELS, LEAD_EMAIL_FILTER_LABELS, LEAD_EMAIL_FILTERS, LEAD_FILTERS, leadMatchesEmailFilter, leadMatchesFilter, leadMatchesSearch } from "../model/lead-filters.js";
|
|
44
44
|
import { leadsCsv } from "../model/leads-csv.js";
|
|
45
45
|
import { LeadConvertDialog } from "./lead-convert-dialog.js";
|
|
46
46
|
import { leadSourceLabel, leadSources, leadTimeLabel } from "./lead-history-card.js";
|
|
@@ -49,6 +49,7 @@ import NewLeadDrawer from "./new-lead-drawer.js";
|
|
|
49
49
|
import { useCrmApi } from "./use-crm-api.js";
|
|
50
50
|
import { LeadOwnerSelect } from "./lead-owner-select.js";
|
|
51
51
|
import { CONVERT_PENDING_ERASURE_REASON } from "./lead-properties-card.js";
|
|
52
|
+
import { CrmEmailStateChip } from "./crm-email-state-chip.js";
|
|
52
53
|
import { LeadStatusChip } from "./lead-status-chip.js";
|
|
53
54
|
import LeadSurfacesNote from "./lead-surfaces-note.js";
|
|
54
55
|
import { LeadUnqualifyDialog } from "./lead-unqualify-dialog.js";
|
|
@@ -155,30 +156,73 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
155
156
|
}, [
|
|
156
157
|
views.state.filters
|
|
157
158
|
]);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
/*
|
|
160
|
+
* The `Email` filter (AGL-3245) is the view's too, as an `emailState`
|
|
161
|
+
* clause beside the status one. Each setter keeps the other's clause:
|
|
162
|
+
* narrowing to bounced leads does not reopen the unqualified ones.
|
|
163
|
+
*/ const emailFilter = useMemo(()=>{
|
|
164
|
+
var _views_state_filters_find;
|
|
165
|
+
const value = (_views_state_filters_find = views.state.filters.find((clause)=>clause.field === 'emailState')) == null ? void 0 : _views_state_filters_find.value;
|
|
166
|
+
return LEAD_EMAIL_FILTERS.includes(value != null ? value : '') ? value : 'any';
|
|
167
|
+
}, [
|
|
168
|
+
views.state.filters
|
|
169
|
+
]);
|
|
170
|
+
const setFilter = useCallback((next)=>views.setFilters([
|
|
171
|
+
...views.state.filters.filter((clause)=>clause.field !== 'status'),
|
|
172
|
+
...next === 'open' ? [] : [
|
|
173
|
+
{
|
|
174
|
+
field: 'status',
|
|
175
|
+
op: 'equals',
|
|
176
|
+
value: next
|
|
177
|
+
}
|
|
178
|
+
]
|
|
164
179
|
]), [
|
|
165
|
-
views.setFilters
|
|
180
|
+
views.setFilters,
|
|
181
|
+
views.state.filters
|
|
182
|
+
]);
|
|
183
|
+
const setEmailFilter = useCallback((next)=>views.setFilters([
|
|
184
|
+
...views.state.filters.filter((clause)=>clause.field !== 'emailState'),
|
|
185
|
+
...next === 'any' ? [] : [
|
|
186
|
+
{
|
|
187
|
+
field: 'emailState',
|
|
188
|
+
op: 'equals',
|
|
189
|
+
value: next
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
]), [
|
|
193
|
+
views.setFilters,
|
|
194
|
+
views.state.filters
|
|
166
195
|
]);
|
|
167
196
|
// The label's id, so the filter's combobox is named "Show" rather than
|
|
168
197
|
// after the option it shows — see `LeadOwnerSelect`.
|
|
169
198
|
const filterLabelId = useId();
|
|
170
|
-
const
|
|
199
|
+
const emailFilterLabelId = useId();
|
|
200
|
+
/*
|
|
201
|
+
* The search box is the SECTION'S, not the grid's (AGL-3246). The grid's
|
|
202
|
+
* quick filter runs over the rows the grid holds, and the grid holds one
|
|
203
|
+
* PAGE of the window — so a lead on page three answered "no match" while
|
|
204
|
+
* the footer below went on counting the unfiltered window. The term
|
|
205
|
+
* narrows the whole loaded window here, beside the status filter and
|
|
206
|
+
* before the footer's count and the page slice, over the fields a person
|
|
207
|
+
* types to find a lead: name, email, company, title and tags.
|
|
208
|
+
*/ const [search, setSearch] = useState('');
|
|
209
|
+
const rows = useMemo(()=>window.filter((lead)=>leadMatchesFilter(lead, filter) && leadMatchesEmailFilter(lead, emailFilter) && leadMatchesSearch(lead, search)), [
|
|
171
210
|
window,
|
|
172
|
-
filter
|
|
211
|
+
filter,
|
|
212
|
+
emailFilter,
|
|
213
|
+
search
|
|
173
214
|
]);
|
|
174
215
|
const [page, setPage] = useState(0);
|
|
175
216
|
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
176
|
-
// A new filter starts on page one: page three of the open
|
|
177
|
-
// page of the unqualified ones, and an out-of-range page
|
|
217
|
+
// A new filter or search term starts on page one: page three of the open
|
|
218
|
+
// leads is not a page of the unqualified ones, and an out-of-range page
|
|
219
|
+
// renders empty.
|
|
178
220
|
useEffect(()=>{
|
|
179
221
|
setPage(0);
|
|
180
222
|
}, [
|
|
181
|
-
filter
|
|
223
|
+
filter,
|
|
224
|
+
emailFilter,
|
|
225
|
+
search
|
|
182
226
|
]);
|
|
183
227
|
const pageRows = useMemo(()=>rows.slice(page * pageSize, (page + 1) * pageSize), [
|
|
184
228
|
rows,
|
|
@@ -186,13 +230,15 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
186
230
|
pageSize
|
|
187
231
|
]);
|
|
188
232
|
/*
|
|
189
|
-
* The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter
|
|
190
|
-
* changes: a selection made on the open leads is not a
|
|
191
|
-
* unqualified ones, and the bar's count would be over
|
|
192
|
-
* listed.
|
|
233
|
+
* The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or
|
|
234
|
+
* the search term changes: a selection made on the open leads is not a
|
|
235
|
+
* selection of the unqualified ones, and the bar's count would be over
|
|
236
|
+
* rows no longer listed.
|
|
193
237
|
*/ const [selectedIds, setSelectedIds] = useState([]);
|
|
194
238
|
useEffect(()=>setSelectedIds([]), [
|
|
195
|
-
filter
|
|
239
|
+
filter,
|
|
240
|
+
emailFilter,
|
|
241
|
+
search
|
|
196
242
|
]);
|
|
197
243
|
// How the file names the owner and, at the org level, the site.
|
|
198
244
|
const csvOptions = useMemo(()=>_extends({
|
|
@@ -204,7 +250,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
204
250
|
hostId,
|
|
205
251
|
mount
|
|
206
252
|
]);
|
|
207
|
-
// The listed window — every row the filter
|
|
253
|
+
// The listed window — every row the filter and the search admit, not just
|
|
254
|
+
// the page.
|
|
208
255
|
const handleExport = useCallback(()=>{
|
|
209
256
|
downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions));
|
|
210
257
|
}, [
|
|
@@ -371,6 +418,30 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
371
418
|
}
|
|
372
419
|
})
|
|
373
420
|
},
|
|
421
|
+
{
|
|
422
|
+
/*
|
|
423
|
+
* The verdict on the address (AGL-3245): the chip the lead's page
|
|
424
|
+
* carries, so a bounced lead is told apart in the queue it is worked
|
|
425
|
+
* from; its label for the sort and the export.
|
|
426
|
+
*/ field: 'emailState',
|
|
427
|
+
headerName: 'Email',
|
|
428
|
+
flex: 0.9,
|
|
429
|
+
minWidth: 150,
|
|
430
|
+
valueGetter: (_value, row)=>{
|
|
431
|
+
const state = Aglyn.readEmailState(row);
|
|
432
|
+
return state ? Aglyn.EMAIL_STATE_LABELS[state.status] : '';
|
|
433
|
+
},
|
|
434
|
+
renderCell: ({ row })=>{
|
|
435
|
+
const state = Aglyn.readEmailState(row);
|
|
436
|
+
return state ? /*#__PURE__*/ _jsx(CrmEmailStateChip, {
|
|
437
|
+
state: state
|
|
438
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
439
|
+
variant: "caption",
|
|
440
|
+
color: "text.secondary",
|
|
441
|
+
children: '—'
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
},
|
|
374
445
|
{
|
|
375
446
|
field: 'ownerUid',
|
|
376
447
|
headerName: 'Owner',
|
|
@@ -538,6 +609,52 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
538
609
|
})
|
|
539
610
|
]
|
|
540
611
|
}),
|
|
612
|
+
/*#__PURE__*/ _jsxs(FormControl, {
|
|
613
|
+
size: "small",
|
|
614
|
+
sx: {
|
|
615
|
+
minWidth: 160
|
|
616
|
+
},
|
|
617
|
+
children: [
|
|
618
|
+
/*#__PURE__*/ _jsx(InputLabel, {
|
|
619
|
+
id: emailFilterLabelId,
|
|
620
|
+
children: 'Email'
|
|
621
|
+
}),
|
|
622
|
+
/*#__PURE__*/ _jsx(Select, {
|
|
623
|
+
labelId: emailFilterLabelId,
|
|
624
|
+
label: "Email",
|
|
625
|
+
value: emailFilter,
|
|
626
|
+
onChange: (event)=>setEmailFilter(event.target.value),
|
|
627
|
+
children: LEAD_EMAIL_FILTERS.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
628
|
+
value: option,
|
|
629
|
+
children: LEAD_EMAIL_FILTER_LABELS[option]
|
|
630
|
+
}, option))
|
|
631
|
+
})
|
|
632
|
+
]
|
|
633
|
+
}),
|
|
634
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
635
|
+
size: "small",
|
|
636
|
+
value: search,
|
|
637
|
+
onChange: (event)=>setSearch(event.target.value),
|
|
638
|
+
placeholder: "Search leads",
|
|
639
|
+
slotProps: {
|
|
640
|
+
input: {
|
|
641
|
+
startAdornment: /*#__PURE__*/ _jsx(InputAdornment, {
|
|
642
|
+
position: "start",
|
|
643
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
644
|
+
path: mdiMagnify.path,
|
|
645
|
+
size: 0.8
|
|
646
|
+
})
|
|
647
|
+
})
|
|
648
|
+
},
|
|
649
|
+
htmlInput: {
|
|
650
|
+
'aria-label': 'Search leads',
|
|
651
|
+
type: 'search'
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
sx: {
|
|
655
|
+
minWidth: 200
|
|
656
|
+
}
|
|
657
|
+
}),
|
|
541
658
|
/*#__PURE__*/ _jsx(LeadImportButton, {
|
|
542
659
|
hostId: hostId
|
|
543
660
|
}),
|
|
@@ -569,7 +686,7 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
569
686
|
}) : status === 'success' && rows.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
570
687
|
variant: "body2",
|
|
571
688
|
color: "text.secondary",
|
|
572
|
-
children: `No ${LEAD_FILTER_LABELS[filter].toLowerCase()} leads
|
|
689
|
+
children: `No ${LEAD_FILTER_LABELS[filter].toLowerCase()} leads` + (search.trim() ? ` match “${search.trim()}”` : '') + ` among the ${window.length.toLocaleString()} most recently seen.`
|
|
573
690
|
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
574
691
|
children: [
|
|
575
692
|
/*#__PURE__*/ _jsx(LeadsBulkBar, {
|
|
@@ -598,6 +715,9 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
598
715
|
onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
|
|
599
716
|
sortModel: grid.sortModel,
|
|
600
717
|
onSortModelChange: grid.onSortModelChange,
|
|
718
|
+
// The search is the section's, above: the grid's own box
|
|
719
|
+
// would search this page alone.
|
|
720
|
+
quickFilter: false,
|
|
601
721
|
// Paged by the footer below, so the grid must not also slice.
|
|
602
722
|
hideFooter: true
|
|
603
723
|
})
|
|
@@ -614,7 +734,7 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
|
|
|
614
734
|
}),
|
|
615
735
|
truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
616
736
|
severity: "info",
|
|
617
|
-
children: `Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` + 'leads. The status filter
|
|
737
|
+
children: `Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` + 'leads. The search box and the status filter narrow these ' + `${LEADS_WINDOW.toLocaleString()} only; older leads are still ` + 'listed in the Inbox and reached by campaign audiences.'
|
|
618
738
|
}) : null
|
|
619
739
|
]
|
|
620
740
|
})
|