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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/companies-section.js +43 -8
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/company-edit-drawer.js +3 -1
  5. package/src/lib/components/company-edit-drawer.js.map +1 -1
  6. package/src/lib/components/contact-detail-page.js +6 -1
  7. package/src/lib/components/contact-detail-page.js.map +1 -1
  8. package/src/lib/components/contacts-section.js +37 -8
  9. package/src/lib/components/contacts-section.js.map +1 -1
  10. package/src/lib/components/crm-email-check.d.ts +14 -0
  11. package/src/lib/components/crm-email-check.js +38 -1
  12. package/src/lib/components/crm-email-check.js.map +1 -1
  13. package/src/lib/components/crm-next-activity-column.d.ts +5 -4
  14. package/src/lib/components/crm-next-activity-column.js +5 -4
  15. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  16. package/src/lib/components/deal-edit-drawer.js +3 -1
  17. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  18. package/src/lib/components/deals-section.js +50 -6
  19. package/src/lib/components/deals-section.js.map +1 -1
  20. package/src/lib/components/lead-properties-card.js +9 -3
  21. package/src/lib/components/lead-properties-card.js.map +1 -1
  22. package/src/lib/components/leads-section.js +61 -11
  23. package/src/lib/components/leads-section.js.map +1 -1
  24. package/src/lib/components/tasks-section.js +61 -9
  25. package/src/lib/components/tasks-section.js.map +1 -1
  26. package/src/lib/constants/company-filters.js +40 -2
  27. package/src/lib/constants/company-filters.js.map +1 -1
  28. package/src/lib/constants/contact-filters.d.ts +10 -1
  29. package/src/lib/constants/contact-filters.js +62 -2
  30. package/src/lib/constants/contact-filters.js.map +1 -1
  31. package/src/lib/constants/deal-filters.js +78 -0
  32. package/src/lib/constants/deal-filters.js.map +1 -1
  33. package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
  34. package/src/lib/hooks/use-crm-column-sort.js +156 -0
  35. package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
  36. package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
  37. package/src/lib/hooks/use-crm-tasks.js +10 -8
  38. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  39. package/src/lib/model/crm-list-query.d.ts +6 -0
  40. package/src/lib/model/crm-list-query.js +13 -0
  41. package/src/lib/model/crm-list-query.js.map +1 -1
  42. package/src/lib/model/lead-filters.d.ts +19 -2
  43. package/src/lib/model/lead-filters.js +112 -2
  44. package/src/lib/model/lead-filters.js.map +1 -1
  45. package/src/lib/model/native-vocabulary.d.ts +41 -0
  46. package/src/lib/model/native-vocabulary.js +59 -0
  47. package/src/lib/model/native-vocabulary.js.map +1 -0
  48. package/src/lib/model/task-views.d.ts +15 -2
  49. package/src/lib/model/task-views.js +50 -4
  50. package/src/lib/model/task-views.js.map +1 -1
  51. package/src/lib/server/email-check.d.ts +4 -1
  52. package/src/lib/server/email-check.js +18 -6
  53. package/src/lib/server/email-check.js.map +1 -1
  54. package/src/lib/transfer/tasks.js +2 -1
  55. package/src/lib/transfer/tasks.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.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 ConsolePluginPageProps,\n type CrmDealStatus,\n type CrmPicklist,\n CRM_COLLECTIONS,\n crmPicklistRank,\n dealStageById,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport {\n DEAL_FILTER_CODECS,\n DEAL_LIST_DECLARATION,\n DEAL_LIST_FILTER_FIELDS,\n DEAL_PICKLIST_FILTER_NONE,\n DEAL_PREFIX_SEARCH,\n dealPipelineBase,\n dealQueryClause,\n} from '../constants/deal-filters'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n useDealsByStatus,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_DEALS_RESOURCE } from '../transfer/fields'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the status, \"No next activity\", the Type and the Lead source. */\nconst DEAL_GRID_FILTER_FIELDS = DEAL_LIST_FILTER_FIELDS\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n type: 'Type',\n leadSource: 'Lead source',\n}\nconst DEAL_STATUS_FILTER_OPTIONS = (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n}))\n\n/** Salesforce's Type and Lead source columns are offered, not shown, until a view turns them on (AGL-3516). */\nconst DEAL_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { type: false, leadSource: false }\n\n/** A picklist's choices for its filter: every value the org keeps, inactive ones marked, then \"none\". */\nfunction picklistFilterOptions(picklist: CrmPicklist, none: string): ListFilterOption[] {\n return [\n ...picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: DEAL_PICKLIST_FILTER_NONE, label: none },\n ]\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by a query that carries its status, \"No next activity\" and search\n * word (AGL-3321); it is where the closed history is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Import and Export open the console's import wizard and export\n * dialog on `crm.deals` (AGL-3528): the export over the selection, the\n * list's own query, or every deal.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // Salesforce's Type and Lead source lists, for their columns and filters (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', scope.orgId)\n const leadSourceList = useCrmPicklist('leadSource', scope.orgId)\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // Every clause is the query's, the status and \"No next activity\" alike.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n /*\n * THE TABLE (AGL-3321): the pipeline's deals, every clause and the search\n * word on one query — the search reads the title's word prefixes every\n * deal writer stamps, so a deal on page four is found by any word of its\n * title — paged by that query, and read only while the table is showing.\n */\n const [searchWords, setSearchWords] = useState<string[]>([])\n const searching = searchWords.some((word) => word.trim())\n const searchKey = searchWords.join(' ')\n const foldsScope = useCrmFoldsScope(scope.orgId, scope.visibleTo)\n const pipelineBase = useMemo(() => dealPipelineBase(pipeline?.$id ?? null), [pipeline?.$id])\n // Each stored clause asked through the field its writer keeps — a Type\n // or a Lead source by its key (AGL-3516).\n const asked = useMemo(() => crmAskClauses(viewFilters, dealQueryClause), [viewFilters])\n const paged = useCrmListQuery<DealDoc>({\n scope: scope.scope,\n collection: CRM_COLLECTIONS.deals,\n visibleTo: scope.visibleTo,\n foldsScope,\n declaration: DEAL_LIST_DECLARATION,\n clauses: asked.clauses,\n search: searchWords,\n base: pipelineBase,\n prefixSearch: DEAL_PREFIX_SEARCH,\n enabled: view === 'table' && Boolean(pipeline),\n })\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [paged.plan, paged.page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n const exportFilter = useMemo(\n () => (paged.plan.served.length || paged.plan.searched ? { label: 'what the list shows', plan: paged.plan } : null),\n [paged.plan],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Type and Lead source, optional, in their lists' order (AGL-3516).\n picklistColumn('type', 'Type', typeList.picklist),\n picklistColumn('leadSource', 'Lead source', leadSourceList.picklist),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs, typeList.picklist, leadSourceList.picklist],\n )\n /** The table's rows: the query's page. */\n const tableRows = paged.rows\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313),\n * every one of them asked of the query (AGL-3321).\n */\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n viewFilters\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const types = picklistFilterOptions(typeList.picklist, 'No type')\n const sources = picklistFilterOptions(leadSourceList.picklist, 'No lead source')\n return {\n status: DEAL_STATUS_FILTER_OPTIONS,\n type: [...types, ...stale('type', types)],\n leadSource: [...sources, ...stale('leadSource', sources)],\n }\n }, [viewFilters, typeList.picklist, leadSourceList.picklist])\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, DEAL_GRID_FILTER_FIELDS, filterOptions, DEAL_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, DEAL_HIDDEN_COLUMNS)\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status', 'type', 'leadSource'],\n codecs: DEAL_FILTER_CODECS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: DEAL_GRID_FILTER_FIELDS,\n headers: DEAL_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_DEALS_RESOURCE} noun=\"deals\" hostId={hostId} mappingZone=\"deals\" />\n <CrmExportButton resource={CRM_DEALS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Figure label=\"Open deals\" value={summary.openCount.toLocaleString()} />\n <Figure label=\"Pipeline value\" value={formatAmountByCurrency(summary.valueByCurrency)} />\n <Figure label=\"Weighted value\" value={formatAmountByCurrency(summary.weightedByCurrency)} />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <ListFilterChips\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={paged.plan.notices} />\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tableRows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel and the search are the query's (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n loading={paged.status === 'loading'}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={\n searching\n ? `No deals match “${searchKey.trim()}”`\n : statusFilter === 'all'\n ? 'No deals yet'\n : `No ${statusFilter} deals`\n }\n noRowsDescription={\n statusFilter === 'all' && !searching\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' && !searching ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\n/** An optional column of a picklist field: its label, sorted in the list's order. */\nfunction picklistColumn(field: 'type' | 'leadSource', headerName: string, picklist: CrmPicklist): GridColDef {\n return {\n field,\n headerName,\n flex: 0.8,\n minWidth: 130,\n valueGetter: (_value: unknown, row: DealDoc) => String(row[field] ?? ''),\n sortComparator: (a: string, b: string) =>\n crmPicklistRank(picklist, a) - crmPicklistRank(picklist, b) || a.localeCompare(b),\n }\n}\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["CRM_COLLECTIONS","crmPicklistRank","dealStageById","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","ListQueryNotices","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","DEAL_FILTER_CODECS","DEAL_LIST_DECLARATION","DEAL_LIST_FILTER_FIELDS","DEAL_PICKLIST_FILTER_NONE","DEAL_PREFIX_SEARCH","dealPipelineBase","dealQueryClause","useCrmFoldsScope","useCrmListQuery","useCrmPicklist","crmAskClauses","crmQueryRefusals","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","useDealsByStatus","useOrgMemberDirectory","usePipeline","crmRoutes","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","CrmExportButton","CrmImportButton","CRM_DEALS_RESOURCE","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","DEAL_GRID_FILTER_HEADERS","status","column","type","leadSource","DEAL_STATUS_FILTER_OPTIONS","map","value","label","DEAL_HIDDEN_COLUMNS","picklistFilterOptions","picklist","none","values","active","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","typeList","leadSourceList","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","closedPipelineId","won","lost","searchWords","setSearchWords","searching","some","word","trim","searchKey","join","foldsScope","pipelineBase","asked","paged","collection","deals","declaration","clauses","search","base","prefixSearch","enabled","Boolean","summary","data","moving","setMoving","Set","track","deal","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","plan","page","exportFilter","served","length","searched","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","filter","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","pipelineById","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","expectedCloseAtMs","toLocaleDateString","picklistColumn","tableRows","rows","filterOptions","stale","known","op","flatMap","split","option","types","sources","filterColumns","grid","gridFilter","onChange","setFilters","selectFields","codecs","words","refused","fields","headers","options","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","startIcon","path","disabled","onClick","useFlexGap","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","loading","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","String","sortComparator","a","b","localeCompare","component"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAIEA,eAAe,EACfC,eAAe,EACfC,aAAa,EACbC,eAAe,EACfC,cAAc,QACT,eAAc;AACrB,SACEC,aAAa,EACbC,OAAO,EACPC,aAAa,EACbC,oBAAoB,QACf,yBAAwB;AAC/B,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,OAAOC,sBAAsB,+DAA8D;AAC3F,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,cAAc,EAAEC,cAAc,QAAQ,wBAAoB;AACnE,SAASC,cAAc,EAAEC,sBAAsB,QAAQ,uBAAmB;AAC1E,SACEC,8BAA8B,EAC9BC,+BAA+B,EAC/BC,kBAAkB,QACb,gCAA4B;AACnC,SACEC,kBAAkB,EAClBC,qBAAqB,EACrBC,uBAAuB,EACvBC,yBAAyB,EACzBC,kBAAkB,EAClBC,gBAAgB,EAChBC,eAAe,QACV,+BAA2B;AAClC,SAASC,gBAAgB,EAAEC,eAAe,QAAQ,iCAA6B;AAC/E,SAASC,cAAc,QAAQ,+BAA2B;AAC1D,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,6BAAyB;AACzE,OAAOC,qBAAqB,8DAA6D;AACzF,SAEEC,qBAAqB,QAChB,8CAA6C;AACpD,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,OAAOC,qBAAqB,yBAAqB;AACjD,OAAOC,yBAAyB,wDAAuD;AACvF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,KAAK,EACLC,MAAM,EACNC,IAAI,EACJC,gBAAgB,EAChBC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,QACL,gBAAe;AAEtB,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,WAAW,QAAQ,4BAAwB;AACpD,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,gBAAgB,QACX,wBAAoB;AAC3B,SAASC,qBAAqB,QAAQ,uCAAmC;AACzE,SAASC,WAAW,QAAQ,2BAAuB;AACnD,SAASC,SAAS,QAAQ,yBAAqB;AAC/C,SACEC,YAAY,EACZC,kBAAkB,EAElBC,sBAAsB,EACtBC,WAAW,QACN,+BAA2B;AAClC,SAASC,kBAAkB,QAAQ,8BAA0B;AAC7D,SAASC,SAAS,QAAQ,kBAAc;AACxC,SAASC,cAAc,QAAQ,wBAAoB;AACnD,SAASC,eAAe,EAAEC,eAAe,QAAQ,4BAAwB;AACzE,SAASC,kBAAkB,QAAQ,wBAAoB;AACvD,OAAOC,kBAAkB,sBAAkB;AAC3C,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,SAASC,WAAW,QAAQ,oBAAgB;AAC5C,SAASC,eAAe,QAAQ,wBAAoB;AAKpD,uGAAuG,GACvG,MAAMC,0BAA0BtD;AAChC,MAAMuD,2BAA6D;IACjEC,QAAQ;IACR,CAAC7D,+BAA+B8D,MAAM,CAAC,EAAE7D;IACzC8D,MAAM;IACNC,YAAY;AACd;AACA,MAAMC,6BAA6B,AAAC;IAAC;IAAQ;IAAO;CAAO,CAAWC,GAAG,CAAC,CAACC,QAAW,CAAA;QACpFA;QACAC,OAAOtB,kBAAkB,CAACqB,MAAM;IAClC,CAAA;AAEA,6GAA6G,GAC7G,MAAME,sBAAyD;IAAEN,MAAM;IAAOC,YAAY;AAAM;AAEhG,uGAAuG,GACvG,SAASM,sBAAsBC,QAAqB,EAAEC,IAAY;IAChE,OAAO;WACFD,SAASE,MAAM,CAACP,GAAG,CAAC,CAACC,QAAW,CAAA;gBACjCA,OAAOA,MAAMC,KAAK;gBAClBA,OAAOD,MAAMO,MAAM,GAAGP,MAAMC,KAAK,GAAG,GAAGD,MAAMC,KAAK,CAAC,WAAW,CAAC;YACjE,CAAA;QACA;YAAED,OAAO7D;YAA2B8D,OAAOI;QAAK;KACjD;AACH;AAGA,0FAA0F,GAC1F,MAAMG,sBAAyC;IAAC5F;CAAgB;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CC,GACD,OAAO,SAAS6F,aAAaC,KAA6B;QAgBrDC,2BAoiBUC;IAnjBb,MAAM,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGL;IAClC,MAAMM,SAASvC,UAAUsC,mBAAAA,WAAY;IACrC,MAAME,SAASrD;IACf,MAAM,EAAEsD,eAAe,EAAE,GAAGjE;IAC5B,MAAM2D,QAAQ1C,YAAY;QAAE2C;QAAQC;IAAI;IACxC,MAAMK,gBAAgB3C,YAAYoC,MAAMQ,KAAK,EAAE;QAC7CP;QACAC,GAAG,EAAGA,cAAAA,MAAO;IACf;IACA,MAAM,EAAEO,eAAe,EAAE,GAAGF;IAC5B,MAAM,CAACR,YAAYW,cAAc,GAAGtD,SAAwB;IAC5D,oEAAoE;IACpE,wEAAwE;IACxE,kDAAkD;IAClD,MAAMuD,YACHZ,OAAAA,aAAaU,gBAAgBG,IAAI,CAAC,CAACC,QAAUA,MAAMC,GAAG,KAAKf,cAAc,gBAAzEA,OACDQ,cAAcI,QAAQ;IACxB,MAAMI,SAASpD,sBAAsBqC,MAAMQ,KAAK;IAChD,MAAMQ,MAAMzD,gBAAgB0C;IAC5B,MAAMgB,QAAQ9D,QAAQ,IAAM+D,KAAKC,GAAG,IAAI,EAAE;IAC1C,sEAAsE;IACtE,MAAMC,aAAa/D,2BAA2B2C,MAAMQ,KAAK,EAAE;IAC3D,qFAAqF;IACrF,MAAMa,WAAWxF,eAAe,mBAAmBmE,MAAMQ,KAAK;IAC9D,MAAMc,iBAAiBzF,eAAe,cAAcmE,MAAMQ,KAAK;IAE/D,MAAM,CAACe,MAAMC,QAAQ,GAAGpE,SAAe;IACvC,MAAM,CAACqE,gBAAgBC,kBAAkB,GAAGtE,SAAS;IACrD;;;;;GAKC,GACD,MAAMuE,QAAQhH,gBAAgB;QAC5BiH,SAAS;QACT3B;QACAC;QACAC,QAAQ,EAAEA,mBAAAA,WAAY;IACxB;IACA,MAAM0B,eAA6B1E,QAAQ;YAC3BwE;QAAd,MAAMvC,SAAQuC,4BAAAA,MAAMG,KAAK,CAACC,OAAO,CAACnB,IAAI,CAAC,CAACoB,SAAWA,OAAOC,KAAK,KAAK,8BAAtDN,0BAAiEvC,KAAK;QACpF,OAAOA,UAAU,UAAUA,UAAU,SAASA,UAAU,SAASA,QAAQ;IAC3E,GAAG;QAACuC,MAAMG,KAAK,CAACC,OAAO;KAAC;IACxB,wEAAwE;IACxE,MAAMG,cAAcP,MAAMG,KAAK,CAACC,OAAO;IACvC7E,UAAU;QACR,IAAIyE,MAAMQ,SAAS,EAAEX,QAAQ;IAC/B,GAAG;QAACG,MAAMQ,SAAS;KAAC;IAEpB,0EAA0E;IAC1E,gEAAgE;IAChE,MAAMC,OAAO1E,iBACXsC,MAAMQ,KAAK,EACXR,MAAMqC,SAAS,WACf1B,4BAAAA,SAAUG,GAAG,oBAAI,MACjB,QACArD;IAEF,MAAM6E,mBACJf,SAAS,WAAWE,0BAAkBd,4BAAAA,SAAUG,GAAG,oBAAI,OAAQ;IACjE,MAAMyB,MAAM7E,iBAAiBsC,MAAMQ,KAAK,EAAER,MAAMqC,SAAS,EAAEC,kBAAkB,OAAO9E;IACpF,MAAMgF,OAAO9E,iBAAiBsC,MAAMQ,KAAK,EAAER,MAAMqC,SAAS,EAAEC,kBAAkB,QAAQ9E;IACtF;;;;;GAKC,GACD,MAAM,CAACiF,aAAaC,eAAe,GAAGtF,SAAmB,EAAE;IAC3D,MAAMuF,YAAYF,YAAYG,IAAI,CAAC,CAACC,OAASA,KAAKC,IAAI;IACtD,MAAMC,YAAYN,YAAYO,IAAI,CAAC;IACnC,MAAMC,aAAatH,iBAAiBqE,MAAMQ,KAAK,EAAER,MAAMqC,SAAS;IAChE,MAAMa,eAAe/F,QAAQ;;eAAM1B,yBAAiBkF,4BAAAA,SAAUG,GAAG,mBAAI;OAAO;QAACH,4BAAAA,SAAUG,GAAG;KAAC;IAC3F,uEAAuE;IACvE,0CAA0C;IAC1C,MAAMqC,QAAQhG,QAAQ,IAAMrB,cAAcoG,aAAaxG,kBAAkB;QAACwG;KAAY;IACtF,MAAMkB,QAAQxH,gBAAyB;QACrCoE,OAAOA,MAAMA,KAAK;QAClBqD,YAAYxJ,gBAAgByJ,KAAK;QACjCjB,WAAWrC,MAAMqC,SAAS;QAC1BY;QACAM,aAAalI;QACbmI,SAASL,MAAMK,OAAO;QACtBC,QAAQhB;QACRiB,MAAMR;QACNS,cAAcnI;QACdoI,SAASrC,SAAS,WAAWsC,QAAQlD;IACvC;IAEA,MAAMmD,UAAU3G,QAAQ,IAAMW,aAAasE,KAAK2B,IAAI,EAAEpD,WAAW;QAACyB,KAAK2B,IAAI;QAAEpD;KAAS;IAEtF;;;GAGC,GACD,MAAM,CAACqD,QAAQC,UAAU,GAAG7G,SAA8B,IAAM,IAAI8G;IACpE,MAAMC,QAAQlH,YACZ,OAAOmH,MAAeC,SAAiCC;QACrDL,UAAU,CAACM,UAAY,IAAIL,IAAIK,SAASC,GAAG,CAACJ,KAAKtD,GAAG;QACpD,IAAI;YACF,MAAMuD;YACN/D,gBAAgBgE,MAAM;gBAAEG,SAAS;gBAAWC,SAAS;YAAM;YAC3D,OAAO;QACT,EAAE,OAAOC,OAAO;YACdrE,gBACEqE,iBAAiBC,QAAQD,MAAME,OAAO,GAAG,gCACzC;gBAAEJ,SAAS;gBAAWK,gBAAgB;YAAK;YAE7C,OAAO;QACT,SAAU;YACRb,UAAU,CAACM;gBACT,MAAMQ,OAAO,IAAIb,IAAIK;gBACrBQ,KAAKC,MAAM,CAACZ,KAAKtD,GAAG;gBACpB,OAAOiE;YACT;QACF;IACF,GACA;QAACzE;KAAgB;IAEnB,MAAM2E,aAAahI,YACjB,CAACmH,MAAec;;QACd,MAAMC,QAAQpL,cAAc4G,UAAUuE;QACtC,KAAKf,MACHC,MACA,IAAMpD,IAAIoE,WAAW,CAAChB,MAAMc,UAC5B,CAAC,SAAS,UAAEC,yBAAAA,MAAOE,IAAI,mBAAI,SAAS;IAExC,GACA;QAACrE;QAAKL;QAAUwD;KAAM;IAExB,MAAMmB,YAAYrI,YAChB,CAACmH,OAAkB,KAAKD,MAAMC,MAAM,IAAMpD,IAAIuE,OAAO,CAACnB,OAAO,aAC7D;QAACpD;QAAKmD;KAAM;IAEd,MAAM,CAACqB,QAAQC,UAAU,GAAGrI,SAAyB;IACrD,MAAMsI,aAAazI,YAAY,CAACmH,OAAkBqB,UAAUrB,OAAO,EAAE;IACrE,MAAMuB,cAAc1I,YAClB,CAAC2I;QACC,MAAMxB,OAAOoB;QACb,IAAI,CAACpB,MAAM;QACXqB,UAAU;QACV,KAAKtB,MAAMC,MAAM,IAAMpD,IAAI6E,QAAQ,CAACzB,MAAMwB,SAAS;IACrD,GACA;QAAC5E;QAAKwE;QAAQrB;KAAM;IAGtB,MAAM2B,WAAW7I,YACf,CAACmH,OAAkB/D,OAAO0F,IAAI,CAAC3F,OAAOgE,IAAI,CAACA,KAAKtD,GAAG,IACnD;QAACT;QAAQD;KAAO;IAGlB,MAAM,CAAC4F,UAAUC,YAAY,GAAG7I,SAAS;IACzC,MAAM,CAAC8I,mBAAmBC,qBAAqB,GAAG/I,SAAS;IAE3D,MAAM,CAACgJ,aAAaC,eAAe,GAAGjJ,SAAmB,EAAE;IAC3D,qEAAqE;IACrE,+DAA+D;IAC/DF,UAAU,IAAMmJ,eAAe,EAAE,GAAG;QAACjD,MAAMkD,IAAI;QAAElD,MAAMmD,IAAI;KAAC;IAC5D,0EAA0E;IAC1E,oCAAoC;IACpC,MAAMC,eAAerJ,QACnB,IAAOiG,MAAMkD,IAAI,CAACG,MAAM,CAACC,MAAM,IAAItD,MAAMkD,IAAI,CAACK,QAAQ,GAAG;YAAEtH,OAAO;YAAuBiH,MAAMlD,MAAMkD,IAAI;QAAC,IAAI,MAC9G;QAAClD,MAAMkD,IAAI;KAAC;IAGd,MAAMM,UAAwBzJ,QAC5B,IAAM;YACJ;gBACE8E,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAAoB;oBACpC,MAAMC,WAAW;wBAACD,IAAIE,WAAW;wBAAEF,IAAIG,WAAW;qBAAC,CAACC,MAAM,CAACxD,SAASb,IAAI,CAAC;oBACzE,qBACE,MAACrG;wBAAM2K,IAAI;4BAAEC,gBAAgB;4BAAUC,YAAY;wBAAK;;0CACtD,KAACzK;gCAAW0H,SAAQ;gCAAQ6C,IAAI;oCAAEE,YAAY;gCAAK;0CAChDP,IAAIQ,KAAK,IAAI;;4BAEfP,yBACC,KAACnK;gCAAW0H,SAAQ;gCAAUiD,OAAM;gCAAiBC,MAAM;gCAACL,IAAI;oCAAEE,YAAY;gCAAK;0CAChFN;iCAED;;;gBAGV;YACF;YACA;gBACEjF,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVa,aAAa,CAACC,QAAiBZ;;wBAC7BlN;oCAAAA,iBAAAA,cAAcwG,cAAcuH,YAAY,CAACb,IAAIlH,UAAU,GAAGkH,IAAI/B,OAAO,sBAArEnL,eAAwEsL,IAAI,mBAC5E4B,IAAI/B,OAAO;;YACf;YACA;gBACEjD,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVgB,OAAO;gBACPC,aAAa;gBACbJ,aAAa,CAACC,QAAiBZ;wBAAiBA;4BAAAA,mBAAAA,IAAIgB,WAAW,YAAfhB,mBAAmB;;gBACnED,YAAY,CAAC,EAAEC,GAAG,EAAoB,GACpC,OAAOA,IAAIgB,WAAW,KAAK,WAAWhK,YAAYgJ,IAAIgB,WAAW,EAAEhB,IAAIiB,QAAQ,IAAI;YACvF;YACA;gBACEjG,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVa,aAAa,CAACC,QAAiBZ,MAAiBlG,OAAOoH,MAAM,CAAClB,IAAImB,QAAQ;gBAC1EpB,YAAY,CAAC,EAAEC,GAAG,EAAoB;oBACpC,MAAM5H,QAAQ0B,OAAOoH,MAAM,CAAClB,IAAImB,QAAQ;oBACxC,OAAO/I,sBACL,MAAC1C;wBAAM0L,WAAU;wBAAMC,SAAS;wBAAGhB,IAAI;4BAAEiB,YAAY;wBAAS;;0CAC5D,KAAC7J;gCAAYW,OAAOA;gCAAOmJ,MAAM;;0CACjC,KAACzL;gCAAW0H,SAAQ;gCAAQkD,MAAM;0CAC/BtI;;;uCAIL,KAACtC;wBAAW0H,SAAQ;wBAAQiD,OAAM;kCAC/B;;gBAGP;YACF;YACA;gBACEzF,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACV/H,MAAM;gBACN4I,aAAa,CAACC,QAAiBZ,MAC7B,OAAOA,IAAIwB,iBAAiB,KAAK,WAAW,IAAIvH,KAAK+F,IAAIwB,iBAAiB,IAAI;gBAChFzB,YAAY,CAAC,EAAEC,GAAG,EAAoB,GACpC,OAAOA,IAAIwB,iBAAiB,KAAK,WAC7B,IAAIvH,KAAK+F,IAAIwB,iBAAiB,EAAEC,kBAAkB,KAClD;YACR;YACA;gBACEzG,OAAO;gBACP4E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAAoB;wBAG3BlJ;yCAFT,KAACvB;wBACCgM,MAAK;wBACLnJ,KAAK,GAAEtB,iCAAAA,kBAAkB,CAACkJ,IAAInI,MAAM,CAAC,YAA9Bf,iCAAkCkJ,IAAInI,MAAM;wBACnD4I,OAAOT,IAAInI,MAAM,KAAK,QAAQ,YAAYmI,IAAInI,MAAM,KAAK,SAAS,YAAY;wBAC9E2F,SAASwC,IAAInI,MAAM,KAAK,SAAS,aAAa;;;YAGpD;YACA,kEAAkE;YAClE3D,mBAAmB8F;YACnB,iFAAiF;YACjF0H,eAAe,QAAQ,QAAQtH,SAAS7B,QAAQ;YAChDmJ,eAAe,cAAc,eAAerH,eAAe9B,QAAQ;YACnE,wDAAwD;eACrDtB,mBAAmBkD,WAAWzB,MAAM;SACxC,EACD;QAACY;QAAeQ;QAAQK,WAAWzB,MAAM;QAAEsB;QAAOI,SAAS7B,QAAQ;QAAE8B,eAAe9B,QAAQ;KAAC;IAE/F,wCAAwC,GACxC,MAAMoJ,YAAYxF,MAAMyF,IAAI;IAC5B;;;;GAIC,GACD;;;GAGC,GACD,MAAMC,gBAAgB3L,QAA4C;QAChE,MAAM4L,QAAQ,CAAC9G,OAAe+G,QAC5B9G,YACGmF,MAAM,CAAC,CAACrF,SAAWA,OAAOC,KAAK,KAAKA,SAASD,OAAOiH,EAAE,KAAK,WAC3DC,OAAO,CAAC,CAAClH,SAAWA,OAAO5C,KAAK,CAAC+J,KAAK,CAAC,MACvChK,GAAG,CAAC,CAACC,QAAUA,MAAM0D,IAAI,IACzBuE,MAAM,CAAC,CAACjI,QAAUA,SAAS,CAAC4J,MAAMpG,IAAI,CAAC,CAACwG,SAAWA,OAAOhK,KAAK,KAAKA,QACpED,GAAG,CAAC,CAACC,QAAW,CAAA;oBAAEA;oBAAOC,OAAOD;gBAAM,CAAA;QAC3C,MAAMiK,QAAQ9J,sBAAsB8B,SAAS7B,QAAQ,EAAE;QACvD,MAAM8J,UAAU/J,sBAAsB+B,eAAe9B,QAAQ,EAAE;QAC/D,OAAO;YACLV,QAAQI;YACRF,MAAM;mBAAIqK;mBAAUN,MAAM,QAAQM;aAAO;YACzCpK,YAAY;mBAAIqK;mBAAYP,MAAM,cAAcO;aAAS;QAC3D;IACF,GAAG;QAACpH;QAAab,SAAS7B,QAAQ;QAAE8B,eAAe9B,QAAQ;KAAC;IAC5D,MAAM+J,gBAAgBpM,QACpB,IAAMlB,sBAAsB2K,SAAShI,yBAAyBkK,eAAejK,2BAC7E;QAAC+H;QAASkC;KAAc;IAE1B,MAAMU,OAAO5O,eAAe+G,OAAO4H,eAAejK;IAClD,MAAMmK,aAAavN,kBAAkB;QACnCsH,SAAStB;QACTwH,UAAU/H,MAAMgI,UAAU;QAC1BC,cAAc;YAAC;YAAU;YAAQ;SAAa;QAC9CC,QAAQzO;QACRqI,QAAQ;YAAEqG,OAAOrH;YAAaiH,UAAUhH;QAAe;IACzD;IACA,qEAAqE;IACrE,MAAMqH,UAAU5M,QACd,IACEpB,iBAAiBqH,MAAMkD,IAAI,EAAEnD,OAAO;YAClC6G,QAAQpL;YACRqL,SAASpL;YACTqL,SAASpB;QACX,IACF;QAAC1F,MAAMkD,IAAI;QAAEnD;QAAO2F;KAAc;IAGpC,MAAMqB,QAAQnK,MAAMoK,KAAK,IAAI,CAACpK,MAAMQ,KAAK;IACzC,MAAM6J,kBACJ,CAACF,SAAU5J,CAAAA,cAAczB,MAAM,KAAK,aAAayB,cAAc+J,OAAO,IAAK,CAAC3J,YAAYJ,cAAczB,MAAM,KAAK,OAAO;IAE1H,qBACE;;0BACE,KAACxE;gBACCiQ,QAAQ;gBACRC,MAAMvQ,eAAe,SAAS;oBAAEwQ,QAAQ;gBAA2B;gBACnEC,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACX,8DAA8D;oBAC9DC,sBACE,MAAChQ;;0CACC,KAACyD;gCAAgBwM,UAAUvM;gCAAoBwM,MAAK;gCAAQ9K,QAAQA;gCAAQ+K,aAAY;;0CACxF,KAAC3M;gCAAgByM,UAAUvM;gCAAoB0B,QAAQA;gCAAQoH,QAAQb;;0CACvE,KAACjK;gCACCiM,MAAK;gCACLyC,yBAAW,KAAC1Q;oCAAQ2Q,MAAMhR,cAAcgR,IAAI;oCAAE1C,MAAM;;gCACpD2C,UAAU,CAACnL,MAAMQ,KAAK,IAAID,cAAczB,MAAM,KAAK;gCACnDsM,SAAS,IAAMjF,qBAAqB;0CAEnC;;0CAEH,KAAC5J;gCACCiM,MAAK;gCACL/D,SAAQ;gCACRwG,yBAAW,KAAC1Q;oCAAQ2Q,MAAM/Q,QAAQ+Q,IAAI;oCAAE1C,MAAM;;gCAC9C2C,UAAU,CAACxK,YAAY,CAACX,MAAMQ,KAAK;gCACnC4K,SAAS,IAAMnF,YAAY;0CAE1B;;;;gBAIT;0BAEA,cAAA,MAACtJ;oBAAM2L,SAAS;;sCACd,MAAC3L;4BACC0O,UAAU;4BACVhD,WAAU;4BACVC,SAAS;4BACThB,IAAI;gCAAEiB,YAAY;gCAAY+C,UAAU;gCAAQC,QAAQ;4BAAE;;8CAE1D,KAACC;oCAAOnM,OAAM;oCAAaD,OAAO0E,QAAQ2H,SAAS,CAACC,cAAc;;8CAClE,KAACF;oCAAOnM,OAAM;oCAAiBD,OAAOpB,uBAAuB8F,QAAQ6H,eAAe;;8CACpF,KAACH;oCAAOnM,OAAM;oCAAiBD,OAAOpB,uBAAuB8F,QAAQ8H,kBAAkB;;8CACvF,KAACjP;oCAAM2K,IAAI;wCAAER,MAAM;oCAAE;;gCAEpBrG,gBAAgBiG,MAAM,GAAG,kBACxB,KAAC9J;oCACCiP,MAAM;oCACNrD,MAAK;oCACLnJ,OAAM;oCACND,KAAK,WAAEuB,4BAAAA,SAAUG,GAAG,oBAAI;oCACxB4I,UAAU,CAACoC,QAAUpL,cAAcoL,MAAMC,MAAM,CAAC3M,KAAK;oCACrDkI,IAAI;wCAAEP,UAAU;oCAAI;oCACpBiF,WAAW;wCAAEH,QAAQ;4CAAE,cAAc;wCAAW;oCAAE;8CAEjDpL,gBAAgBtB,GAAG,CAAC,CAAC0B,sBACpB,KAACnE;4CAAyB0C,OAAOyB,MAAMC,GAAG;sDACvCD,MAAMwE,IAAI;2CADExE,MAAMC,GAAG;qCAK1B;8CACJ,MAAChE;oCACCmP,SAAS;oCACTzD,MAAK;oCACLd,OAAM;oCACNtI,OAAOmC;oCACPmI,UAAU,CAACwC,QAAQnH;wCACjB,IAAIA,MAAMvD,QAAQuD;oCACpB;oCACAoH,cAAW;;sDAEX,MAACtP;4CAAauC,OAAM;4CAAQ+M,cAAW;;8DACrC,KAAC5R;oDAAQ2Q,MAAM7Q,qBAAqB6Q,IAAI;oDAAE1C,MAAM;;8DAChD,KAACzL;oDAAW0H,SAAQ;oDAAS6C,IAAI;wDAAE8E,IAAI;oDAAI;8DACxC;;;;sDAGL,MAACvP;4CAAauC,OAAM;4CAAQ+M,cAAW;;8DACrC,KAAC5R;oDAAQ2Q,MAAM9Q,cAAc8Q,IAAI;oDAAE1C,MAAM;;8DACzC,KAACzL;oDAAW0H,SAAQ;oDAAS6C,IAAI;wDAAE8E,IAAI;oDAAI;8DACxC;;;;;;;;wBAMRjC,sBACC,KAAC7N;4BAAM+P,UAAS;sCACb;6BAED9L,cAAczB,MAAM,KAAK,wBAC3B,KAACxC;4BAAM+P,UAAS;sCACb;6BAEDhC,gCACF,MAAC1N;4BAAM0L,WAAU;4BAAMC,SAAS;4BAAGhB,IAAI;gCAAEiB,YAAY;4BAAS;;8CAC5D,KAAC9L;oCAAiB+L,MAAM;;8CACxB,KAACzL;oCAAW0H,SAAQ;oCAAQiD,OAAM;8CAC/BnH,cAAc+J,OAAO,GAAG,oCAAoC;;;6BAG/D/I,SAAS,WAAWZ,yBACtB;;gCACGyB,KAAKtD,MAAM,KAAK,aAAasD,KAAK2B,IAAI,CAAC2C,MAAM,KAAK,kBACjD,KAACtK;oCACCkQ,OAAO;oCACPjN,OAAO;oCACPkN,aAAa;oCACb1B,sBACE,KAACtO;wCACCiM,MAAK;wCACL/D,SAAQ;wCACRwG,yBAAW,KAAC1Q;4CAAQ2Q,MAAM/Q,QAAQ+Q,IAAI;4CAAE1C,MAAM;;wCAC9C2C,UAAU,CAACxK,YAAY,CAACX,MAAMQ,KAAK;wCACnC4K,SAAS,IAAMnF,YAAY;kDAE1B;;qCAIL;8CACJ,KAAC9H;oCACCwC,UAAUA;oCACV2C,OAAOlB,KAAK2B,IAAI;oCAChBxB,KAAKd,kBAAkBc,IAAIzD,MAAM,KAAK,YAAYyD,IAAIwB,IAAI,GAAG;oCAC7DvB,MAAMf,kBAAkBe,KAAK1D,MAAM,KAAK,YAAY0D,KAAKuB,IAAI,GAAG;oCAChEtC,gBAAgBA;oCAChB+K,gBAAgB,IAAM9K,kBAAkB,CAAC6C,UAAY,CAACA;oCACtDkI,UAAU1L,OAAOoH,MAAM;oCACvBnE,QAAQA;oCACR0I,QAAQ5G;oCACR6G,QAAQ1H;oCACR2H,OAAOtH;oCACPuH,QAAQnH;oCACRzE,OAAOA;;;2CAIX,MAACtE;4BAAM2L,SAAS;;8CAMd,MAACxN;oCAAeuE,OAAM;;sDACpB,KAAClD;4CAAgB2Q,YAAYnL;4CAAOoL,UAAS;;sDAC7C,KAAC/Q;4CACCgO,QAAQpL;4CACRqL,SAASpL;4CACT2E,SAAStB;4CACTwH,UAAU/H,MAAMgI,UAAU;4CAC1BO,SAASpB;4CACTkE,aAAa;;;;8CAGjB,KAACtS;oCAAiBqP,SAASA;oCAASkD,SAAS7J,MAAMkD,IAAI,CAAC2G,OAAO;;8CAC7D;;sDACE,KAACzO;4CACCyB,QAAQA;4CACRD,OAAOA,MAAMA,KAAK;4CAClB6I,MAAMD;4CACNsE,UAAU9G;4CACV+G,kBAAkB9G;4CAClByB,cAAcvH,cAAcuH,YAAY;4CACxC/G,QAAQA;4CACRC,KAAKA;;sDAEP,KAAChG;4CAAuBoE,OAAOoK,KAAK4D,WAAW;sDAC7C,cAAA,KAAC3S;gDACCoO,MAAMD;gDACNhC,SAAS4C,KAAK5C,OAAO;gDACrByG,OAAOtS;gDACPuS,YAAY;oDAAEJ,UAAU9G;oDAAasD,UAAUrD;gDAAe;gDAC9DqG,QAAQ,CAACa,KAAKtG,MAAQnB,SAASmB;gDAC/B,uDAAuD;gDACvDuG,YAAW;gDACXC,aAAahE,WAAWgE,WAAW;gDACnCC,qBAAqBjE,WAAWiE,mBAAmB;gDACnDC,WAAW;gDACXC,SAASxK,MAAMtE,MAAM,KAAK;gDAC1B,yDAAyD;gDACzD,8CAA8C;gDAC9C+O,aACElL,YACI,CAAC,gBAAgB,EAAEI,UAAUD,IAAI,GAAG,CAAC,CAAC,GACtCjB,iBAAiB,QACf,iBACA,CAAC,GAAG,EAAEA,aAAa,MAAM,CAAC;gDAElCiM,mBACEjM,iBAAiB,SAAS,CAACc,YACvB,4EACAoL;gDAENC,cACEnM,iBAAiB,SAAS,CAACc,0BACzB,KAACpG;oDACCiM,MAAK;oDACL/D,SAAQ;oDACRwG,yBAAW,KAAC1Q;wDAAQ2Q,MAAM/Q,QAAQ+Q,IAAI;wDAAE1C,MAAM;;oDAC9C2C,UAAU,CAACxK,YAAY,CAACX,MAAMQ,KAAK;oDACnC4K,SAAS,IAAMnF,YAAY;8DAE1B;qDAED8H;gDAEN,0DAA0D;gDAC1DE,uBAAuBzE,KAAKyE,qBAAqB;gDACjDC,+BAA+B1E,KAAK0E,6BAA6B;gDACjEC,WAAW3E,KAAK2E,SAAS;gDACzBC,mBAAmB5E,KAAK4E,iBAAiB;gDACzCC,UAAU;;;sDAGd,KAAC7T;4CACC+L,MAAMnD,MAAMmD,IAAI;4CAChB+H,UAAUlL,MAAMkL,QAAQ;4CACxBC,UAAUnL,MAAMyF,IAAI,CAACnC,MAAM;4CAC3B8H,SAASpL,MAAMoL,OAAO;4CACtBC,cAAcrL,MAAMsL,OAAO;4CAC3BC,kBAAkBvL,MAAMwL,WAAW;;;;;;;;;0BAOjD,KAACxQ;gBACCgE,MAAM4D;gBACN6I,SAAS,IAAM5I,YAAY;gBAC3BhG,QAAQA;gBACRC,KAAKA;gBACL4O,WAAWrO;gBACXsO,iBAAiBpO;;0BAEnB,KAAChC;gBACCyD,MAAM8D;gBACN2I,SAAS,IAAM1I,qBAAqB;gBACpC3F,KAAK,GAAER,eAAAA,MAAMQ,KAAK,YAAXR,eAAe;gBACtB,gEAAgE;gBAChE,8DAA8D;gBAC9D,gEAAgE;gBAChE,yBAAyB;gBACzBC,QAAQD,MAAMgP,YAAY;gBAC1BF,WAAWvO,cAAcuO,SAAS;gBAClCG,WAAW1O,cAAc0O,SAAS;gBAClCC,YAAY3O,cAAczB,MAAM,KAAK;gBACrCqQ,iBAAiBnP,MAAMqC,SAAS;gBAChC+M,cAAcpP,MAAMqP,KAAK,KAAK,SAASrP,MAAMoP,YAAY,GAAGxP;;0BAE9D,KAACnB;gBACC2D,MAAMyB,QAAQ2B;gBACd8J,SAAS,WAAE9J,0BAAAA,OAAQiC,KAAK,oBAAI;gBAC5BoH,SAAS,IAAMpJ,UAAU;gBACzB8J,WAAW5J;;;;AAInB;AACA9F,aAAa2P,WAAW,GAAG;AAE3B,mFAAmF,GACnF,SAAS7G,eAAe1G,KAA4B,EAAE4E,UAAkB,EAAErH,QAAqB;IAC7F,OAAO;QACLyC;QACA4E;QACAC,MAAM;QACNC,UAAU;QACVa,aAAa,CAACC,QAAiBZ;gBAAwBA;mBAAPwI,QAAOxI,aAAAA,GAAG,CAAChF,MAAM,YAAVgF,aAAc;;QACrEyI,gBAAgB,CAACC,GAAWC,IAC1B9V,gBAAgB0F,UAAUmQ,KAAK7V,gBAAgB0F,UAAUoQ,MAAMD,EAAEE,aAAa,CAACD;IACnF;AACF;AAEA,SAASpE,OAAO1L,KAAuC;IACrD,qBACE,MAACnD;QAAM2K,IAAI;YAAEE,YAAY;QAAI;;0BAC3B,KAACzK;gBAAW0H,SAAQ;gBAAUiD,OAAM;0BACjC5H,MAAMT,KAAK;;0BAEd,KAACtC;gBAAW0H,SAAQ;gBAAKqL,WAAU;0BAChChQ,MAAMV,KAAK;;;;AAIpB;AACAoM,OAAOgE,WAAW,GAAG;AAErB,eAAe3P,aAAY"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.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 ConsolePluginPageProps,\n type CrmDealStatus,\n type CrmPicklist,\n CRM_COLLECTIONS,\n crmPicklistRank,\n dealStageById,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport {\n DEAL_FILTER_CODECS,\n DEAL_LIST_DECLARATION,\n DEAL_LIST_FILTER_FIELDS,\n DEAL_LIST_SORTS,\n DEAL_PICKLIST_FILTER_NONE,\n DEAL_PREFIX_SEARCH,\n dealPipelineBase,\n dealQueryClause,\n} from '../constants/deal-filters'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n useDealsByStatus,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_DEALS_RESOURCE } from '../transfer/fields'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the status, \"No next activity\", the Type and the Lead source. */\nconst DEAL_GRID_FILTER_FIELDS = DEAL_LIST_FILTER_FIELDS\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n type: 'Type',\n leadSource: 'Lead source',\n}\nconst DEAL_STATUS_FILTER_OPTIONS = (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n}))\n\n/** Salesforce's Type and Lead source columns are offered, not shown, until a view turns them on (AGL-3516). */\nconst DEAL_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { type: false, leadSource: false }\n\n/** A picklist's choices for its filter: every value the org keeps, inactive ones marked, then \"none\". */\nfunction picklistFilterOptions(picklist: CrmPicklist, none: string): ListFilterOption[] {\n return [\n ...picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: DEAL_PICKLIST_FILTER_NONE, label: none },\n ]\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by a query that carries its status, \"No next activity\" and search\n * word (AGL-3321); it is where the closed history is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Import and Export open the console's import wizard and export\n * dialog on `crm.deals` (AGL-3528): the export over the selection, the\n * list's own query, or every deal.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // Salesforce's Type and Lead source lists, for their columns and filters (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', scope.orgId)\n const leadSourceList = useCrmPicklist('leadSource', scope.orgId)\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // Every clause is the query's, the status and \"No next activity\" alike.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n /*\n * THE TABLE (AGL-3321): the pipeline's deals, every clause and the search\n * word on one query — the search reads the title's word prefixes every\n * deal writer stamps, so a deal on page four is found by any word of its\n * title — paged by that query, and read only while the table is showing.\n */\n const [searchWords, setSearchWords] = useState<string[]>([])\n const searching = searchWords.some((word) => word.trim())\n const searchKey = searchWords.join(' ')\n const foldsScope = useCrmFoldsScope(scope.orgId, scope.visibleTo)\n const pipelineBase = useMemo(() => dealPipelineBase(pipeline?.$id ?? null), [pipeline?.$id])\n // Each stored clause asked through the field its writer keeps — a Type\n // or a Lead source by its key (AGL-3516).\n const asked = useMemo(() => crmAskClauses(viewFilters, dealQueryClause), [viewFilters])\n // The header order the view asks (AGL-3680), read before the query it orders.\n const dealSort = useMemo(\n () => crmViewQuerySort(DEAL_LIST_SORTS, views.state.sort, DEAL_LIST_SORTS[0]),\n [views.state.sort],\n )\n const paged = useCrmListQuery<DealDoc>({\n scope: scope.scope,\n collection: CRM_COLLECTIONS.deals,\n visibleTo: scope.visibleTo,\n foldsScope,\n declaration: DEAL_LIST_DECLARATION,\n clauses: asked.clauses,\n search: searchWords,\n base: pipelineBase,\n sort: dealSort.sort,\n prefixSearch: DEAL_PREFIX_SEARCH,\n enabled: view === 'table' && Boolean(pipeline),\n })\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [paged.plan, paged.page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n const exportFilter = useMemo(\n () => (paged.plan.served.length || paged.plan.searched ? { label: 'what the list shows', plan: paged.plan } : null),\n [paged.plan],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Type and Lead source, optional, in their lists' order (AGL-3516).\n picklistColumn('type', 'Type', typeList.picklist),\n picklistColumn('leadSource', 'Lead source', leadSourceList.picklist),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs, typeList.picklist, leadSourceList.picklist],\n )\n /** The table's rows: the query's page. */\n const tableRows = paged.rows\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313),\n * every one of them asked of the query (AGL-3321).\n */\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n viewFilters\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const types = picklistFilterOptions(typeList.picklist, 'No type')\n const sources = picklistFilterOptions(leadSourceList.picklist, 'No lead source')\n return {\n status: DEAL_STATUS_FILTER_OPTIONS,\n type: [...types, ...stale('type', types)],\n leadSource: [...sources, ...stale('leadSource', sources)],\n }\n }, [viewFilters, typeList.picklist, leadSourceList.picklist])\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, DEAL_GRID_FILTER_FIELDS, filterOptions, DEAL_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, DEAL_HIDDEN_COLUMNS)\n /*\n * EVERY HEADER SORTS (AGL-3680). The grid used to sort the view's order\n * over the one page on screen while the footer paged the query, which read\n * as the whole pipeline's order. Deal, Amount, Expected close, Status and\n * Next activity sort the QUERY now (`DEAL_LIST_SORTS`); the rest sort the\n * page and say so — Stage in the pipeline's order, Type and Lead source in\n * the order the org keeps their values.\n */\n const pageSorts = useMemo(() => {\n const rank = (list: CrmPicklist, label: unknown) =>\n String(label ?? '').trim() ? crmPicklistRank(list, label) : null\n return {\n stageId: (row: DealDoc) => {\n const stages = pipelineState.pipelineById(row.pipelineId)?.stages ?? []\n const at = stages.findIndex((stage) => stage.id === row.stageId)\n return at === -1 ? null : at\n },\n type: (row: DealDoc) => rank(typeList.picklist, row.type),\n leadSource: (row: DealDoc) => rank(leadSourceList.picklist, row.leadSource),\n }\n }, [pipelineState, typeList.picklist, leadSourceList.picklist])\n const columnSort = useCrmColumnSort<DealDoc>({\n views,\n sorts: DEAL_LIST_SORTS,\n defaultSort: DEAL_LIST_SORTS[0],\n asked: dealSort,\n orderBy: paged.plan.orderBy,\n rows: tableRows,\n columns: grid.columns,\n pageSorts,\n })\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status', 'type', 'leadSource'],\n codecs: DEAL_FILTER_CODECS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: DEAL_GRID_FILTER_FIELDS,\n headers: DEAL_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_DEALS_RESOURCE} noun=\"deals\" hostId={hostId} mappingZone=\"deals\" />\n <CrmExportButton resource={CRM_DEALS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Figure label=\"Open deals\" value={summary.openCount.toLocaleString()} />\n <Figure label=\"Pipeline value\" value={formatAmountByCurrency(summary.valueByCurrency)} />\n <Figure label=\"Weighted value\" value={formatAmountByCurrency(summary.weightedByCurrency)} />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <ListFilterChips\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices\n refused={refused}\n notices={[...paged.plan.notices, ...columnSort.notices]}\n />\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={columnSort.rows}\n columnSort={columnSort}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel and the search are the query's (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n loading={paged.status === 'loading'}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={\n searching\n ? `No deals match “${searchKey.trim()}”`\n : statusFilter === 'all'\n ? 'No deals yet'\n : `No ${statusFilter} deals`\n }\n noRowsDescription={\n statusFilter === 'all' && !searching\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' && !searching ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns are the view's, controlled (AGL-2617); so is the\n // header order, which `columnSort` keeps in it (AGL-3680).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\n/** An optional column of a picklist field: its label, sorted in the list's order. */\nfunction picklistColumn(field: 'type' | 'leadSource', headerName: string, picklist: CrmPicklist): GridColDef {\n return {\n field,\n headerName,\n flex: 0.8,\n minWidth: 130,\n valueGetter: (_value: unknown, row: DealDoc) => String(row[field] ?? ''),\n sortComparator: (a: string, b: string) =>\n crmPicklistRank(picklist, a) - crmPicklistRank(picklist, b) || a.localeCompare(b),\n }\n}\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["CRM_COLLECTIONS","crmPicklistRank","dealStageById","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","ListQueryNotices","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","DEAL_FILTER_CODECS","DEAL_LIST_DECLARATION","DEAL_LIST_FILTER_FIELDS","DEAL_LIST_SORTS","DEAL_PICKLIST_FILTER_NONE","DEAL_PREFIX_SEARCH","dealPipelineBase","dealQueryClause","useCrmFoldsScope","useCrmListQuery","crmViewQuerySort","useCrmColumnSort","useCrmPicklist","crmAskClauses","crmQueryRefusals","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","useDealsByStatus","useOrgMemberDirectory","usePipeline","crmRoutes","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","CrmExportButton","CrmImportButton","CRM_DEALS_RESOURCE","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","DEAL_GRID_FILTER_HEADERS","status","column","type","leadSource","DEAL_STATUS_FILTER_OPTIONS","map","value","label","DEAL_HIDDEN_COLUMNS","picklistFilterOptions","picklist","none","values","active","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","typeList","leadSourceList","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","closedPipelineId","won","lost","searchWords","setSearchWords","searching","some","word","trim","searchKey","join","foldsScope","pipelineBase","asked","dealSort","sort","paged","collection","deals","declaration","clauses","search","base","prefixSearch","enabled","Boolean","summary","data","moving","setMoving","Set","track","deal","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","plan","page","exportFilter","served","length","searched","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","filter","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","pipelineById","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","expectedCloseAtMs","toLocaleDateString","picklistColumn","tableRows","rows","filterOptions","stale","known","op","flatMap","split","option","types","sources","filterColumns","grid","pageSorts","rank","list","String","stages","at","findIndex","id","columnSort","sorts","defaultSort","orderBy","gridFilter","onChange","setFilters","selectFields","codecs","words","refused","fields","headers","options","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","startIcon","path","disabled","onClick","useFlexGap","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","loading","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","sortComparator","a","b","localeCompare","component"],"mappings":"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"}
@@ -34,16 +34,18 @@ import { crmRoutes } from "../model/crm-routes.js";
34
34
  import { LeadProfileFields, leadProfileDraftBody, leadProfileDraftFrom } from "./lead-profile-fields.js";
35
35
  import { CrmCallButton, CrmPhoneLink } from "./crm-call-actions.js";
36
36
  import { CrmEmailStateChip } from "./crm-email-state-chip.js";
37
- import { CrmEmailGatewayChip } from "./crm-email-check.js";
37
+ import { CrmAccountLockChip, CrmEmailGatewayChip } from "./crm-email-check.js";
38
38
  import { CrmRecordChip, CrmRecordHeader } from "./crm-record-header.js";
39
39
  import { CrmSendEmailButton } from "./crm-send-email-button.js";
40
40
  import { LeadOwnerSelect } from "./lead-owner-select.js";
41
41
  import { LeadStatusChip } from "./lead-status-chip.js";
42
42
  import { leadStatusChoices, leadStatusMenuItems } from "./lead-status-options.js";
43
- import { crmClientListFields, CRM_CLIENT_SEARCH_FIELDS } from "../model/crm-list-query.js";
43
+ import { crmClientListFields, CRM_CLIENT_SEARCH_FIELDS, CRM_CLIENT_SORT_FIELDS } from "../model/crm-list-query.js";
44
44
  import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
45
45
  /** The Leads list's fields a profile save rewrites; the verdict key is the server's. */ const LEAD_CLIENT_LIST_FIELDS = [
46
46
  ...CRM_CLIENT_SEARCH_FIELDS,
47
+ // The Lead, Company and Title headers' sort keys (AGL-3680).
48
+ ...CRM_CLIENT_SORT_FIELDS,
47
49
  'leadSourceKey',
48
50
  // The lead source's group, read off the org's list (AGL-3577).
49
51
  'leadSourceDirection',
@@ -103,7 +105,7 @@ const NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX;
103
105
  * conversion, and the header's actions become links to what the conversion
104
106
  * made.
105
107
  */ export function LeadPropertiesCard(props) {
106
- var _lead_phone, _lead_mobilePhone, _lead_notes, _lead_email, _lead_email1, _lead_name, _lead_email2;
108
+ var _lead_phone, _lead_mobilePhone, _lead_notes, _lead_email, _lead_email1, _lead_name, _lead_email2, _lead_email3;
107
109
  const { hostId, consentGroup, orgId, leadId, lead, leadStatus, fromCache, basePath, roster, onConvert, onUnqualify, extraMenuItems = [], banner, extraChips, erasurePending = false, org } = props;
108
110
  const firestore = useFirestore();
109
111
  const { enqueueSnackbar } = useSnackbar();
@@ -427,6 +429,10 @@ const NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX;
427
429
  email: String((_lead_email2 = lead['email']) != null ? _lead_email2 : ''),
428
430
  emailState: emailState
429
431
  }),
432
+ /*#__PURE__*/ _jsx(CrmAccountLockChip, {
433
+ hostId: hostId,
434
+ email: String((_lead_email3 = lead['email']) != null ? _lead_email3 : '')
435
+ }),
430
436
  /*#__PURE__*/ _jsx(CrmRecordChip, {
431
437
  label: "Owner",
432
438
  value: lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined
@@ -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 ConsentGroup,\n CrmLeadFields,\n CrmLeadProfilePatch,\n} from '@aglyn/aglyn'\nimport { mdiAccountCancelOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, 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 Select,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useId, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { useLeadStatusPicklist } from '../hooks/use-lead-status-picklist'\nimport { useLeadPicklists } from '../hooks/use-lead-picklists'\nimport {\n crmCustomDraftChanges,\n type CrmCustomDraft,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LeadProfileFields,\n type LeadProfileDraft,\n leadProfileDraftBody,\n leadProfileDraftFrom,\n} from './lead-profile-fields'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { CrmEmailGatewayChip } from './crm-email-check'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { 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'\nimport { leadStatusChoices, leadStatusMenuItems } from './lead-status-options'\nimport { crmClientListFields, CRM_CLIENT_SEARCH_FIELDS } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The Leads list's fields a profile save rewrites; the verdict key is the server's. */\nconst LEAD_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n 'leadSourceKey',\n // The lead source's group, read off the org's list (AGL-3577).\n 'leadSourceDirection',\n // Industry and Rating (AGL-3513).\n 'industryKey',\n 'ratingKey',\n] as const\n\nconst NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX\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 /**\n * The site this lead is acted on as — the mounted one, or at the\n * organization level the lead's own first capturing site (AGL-3278).\n * `null` for a lead no site has captured: the booking door, the call and\n * the email close, because each of them is one site's to offer.\n */\n hostId: string | null\n /**\n * The group the consent basis is read against (AGL-3278): the site's own\n * declared group, resolved by the page. Not `soloConsentGroup(hostId)`,\n * which is what this card used to spell and which cannot see a refusal\n * recorded against a sibling brand — three sites presenting as one sender\n * are one sender to the person unsubscribing from them.\n */\n consentGroup: ConsentGroup\n /**\n * The org the site belongs to, as the page already resolved it — the\n * custom lead fields are ORG-wide (AGL-3272), and a second lookup here\n * would be one more read per record page for an answer already in hand.\n */\n orgId: string | null\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 /** Opens the Unqualify dialog — with the Unqualified value picked, when the status select named one. */\n onUnqualify: (statusLabel?: string) => 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 * Chips the page adds after the owner — the campaigns the lead is filed\n * under (AGL-3274), named from the containers the page reads once for\n * this header and the Campaigns card below it.\n */\n extraChips?: 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 * Two cards. The record's header carries the identity, the status and owner,\n * the consent the capture recorded and the record's actions. **Details**\n * under it carries the profile, the org's own lead fields under **More\n * fields**, and the notes — one card with one Save in its header, the way\n * Salesforce keeps a record's standard and custom fields in one Details\n * section.\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. The details are drafts seeded\n * from the document, so their save goes through `writeGuardedBySeed`: a\n * draft edited over a cached read would otherwise overwrite a newer note or\n * profile with an older one.\n *\n * Converted leads are read-only here, but for the notes. Their status is the\n * conversion, and the header's actions become links to what the conversion\n * made.\n */\nexport function LeadPropertiesCard(props: LeadPropertiesCardProps) {\n const {\n hostId,\n consentGroup,\n orgId,\n leadId,\n lead,\n leadStatus,\n fromCache,\n basePath,\n roster,\n onConvert,\n onUnqualify,\n extraMenuItems = [],\n banner,\n extraChips,\n erasurePending = false,\n org,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const routes = crmRoutes(basePath)\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n /*\n * Built at USE, not at render (AGL-3275). A lead is an org row, so the org\n * has to be known to address one — and `orgId` is null while the lookup\n * settles. Composing a ref from that null throws during render, which the\n * page shows as a 500 rather than as a card still loading.\n */\n const refFor = () => (orgId ? doc(firestore, 'orgs', orgId, 'leads', leadId) : null)\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 const leadMobile = String(lead.mobilePhone ?? '').trim()\n // The person asked not to be phoned (AGL-3513): a hint on every dial control.\n const doNotCall = lead.doNotCall === true\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 /*\n * THE LEAD'S OWN PROFILE (AGL-3231, AGL-3513) — the name's parts,\n * company, title, the phones, website, address, tags, lead source,\n * industry, rating, size and revenue — edited in the Details card with\n * the notes and the custom fields, under one Save, the way the contact's\n * Properties card saves. Seeded from the document and guarded on save: a\n * draft edited over a cached read must not overwrite a newer profile\n * with an older one.\n */\n const [profile, setProfile] = useState<LeadProfileDraft>(() => leadProfileDraftFrom(lead))\n const [profileDirty, setProfileDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [profileErrors, setProfileErrors] = useState<Record<string, string>>({})\n // The stored profile as one value, so a change to any of its fields reseeds.\n const profileSeed = JSON.stringify(Aglyn.CRM_LEAD_PROFILE_KEYS.map((key) => lead[key] ?? null))\n useEffect(() => {\n if (!profileDirty) setProfile(leadProfileDraftFrom(lead))\n // The draft follows the document until it is edited.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [profileDirty, profileSeed])\n /*\n * THE ORG'S OWN LEAD FIELDS (AGL-3272), edited under the same Save as\n * the profile: one button over one card, so a person filling a lead in\n * does not have to find two.\n *\n * The draft holds only the keys the reader touched, and Save writes the\n * difference as dotted paths — a `custom` map written whole would take\n * out every key this card did not show, which is what a retired field's\n * values and an integration's writes sit under.\n */\n const fields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298), for the select below.\n const leadSources = useLeadSourcePicklist(orgId)\n // The org's lead status values (AGL-3512): the chip's words and the select's choices.\n const leadStatuses = useLeadStatusPicklist(orgId)\n // The org's Salutation, Industry and Rating lists (AGL-3513).\n const leadPicklists = useLeadPicklists(orgId)\n const statusChoices = useMemo(\n () => leadStatusChoices(leadStatuses.picklist, lead),\n [leadStatuses.picklist, lead],\n )\n const storedCustom = useMemo(() => lead.custom ?? {}, [lead.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n\n const editProfile = <K extends keyof LeadProfileDraft>(key: K, value: LeadProfileDraft[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 /** One update of the lead, and whether it landed. */\n const write = async (fields: Record<string, unknown>, done: string): Promise<boolean> => {\n try {\n const ref = refFor()\n if (!ref) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return false\n }\n await updateDoc(ref, { ...fields, updatedAt: serverTimestamp() })\n followUpSharing('leads', [leadId])\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The lead could not be updated.',\n { variant: 'error' },\n )\n return false\n }\n }\n\n /*\n * What Save is offered for: a profile field edited, a custom value that\n * differs from the stored map, or the notes. Touched-and-put-back does\n * not count for a custom value — the draft keeps the key either way, and\n * a Save that wrote nothing would still bump `updatedAt`.\n */\n const customChanged = crmCustomDraftChanges(storedCustom, custom).length > 0\n const dirty = profileDirty || customChanged || notesDirty\n\n /** Every draft back to what the document holds. */\n const discard = () => {\n setProfile(leadProfileDraftFrom(lead))\n setProfileDirty(false)\n setProfileErrors({})\n setCustom({})\n setNotes(String(lead.notes ?? ''))\n setNotesDirty(false)\n }\n\n /*\n * The Details card's one Save: the profile and the custom values while\n * the lead is open, and the notes, in one update. A converted lead's\n * profile is the contact's now, so only its notes are written.\n */\n const saveDetails = async () => {\n const update: Record<string, unknown> = {}\n if (!converted && (profileDirty || customChanged)) {\n const { body, revenueError } = leadProfileDraftBody(profile)\n const { patch, errors } = Aglyn.normalizeCrmLeadProfile(body)\n const shown: Record<string, string> = { ...errors }\n if (revenueError) shown['annualRevenueCents'] = revenueError\n setProfileErrors(shown)\n if (Object.keys(shown).length) return\n /*\n * A required field the reader CLEARED is refused; one the lead has\n * always lacked is not this save's to demand, or a field added after\n * the lead was captured would block every later edit to its company.\n */\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n 'edit',\n )\n if (missing.length) {\n enqueueSnackbar(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`, {\n variant: 'warning',\n })\n return\n }\n const profileFields = profileWrite(patch)\n // While a first or last name is set, the name is theirs (AGL-3513).\n const composed = Aglyn.crmLeadComposedName(lead, patch)\n if (composed && composed !== lead['name']) profileFields['name'] = composed\n Object.assign(\n update,\n profileFields,\n crmCustomDraftWrites(storedCustom, custom),\n // The company, title, tags and lead source are what the Leads list\n // searches and filters by (AGL-3321).\n crmClientListFields(\n 'leads',\n lead,\n profileFields,\n LEAD_CLIENT_LIST_FIELDS,\n // A direction only from the org's list as the server answered it.\n leadSources.ready ? { leadSources: leadSources.picklist } : {},\n ),\n )\n }\n if (notesDirty) update['notes'] = notes.trim().slice(0, NOTES_MAX)\n if (!Object.keys(update).length) return\n setSaving(true)\n let landed = false\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n landed = await write(update, 'Lead saved')\n },\n )\n setSaving(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The lead could not be saved.', {\n variant: 'warning',\n })\n return\n }\n if (!landed) return\n setProfileDirty(false)\n setNotesDirty(false)\n // The draft is spent: what it held is stored, and the controls read\n // the document again.\n setCustom({})\n }\n\n const consent = Aglyn.readMarketingBasis(lead, consentGroup)\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 <>\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 {/*\n Once converted, the lead's actions are the records the\n conversion made.\n */}\n {converted ? (\n <>\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 </>\n ) : 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 || leadMobile}\n doNotCall={doNotCall}\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} statuses={leadStatuses.picklist} />\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 {/* The mail gateway in front of the address, and what it did\n with this site's mail this week (AGL-3328). */}\n <CrmEmailGatewayChip\n hostId={hostId}\n email={String(lead['email'] ?? '')}\n emailState={emailState}\n />\n <CrmRecordChip\n label=\"Owner\"\n value={lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined}\n />\n {extraChips}\n </>\n }\n >\n <Stack spacing={3}>\n {banner}\n {/*\n Only when the capture carried one (AGL-2661): the sign-up door\n writes no phone, and a booking only when its service asks for\n one (AGL-3493), so a row for every lead would often be a\n permanent blank. A form that captures one fills this.\n */}\n {leadPhone ? (\n <Fact label=\"Phone\">\n <CrmPhoneLink phone={leadPhone} doNotCall={doNotCall} />\n </Fact>\n ) : null}\n {leadMobile ? (\n <Fact label=\"Mobile phone\">\n <CrmPhoneLink phone={leadMobile} doNotCall={doNotCall} />\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} statuses={leadStatuses.picklist} />\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={Aglyn.crmLeadStatusLabel(lead, leadStatuses.picklist)}\n onChange={(event) => {\n const choice = statusChoices.find((entry) => entry.label === event.target.value)\n if (!choice) return\n if (choice.status === 'unqualified') {\n onUnqualify(choice.label)\n return\n }\n // The meaning and the org's label for it, together\n // (AGL-3512). Reopening drops the reason with the closed\n // state: a lead being worked again is not \"unqualified\n // because …\".\n void write(\n {\n status: choice.status,\n statusLabel: choice.label,\n ...(status === 'unqualified' ? { unqualifiedReason: deleteField() } : {}),\n },\n 'Status updated',\n )\n }}\n >\n {leadStatusMenuItems(statusChoices)}\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 </Stack>\n </CrmRecordHeader>\n {/*\n The profile (AGL-3231), the org's own lead fields (AGL-3272) and the\n notes: what Salesforce keeps on a lead and hands to the contact and\n the account on convert. Read-only once converted — the contact is the\n record then, and the header's links lead there — but for the notes.\n */}\n <CardDisplay\n header={'Details'}\n help={Aglyn.pluginDocsHelp('crmLeads', { anchor: '#a-leads-page' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n {dirty ? (\n <Button size=\"small\" disabled={saving} onClick={discard}>\n {'Discard changes'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveDetails()}\n disabled={!dirty || saving}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n ),\n }}\n >\n <Stack spacing={2}>\n <LeadProfileFields\n draft={profile}\n onChange={editProfile}\n errors={profileErrors}\n disabled={converted}\n leadSources={leadSources.picklist}\n lists={leadPicklists.lists}\n stored={lead}\n />\n {/*\n The org's own lead fields (AGL-3272), after the built-in ones\n because they describe the same record and save with it. Absent,\n not an empty box, while the org defines none.\n */}\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'More fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={converted || saving}\n />\n ))}\n </>\n ) : null}\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>\n </CardDisplay>\n </>\n )\n}\nLeadPropertiesCard.displayName = 'LeadPropertiesCard'\n\nexport default LeadPropertiesCard\n"],"names":["Aglyn","mdiAccountCancelOutline","AppLink","CardDisplay","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Alert","Button","FormControl","InputLabel","Select","Stack","TextField","Tooltip","Typography","deleteField","doc","serverTimestamp","updateDoc","useEffect","useId","useMemo","useState","useContactFieldDefinitions","useLeadSourcePicklist","useLeadStatusPicklist","useLeadPicklists","crmCustomDraftChanges","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","crmRoutes","LeadProfileFields","leadProfileDraftBody","leadProfileDraftFrom","CrmCallButton","CrmPhoneLink","CrmEmailStateChip","CrmEmailGatewayChip","CrmRecordChip","CrmRecordHeader","CrmSendEmailButton","LeadOwnerSelect","LeadStatusChip","leadStatusChoices","leadStatusMenuItems","crmClientListFields","CRM_CLIENT_SEARCH_FIELDS","useCrmSharingFollowUp","LEAD_CLIENT_LIST_FIELDS","NOTES_MAX","CRM_LEAD_NOTES_MAX","profileWrite","patch","write","key","value","Object","entries","undefined","CONVERT_PENDING_ERASURE_REASON","Fact","props","spacing","variant","color","label","component","children","LeadPropertiesCard","lead","hostId","consentGroup","orgId","leadId","leadStatus","fromCache","basePath","roster","onConvert","onUnqualify","extraMenuItems","banner","extraChips","erasurePending","org","firestore","enqueueSnackbar","routes","followUpSharing","refFor","status","crmLeadStatus","converted","Boolean","convertedContactId","open","isCrmLeadOpen","emailState","readEmailState","leadPhone","String","trim","leadMobile","mobilePhone","doNotCall","notes","setNotes","statusLabelId","notesDirty","setNotesDirty","profile","setProfile","profileDirty","setProfileDirty","saving","setSaving","profileErrors","setProfileErrors","profileSeed","JSON","stringify","CRM_LEAD_PROFILE_KEYS","map","fields","leadSources","leadStatuses","leadPicklists","statusChoices","picklist","storedCustom","custom","setCustom","editProfile","current","done","ref","persist","updatedAt","error","Error","message","customChanged","length","dirty","discard","saveDetails","update","body","revenueError","errors","normalizeCrmLeadProfile","shown","keys","missing","active","join","profileFields","composed","crmLeadComposedName","assign","ready","slice","landed","verdict","subject","unreadable","ok","consent","readMarketingBasis","consentLine","basis","basisAtMs","Date","toLocaleDateString","kind","title","subtitle","help","pluginDocsHelp","anchor","backHref","section","backLabel","booking","recordId","actions","componentVariant","nativeButton","href","contact","size","companyId","company","dealId","deal","span","disabled","onClick","link","phone","email","name","menuItems","icon","path","destructive","chips","statuses","state","ownerUid","labelFor","direction","xs","md","sx","alignItems","convertedAtMs","toLocaleString","minWidth","id","labelId","crmLeadStatusLabel","onChange","event","choice","find","entry","target","statusLabel","unqualifiedReason","fullWidth","uid","severity","header","contentGutterX","contentGutterY","HeaderProps","action","draft","lists","stored","definition","$id","multiline","minRows","slotProps","htmlInput","maxLength","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 ConsentGroup,\n CrmLeadFields,\n CrmLeadProfilePatch,\n} from '@aglyn/aglyn'\nimport { mdiAccountCancelOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, 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 Select,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useId, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { useLeadStatusPicklist } from '../hooks/use-lead-status-picklist'\nimport { useLeadPicklists } from '../hooks/use-lead-picklists'\nimport {\n crmCustomDraftChanges,\n type CrmCustomDraft,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LeadProfileFields,\n type LeadProfileDraft,\n leadProfileDraftBody,\n leadProfileDraftFrom,\n} from './lead-profile-fields'\nimport { CrmCallButton, CrmPhoneLink } from './crm-call-actions'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { CrmAccountLockChip, CrmEmailGatewayChip } from './crm-email-check'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { 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'\nimport { leadStatusChoices, leadStatusMenuItems } from './lead-status-options'\nimport {\n crmClientListFields,\n CRM_CLIENT_SEARCH_FIELDS,\n CRM_CLIENT_SORT_FIELDS,\n} from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The Leads list's fields a profile save rewrites; the verdict key is the server's. */\nconst LEAD_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n // The Lead, Company and Title headers' sort keys (AGL-3680).\n ...CRM_CLIENT_SORT_FIELDS,\n 'leadSourceKey',\n // The lead source's group, read off the org's list (AGL-3577).\n 'leadSourceDirection',\n // Industry and Rating (AGL-3513).\n 'industryKey',\n 'ratingKey',\n] as const\n\nconst NOTES_MAX = Aglyn.CRM_LEAD_NOTES_MAX\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 /**\n * The site this lead is acted on as — the mounted one, or at the\n * organization level the lead's own first capturing site (AGL-3278).\n * `null` for a lead no site has captured: the booking door, the call and\n * the email close, because each of them is one site's to offer.\n */\n hostId: string | null\n /**\n * The group the consent basis is read against (AGL-3278): the site's own\n * declared group, resolved by the page. Not `soloConsentGroup(hostId)`,\n * which is what this card used to spell and which cannot see a refusal\n * recorded against a sibling brand — three sites presenting as one sender\n * are one sender to the person unsubscribing from them.\n */\n consentGroup: ConsentGroup\n /**\n * The org the site belongs to, as the page already resolved it — the\n * custom lead fields are ORG-wide (AGL-3272), and a second lookup here\n * would be one more read per record page for an answer already in hand.\n */\n orgId: string | null\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 /** Opens the Unqualify dialog — with the Unqualified value picked, when the status select named one. */\n onUnqualify: (statusLabel?: string) => 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 * Chips the page adds after the owner — the campaigns the lead is filed\n * under (AGL-3274), named from the containers the page reads once for\n * this header and the Campaigns card below it.\n */\n extraChips?: 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 * Two cards. The record's header carries the identity, the status and owner,\n * the consent the capture recorded and the record's actions. **Details**\n * under it carries the profile, the org's own lead fields under **More\n * fields**, and the notes — one card with one Save in its header, the way\n * Salesforce keeps a record's standard and custom fields in one Details\n * section.\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. The details are drafts seeded\n * from the document, so their save goes through `writeGuardedBySeed`: a\n * draft edited over a cached read would otherwise overwrite a newer note or\n * profile with an older one.\n *\n * Converted leads are read-only here, but for the notes. Their status is the\n * conversion, and the header's actions become links to what the conversion\n * made.\n */\nexport function LeadPropertiesCard(props: LeadPropertiesCardProps) {\n const {\n hostId,\n consentGroup,\n orgId,\n leadId,\n lead,\n leadStatus,\n fromCache,\n basePath,\n roster,\n onConvert,\n onUnqualify,\n extraMenuItems = [],\n banner,\n extraChips,\n erasurePending = false,\n org,\n } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const routes = crmRoutes(basePath)\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n /*\n * Built at USE, not at render (AGL-3275). A lead is an org row, so the org\n * has to be known to address one — and `orgId` is null while the lookup\n * settles. Composing a ref from that null throws during render, which the\n * page shows as a 500 rather than as a card still loading.\n */\n const refFor = () => (orgId ? doc(firestore, 'orgs', orgId, 'leads', leadId) : null)\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 const leadMobile = String(lead.mobilePhone ?? '').trim()\n // The person asked not to be phoned (AGL-3513): a hint on every dial control.\n const doNotCall = lead.doNotCall === true\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 /*\n * THE LEAD'S OWN PROFILE (AGL-3231, AGL-3513) — the name's parts,\n * company, title, the phones, website, address, tags, lead source,\n * industry, rating, size and revenue — edited in the Details card with\n * the notes and the custom fields, under one Save, the way the contact's\n * Properties card saves. Seeded from the document and guarded on save: a\n * draft edited over a cached read must not overwrite a newer profile\n * with an older one.\n */\n const [profile, setProfile] = useState<LeadProfileDraft>(() => leadProfileDraftFrom(lead))\n const [profileDirty, setProfileDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [profileErrors, setProfileErrors] = useState<Record<string, string>>({})\n // The stored profile as one value, so a change to any of its fields reseeds.\n const profileSeed = JSON.stringify(Aglyn.CRM_LEAD_PROFILE_KEYS.map((key) => lead[key] ?? null))\n useEffect(() => {\n if (!profileDirty) setProfile(leadProfileDraftFrom(lead))\n // The draft follows the document until it is edited.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [profileDirty, profileSeed])\n /*\n * THE ORG'S OWN LEAD FIELDS (AGL-3272), edited under the same Save as\n * the profile: one button over one card, so a person filling a lead in\n * does not have to find two.\n *\n * The draft holds only the keys the reader touched, and Save writes the\n * difference as dotted paths — a `custom` map written whole would take\n * out every key this card did not show, which is what a retired field's\n * values and an integration's writes sit under.\n */\n const fields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298), for the select below.\n const leadSources = useLeadSourcePicklist(orgId)\n // The org's lead status values (AGL-3512): the chip's words and the select's choices.\n const leadStatuses = useLeadStatusPicklist(orgId)\n // The org's Salutation, Industry and Rating lists (AGL-3513).\n const leadPicklists = useLeadPicklists(orgId)\n const statusChoices = useMemo(\n () => leadStatusChoices(leadStatuses.picklist, lead),\n [leadStatuses.picklist, lead],\n )\n const storedCustom = useMemo(() => lead.custom ?? {}, [lead.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n\n const editProfile = <K extends keyof LeadProfileDraft>(key: K, value: LeadProfileDraft[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 /** One update of the lead, and whether it landed. */\n const write = async (fields: Record<string, unknown>, done: string): Promise<boolean> => {\n try {\n const ref = refFor()\n if (!ref) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return false\n }\n await updateDoc(ref, { ...fields, updatedAt: serverTimestamp() })\n followUpSharing('leads', [leadId])\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The lead could not be updated.',\n { variant: 'error' },\n )\n return false\n }\n }\n\n /*\n * What Save is offered for: a profile field edited, a custom value that\n * differs from the stored map, or the notes. Touched-and-put-back does\n * not count for a custom value — the draft keeps the key either way, and\n * a Save that wrote nothing would still bump `updatedAt`.\n */\n const customChanged = crmCustomDraftChanges(storedCustom, custom).length > 0\n const dirty = profileDirty || customChanged || notesDirty\n\n /** Every draft back to what the document holds. */\n const discard = () => {\n setProfile(leadProfileDraftFrom(lead))\n setProfileDirty(false)\n setProfileErrors({})\n setCustom({})\n setNotes(String(lead.notes ?? ''))\n setNotesDirty(false)\n }\n\n /*\n * The Details card's one Save: the profile and the custom values while\n * the lead is open, and the notes, in one update. A converted lead's\n * profile is the contact's now, so only its notes are written.\n */\n const saveDetails = async () => {\n const update: Record<string, unknown> = {}\n if (!converted && (profileDirty || customChanged)) {\n const { body, revenueError } = leadProfileDraftBody(profile)\n const { patch, errors } = Aglyn.normalizeCrmLeadProfile(body)\n const shown: Record<string, string> = { ...errors }\n if (revenueError) shown['annualRevenueCents'] = revenueError\n setProfileErrors(shown)\n if (Object.keys(shown).length) return\n /*\n * A required field the reader CLEARED is refused; one the lead has\n * always lacked is not this save's to demand, or a field added after\n * the lead was captured would block every later edit to its company.\n */\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n 'edit',\n )\n if (missing.length) {\n enqueueSnackbar(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`, {\n variant: 'warning',\n })\n return\n }\n const profileFields = profileWrite(patch)\n // While a first or last name is set, the name is theirs (AGL-3513).\n const composed = Aglyn.crmLeadComposedName(lead, patch)\n if (composed && composed !== lead['name']) profileFields['name'] = composed\n Object.assign(\n update,\n profileFields,\n crmCustomDraftWrites(storedCustom, custom),\n // The company, title, tags and lead source are what the Leads list\n // searches and filters by (AGL-3321).\n crmClientListFields(\n 'leads',\n lead,\n profileFields,\n LEAD_CLIENT_LIST_FIELDS,\n // A direction only from the org's list as the server answered it.\n leadSources.ready ? { leadSources: leadSources.picklist } : {},\n ),\n )\n }\n if (notesDirty) update['notes'] = notes.trim().slice(0, NOTES_MAX)\n if (!Object.keys(update).length) return\n setSaving(true)\n let landed = false\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n landed = await write(update, 'Lead saved')\n },\n )\n setSaving(false)\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The lead could not be saved.', {\n variant: 'warning',\n })\n return\n }\n if (!landed) return\n setProfileDirty(false)\n setNotesDirty(false)\n // The draft is spent: what it held is stored, and the controls read\n // the document again.\n setCustom({})\n }\n\n const consent = Aglyn.readMarketingBasis(lead, consentGroup)\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 <>\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 {/*\n Once converted, the lead's actions are the records the\n conversion made.\n */}\n {converted ? (\n <>\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 </>\n ) : 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 || leadMobile}\n doNotCall={doNotCall}\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} statuses={leadStatuses.picklist} />\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 {/* The mail gateway in front of the address, and what it did\n with this site's mail this week (AGL-3328). */}\n <CrmEmailGatewayChip\n hostId={hostId}\n email={String(lead['email'] ?? '')}\n emailState={emailState}\n />\n <CrmAccountLockChip\n hostId={hostId}\n email={String(lead['email'] ?? '')}\n />\n <CrmRecordChip\n label=\"Owner\"\n value={lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined}\n />\n {extraChips}\n </>\n }\n >\n <Stack spacing={3}>\n {banner}\n {/*\n Only when the capture carried one (AGL-2661): the sign-up door\n writes no phone, and a booking only when its service asks for\n one (AGL-3493), so a row for every lead would often be a\n permanent blank. A form that captures one fills this.\n */}\n {leadPhone ? (\n <Fact label=\"Phone\">\n <CrmPhoneLink phone={leadPhone} doNotCall={doNotCall} />\n </Fact>\n ) : null}\n {leadMobile ? (\n <Fact label=\"Mobile phone\">\n <CrmPhoneLink phone={leadMobile} doNotCall={doNotCall} />\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} statuses={leadStatuses.picklist} />\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={Aglyn.crmLeadStatusLabel(lead, leadStatuses.picklist)}\n onChange={(event) => {\n const choice = statusChoices.find((entry) => entry.label === event.target.value)\n if (!choice) return\n if (choice.status === 'unqualified') {\n onUnqualify(choice.label)\n return\n }\n // The meaning and the org's label for it, together\n // (AGL-3512). Reopening drops the reason with the closed\n // state: a lead being worked again is not \"unqualified\n // because …\".\n void write(\n {\n status: choice.status,\n statusLabel: choice.label,\n ...(status === 'unqualified' ? { unqualifiedReason: deleteField() } : {}),\n },\n 'Status updated',\n )\n }}\n >\n {leadStatusMenuItems(statusChoices)}\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 </Stack>\n </CrmRecordHeader>\n {/*\n The profile (AGL-3231), the org's own lead fields (AGL-3272) and the\n notes: what Salesforce keeps on a lead and hands to the contact and\n the account on convert. Read-only once converted — the contact is the\n record then, and the header's links lead there — but for the notes.\n */}\n <CardDisplay\n header={'Details'}\n help={Aglyn.pluginDocsHelp('crmLeads', { anchor: '#a-leads-page' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n {dirty ? (\n <Button size=\"small\" disabled={saving} onClick={discard}>\n {'Discard changes'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void saveDetails()}\n disabled={!dirty || saving}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n ),\n }}\n >\n <Stack spacing={2}>\n <LeadProfileFields\n draft={profile}\n onChange={editProfile}\n errors={profileErrors}\n disabled={converted}\n leadSources={leadSources.picklist}\n lists={leadPicklists.lists}\n stored={lead}\n />\n {/*\n The org's own lead fields (AGL-3272), after the built-in ones\n because they describe the same record and save with it. Absent,\n not an empty box, while the org defines none.\n */}\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'More fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={converted || saving}\n />\n ))}\n </>\n ) : null}\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>\n </CardDisplay>\n </>\n )\n}\nLeadPropertiesCard.displayName = 'LeadPropertiesCard'\n\nexport default LeadPropertiesCard\n"],"names":["Aglyn","mdiAccountCancelOutline","AppLink","CardDisplay","MdiIcon","useSnackbar","useFirestore","writeGuardedBySeed","Alert","Button","FormControl","InputLabel","Select","Stack","TextField","Tooltip","Typography","deleteField","doc","serverTimestamp","updateDoc","useEffect","useId","useMemo","useState","useContactFieldDefinitions","useLeadSourcePicklist","useLeadStatusPicklist","useLeadPicklists","crmCustomDraftChanges","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","crmRoutes","LeadProfileFields","leadProfileDraftBody","leadProfileDraftFrom","CrmCallButton","CrmPhoneLink","CrmEmailStateChip","CrmAccountLockChip","CrmEmailGatewayChip","CrmRecordChip","CrmRecordHeader","CrmSendEmailButton","LeadOwnerSelect","LeadStatusChip","leadStatusChoices","leadStatusMenuItems","crmClientListFields","CRM_CLIENT_SEARCH_FIELDS","CRM_CLIENT_SORT_FIELDS","useCrmSharingFollowUp","LEAD_CLIENT_LIST_FIELDS","NOTES_MAX","CRM_LEAD_NOTES_MAX","profileWrite","patch","write","key","value","Object","entries","undefined","CONVERT_PENDING_ERASURE_REASON","Fact","props","spacing","variant","color","label","component","children","LeadPropertiesCard","lead","hostId","consentGroup","orgId","leadId","leadStatus","fromCache","basePath","roster","onConvert","onUnqualify","extraMenuItems","banner","extraChips","erasurePending","org","firestore","enqueueSnackbar","routes","followUpSharing","refFor","status","crmLeadStatus","converted","Boolean","convertedContactId","open","isCrmLeadOpen","emailState","readEmailState","leadPhone","String","trim","leadMobile","mobilePhone","doNotCall","notes","setNotes","statusLabelId","notesDirty","setNotesDirty","profile","setProfile","profileDirty","setProfileDirty","saving","setSaving","profileErrors","setProfileErrors","profileSeed","JSON","stringify","CRM_LEAD_PROFILE_KEYS","map","fields","leadSources","leadStatuses","leadPicklists","statusChoices","picklist","storedCustom","custom","setCustom","editProfile","current","done","ref","persist","updatedAt","error","Error","message","customChanged","length","dirty","discard","saveDetails","update","body","revenueError","errors","normalizeCrmLeadProfile","shown","keys","missing","active","join","profileFields","composed","crmLeadComposedName","assign","ready","slice","landed","verdict","subject","unreadable","ok","consent","readMarketingBasis","consentLine","basis","basisAtMs","Date","toLocaleDateString","kind","title","subtitle","help","pluginDocsHelp","anchor","backHref","section","backLabel","booking","recordId","actions","componentVariant","nativeButton","href","contact","size","companyId","company","dealId","deal","span","disabled","onClick","link","phone","email","name","menuItems","icon","path","destructive","chips","statuses","state","ownerUid","labelFor","direction","xs","md","sx","alignItems","convertedAtMs","toLocaleString","minWidth","id","labelId","crmLeadStatusLabel","onChange","event","choice","find","entry","target","statusLabel","unqualifiedReason","fullWidth","uid","severity","header","contentGutterX","contentGutterY","HeaderProps","action","draft","lists","stored","definition","$id","multiline","minRows","slotProps","htmlInput","maxLength","displayName"],"mappings":"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"}