@aglyn/plugins-crm 1.0.0-beta.231 → 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 (105) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/activity-timeline.js +1 -0
  3. package/src/lib/components/activity-timeline.js.map +1 -1
  4. package/src/lib/components/assignment-rule-drawer.js +1 -0
  5. package/src/lib/components/assignment-rule-drawer.js.map +1 -1
  6. package/src/lib/components/companies-section.js +43 -8
  7. package/src/lib/components/companies-section.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +3 -1
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/contact-associations-card.js +1 -0
  11. package/src/lib/components/contact-associations-card.js.map +1 -1
  12. package/src/lib/components/contact-detail-page.js +6 -1
  13. package/src/lib/components/contact-detail-page.js.map +1 -1
  14. package/src/lib/components/contact-duplicates-card.js +1 -0
  15. package/src/lib/components/contact-duplicates-card.js.map +1 -1
  16. package/src/lib/components/contact-field-drawer.js +1 -0
  17. package/src/lib/components/contact-field-drawer.js.map +1 -1
  18. package/src/lib/components/contact-known-by-card.js +1 -0
  19. package/src/lib/components/contact-known-by-card.js.map +1 -1
  20. package/src/lib/components/contact-timeline-card.js +1 -0
  21. package/src/lib/components/contact-timeline-card.js.map +1 -1
  22. package/src/lib/components/contacts-section.js +37 -8
  23. package/src/lib/components/contacts-section.js.map +1 -1
  24. package/src/lib/components/crm-email-check.d.ts +14 -0
  25. package/src/lib/components/crm-email-check.js +38 -1
  26. package/src/lib/components/crm-email-check.js.map +1 -1
  27. package/src/lib/components/crm-glance-card.js +1 -0
  28. package/src/lib/components/crm-glance-card.js.map +1 -1
  29. package/src/lib/components/crm-next-activity-column.d.ts +5 -4
  30. package/src/lib/components/crm-next-activity-column.js +5 -4
  31. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  32. package/src/lib/components/crm-record-header.js +2 -0
  33. package/src/lib/components/crm-record-header.js.map +1 -1
  34. package/src/lib/components/deal-edit-drawer.js +3 -1
  35. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  36. package/src/lib/components/deals-section.js +51 -6
  37. package/src/lib/components/deals-section.js.map +1 -1
  38. package/src/lib/components/fields-section.js +4 -0
  39. package/src/lib/components/fields-section.js.map +1 -1
  40. package/src/lib/components/lead-history-card.js +1 -0
  41. package/src/lib/components/lead-history-card.js.map +1 -1
  42. package/src/lib/components/lead-properties-card.js +9 -3
  43. package/src/lib/components/lead-properties-card.js.map +1 -1
  44. package/src/lib/components/lead-surfaces-note.js +1 -0
  45. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  46. package/src/lib/components/leads-section.js +61 -11
  47. package/src/lib/components/leads-section.js.map +1 -1
  48. package/src/lib/components/linked-deals-card.js +1 -0
  49. package/src/lib/components/linked-deals-card.js.map +1 -1
  50. package/src/lib/components/picklist-values-card.js +1 -0
  51. package/src/lib/components/picklist-values-card.js.map +1 -1
  52. package/src/lib/components/recipes-card.js +1 -0
  53. package/src/lib/components/recipes-card.js.map +1 -1
  54. package/src/lib/components/reports/activity-card.js +1 -0
  55. package/src/lib/components/reports/activity-card.js.map +1 -1
  56. package/src/lib/components/reports/closed-deals-card.js +1 -0
  57. package/src/lib/components/reports/closed-deals-card.js.map +1 -1
  58. package/src/lib/components/reports/contacts-trend-card.js +1 -0
  59. package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
  60. package/src/lib/components/reports/lead-funnel-card.js +1 -0
  61. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  62. package/src/lib/components/reports/lead-source-card.js +1 -0
  63. package/src/lib/components/reports/lead-source-card.js.map +1 -1
  64. package/src/lib/components/reports/pipeline-card.js +1 -0
  65. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  66. package/src/lib/components/reports/source-conversion-card.js +1 -0
  67. package/src/lib/components/reports/source-conversion-card.js.map +1 -1
  68. package/src/lib/components/reports/tasks-card.js +1 -0
  69. package/src/lib/components/reports/tasks-card.js.map +1 -1
  70. package/src/lib/components/reports-section.js +2 -0
  71. package/src/lib/components/reports-section.js.map +1 -1
  72. package/src/lib/components/tasks-calendar.js +1 -0
  73. package/src/lib/components/tasks-calendar.js.map +1 -1
  74. package/src/lib/components/tasks-section.js +61 -9
  75. package/src/lib/components/tasks-section.js.map +1 -1
  76. package/src/lib/constants/company-filters.js +40 -2
  77. package/src/lib/constants/company-filters.js.map +1 -1
  78. package/src/lib/constants/contact-filters.d.ts +10 -1
  79. package/src/lib/constants/contact-filters.js +62 -2
  80. package/src/lib/constants/contact-filters.js.map +1 -1
  81. package/src/lib/constants/deal-filters.js +78 -0
  82. package/src/lib/constants/deal-filters.js.map +1 -1
  83. package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
  84. package/src/lib/hooks/use-crm-column-sort.js +156 -0
  85. package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
  86. package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
  87. package/src/lib/hooks/use-crm-tasks.js +10 -8
  88. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  89. package/src/lib/model/crm-list-query.d.ts +6 -0
  90. package/src/lib/model/crm-list-query.js +13 -0
  91. package/src/lib/model/crm-list-query.js.map +1 -1
  92. package/src/lib/model/lead-filters.d.ts +19 -2
  93. package/src/lib/model/lead-filters.js +112 -2
  94. package/src/lib/model/lead-filters.js.map +1 -1
  95. package/src/lib/model/native-vocabulary.d.ts +41 -0
  96. package/src/lib/model/native-vocabulary.js +59 -0
  97. package/src/lib/model/native-vocabulary.js.map +1 -0
  98. package/src/lib/model/task-views.d.ts +15 -2
  99. package/src/lib/model/task-views.js +50 -4
  100. package/src/lib/model/task-views.js.map +1 -1
  101. package/src/lib/server/email-check.d.ts +4 -1
  102. package/src/lib/server/email-check.js +18 -6
  103. package/src/lib/server/email-check.js.map +1 -1
  104. package/src/lib/transfer/tasks.js +2 -1
  105. package/src/lib/transfer/tasks.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-glance-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 { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport {\n getAggregateFromServer,\n getCountFromServer,\n query,\n sum,\n Timestamp,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useOrgDataScope,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmRoutes } from '../model/crm-routes'\nimport { openLeadsForScope } from './reports/lead-counts'\nimport { scopedCollection, visibleToClause } from './reports/report-scope'\nimport { ReportStatTile } from './reports/report-stat-tile'\nimport { useAggregateRead } from './reports/use-aggregate-read'\nimport { localDayBounds } from '../model/crm-reports'\n\nconst WEEK_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface GlanceFigures {\n contacts: number\n newThisWeek: number\n pipelineCents: number\n tasksDue: number\n /** Open leads — new or being worked — on this site, or across the org's sites. */\n leadsToWork: number\n}\n\nexport interface CrmGlanceCardProps {\n /**\n * The site the card is on the dashboard of, or `null` beneath the org\n * hub's mount (AGL-2634), where the figures total every site: the\n * org-wide reads carry no scope clause, and the lead figure fans out\n * over the mount's site list.\n */\n hostId: string | null\n /** The org hub's own path, for the links when there is no site to derive them from. */\n basePath?: string\n}\n\n/**\n * CRM at a glance (AGL-2604): a `hostDashboard` widget with five numbers —\n * contacts, new this week, open pipeline value, tasks due today or overdue,\n * and leads still to work (AGL-2624) — each a link into the section that\n * explains it.\n *\n * Server aggregates and nothing else: a dashboard card mounts on every\n * visit to the site's front page, so it is the one surface on which a\n * bounded read of a thousand rows would be a cost paid for nothing. Each\n * number is a count or a sum the server takes over the same `visibleTo`\n * predicate the hub's sections query with, so the card never shows a figure\n * the section behind the link would not.\n *\n * The lead figure is two counts and a subtraction — every lead on the site,\n * less the closed ones — because a lead nobody has touched carries no status\n * field and Firestore cannot select on a field's absence; see\n * `openLeadsFromCounts`. Leads are host-scoped by path rather than by\n * `visibleTo`, so those two counts run on `hosts/{hostId}/leads` under the\n * site's own rules, the way the Leads section reads it.\n *\n * The widget is handed a host doc id and nothing else, so the org — needed\n * for the consent group whose tokens scope the reads — comes from\n * `useOrgPlan`, and the link base from `useConsoleHostRoute`, the way the\n * commerce glance card builds its store link. Nothing is read until the org\n * has settled: a first round of aggregates against the solo group, replaced\n * by a second against the real one, would be two reads for one answer.\n *\n * ## The organization level (AGL-2634)\n *\n * Mounted with no site beneath the org hub's mount, the card totals the\n * organization: the reads carry no scope clause — the rules admit an\n * org-wide member to every row — and the lead figure is the same two\n * counts per site, over every site the mount lists, summed. Nothing is\n * read until that list has settled, for the reason nothing is read under\n * a site until the org has. The org's dashboard has no widget slot yet, so\n * the mount is a capability rather than a page; what it needs from the\n * shell is a slot and the hub's `basePath`.\n */\nexport function CrmGlanceCard(props: CrmGlanceCardProps) {\n const { hostId, basePath } = props\n const mount = useCrmOrgMount()\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: planReady } = useOrgPlan(hostId ?? undefined)\n const { scope } = useOrgDataScope(hostId ? { hostId } : { orgId: mount?.orgId })\n const firestore = useFirestore()\n // Under a site, the site's group's tokens once the org has settled; at\n // the org level `null` — no clause — once the site list has.\n const tokens = useMemo(() => {\n if (!hostId) return null\n if (!planReady) return undefined\n const group = Aglyn.consentGroupForHost(org ?? null, hostId)\n return [\n Aglyn.ORG_SCOPE_TOKEN,\n ...group.hostIds.map((id) => Aglyn.hostScopeToken(id)),\n ].slice(0, Aglyn.MAX_SCOPE_HOSTS)\n }, [org, planReady, hostId])\n const leadSites = useMemo(\n () =>\n hostId\n ? [hostId]\n : mount?.hostsReady\n ? mount.hosts.map((host) => host.id)\n : null,\n [hostId, mount],\n )\n const leadSitesKey = leadSites?.join('\\n') ?? ''\n // Anchored once per mount so the queries are stable across renders.\n const nowMs = useMemo(() => Date.now(), [])\n\n const figures = useAggregateRead<GlanceFigures>(() => {\n if (!scope || tokens === undefined || !leadSites) return null\n const contacts = scopedCollection(firestore, scope, 'contacts')\n const deals = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.deals)\n const tasks = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks)\n const day = localDayBounds(nowMs)\n const countOf = (target: ReturnType<typeof query>) =>\n getCountFromServer(target).then((snapshot) => snapshot.data().count)\n return Promise.all([\n countOf(query(contacts, ...visibleToClause(tokens))),\n countOf(\n query(\n contacts,\n ...visibleToClause(tokens),\n where('createdAt', '>=', Timestamp.fromMillis(nowMs - WEEK_MS)),\n ),\n ),\n getAggregateFromServer(\n query(deals, ...visibleToClause(tokens), where('status', '==', 'open')),\n { amountCents: sum('amountCents') },\n ).then((snapshot) => Number(snapshot.data().amountCents ?? 0)),\n // Due today OR overdue: everything open that is due before the day ends.\n countOf(\n query(\n tasks,\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n where('dueAtMs', '<', day.end),\n ),\n ),\n // One scoped count over the org collection (AGL-3275) — see the\n // function's note on why summing per site would double-count.\n openLeadsForScope(firestore, scope[1], tokens),\n ]).then(([contacts, newThisWeek, pipelineCents, tasksDue, leadsToWork]) => ({\n contacts,\n newThisWeek,\n pipelineCents,\n tasksDue,\n leadsToWork,\n }))\n }, [firestore, scope, tokens, nowMs, leadSitesKey])\n\n const routes = useMemo(\n () =>\n hostId\n ? consoleRoute.base\n ? crmRoutes(`${consoleRoute.base}/crm`)\n : null\n : basePath\n ? crmRoutes(basePath)\n : null,\n [hostId, consoleRoute.base, basePath],\n )\n const hubHref = hostId\n ? consoleRoute.base\n ? `${consoleRoute.base}/crm`\n : undefined\n : basePath\n const value = figures.value\n\n return (\n <CardDisplay\n header={'CRM at a glance'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#crm-at-a-glance',\n excerpt:\n 'Contacts, new contacts this week, the value of every open deal, ' +\n 'the tasks due today or overdue, and the leads still to work — ' +\n 'each counted on the server and each a link into the CRM.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={hubHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open CRM'}\n </Button>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Contacts'}\n value={value ? value.contacts.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'New this week'}\n value={value ? value.newThisWeek.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'Open pipeline'}\n value={value ? money(value.pipelineCents, 'usd') : null}\n href={routes?.section('deals')}\n />\n <ReportStatTile\n label={'Tasks due'}\n value={value ? value.tasksDue.toLocaleString() : null}\n note={'today or overdue'}\n color={value?.tasksDue ? 'warning.main' : undefined}\n href={routes?.section('tasks')}\n />\n <ReportStatTile\n label={'Leads to work'}\n value={value ? value.leadsToWork.toLocaleString() : null}\n note={hostId ? 'open on this site' : 'open across every site'}\n href={routes?.section('leads')}\n />\n </Stack>\n {figures.status === 'error' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The CRM figures could not be read. Open the CRM for the full picture.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nCrmGlanceCard.displayName = 'CrmGlanceCard'\n\nexport default CrmGlanceCard\n"],"names":["Aglyn","money","AppLink","CardDisplay","Button","Stack","Typography","getAggregateFromServer","getCountFromServer","query","sum","Timestamp","where","useMemo","useConsoleHostRoute","useFirestore","useOrgDataScope","useOrgPlan","useCrmOrgMount","crmRoutes","openLeadsForScope","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","localDayBounds","WEEK_MS","CrmGlanceCard","props","hostId","basePath","mount","consoleRoute","org","ready","planReady","undefined","scope","orgId","firestore","tokens","group","consentGroupForHost","ORG_SCOPE_TOKEN","hostIds","map","id","hostScopeToken","slice","MAX_SCOPE_HOSTS","leadSites","hostsReady","hosts","host","leadSitesKey","join","nowMs","Date","now","figures","contacts","deals","CRM_COLLECTIONS","tasks","day","countOf","target","then","snapshot","data","count","Promise","all","fromMillis","amountCents","Number","end","newThisWeek","pipelineCents","tasksDue","leadsToWork","routes","base","hubHref","value","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","size","color","spacing","direction","sx","flexWrap","label","toLocaleString","section","note","status","variant","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-glance-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 { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport {\n getAggregateFromServer,\n getCountFromServer,\n query,\n sum,\n Timestamp,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useOrgDataScope,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmRoutes } from '../model/crm-routes'\nimport { openLeadsForScope } from './reports/lead-counts'\nimport { scopedCollection, visibleToClause } from './reports/report-scope'\nimport { ReportStatTile } from './reports/report-stat-tile'\nimport { useAggregateRead } from './reports/use-aggregate-read'\nimport { localDayBounds } from '../model/crm-reports'\n\nconst WEEK_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface GlanceFigures {\n contacts: number\n newThisWeek: number\n pipelineCents: number\n tasksDue: number\n /** Open leads — new or being worked — on this site, or across the org's sites. */\n leadsToWork: number\n}\n\nexport interface CrmGlanceCardProps {\n /**\n * The site the card is on the dashboard of, or `null` beneath the org\n * hub's mount (AGL-2634), where the figures total every site: the\n * org-wide reads carry no scope clause, and the lead figure fans out\n * over the mount's site list.\n */\n hostId: string | null\n /** The org hub's own path, for the links when there is no site to derive them from. */\n basePath?: string\n}\n\n/**\n * CRM at a glance (AGL-2604): a `hostDashboard` widget with five numbers —\n * contacts, new this week, open pipeline value, tasks due today or overdue,\n * and leads still to work (AGL-2624) — each a link into the section that\n * explains it.\n *\n * Server aggregates and nothing else: a dashboard card mounts on every\n * visit to the site's front page, so it is the one surface on which a\n * bounded read of a thousand rows would be a cost paid for nothing. Each\n * number is a count or a sum the server takes over the same `visibleTo`\n * predicate the hub's sections query with, so the card never shows a figure\n * the section behind the link would not.\n *\n * The lead figure is two counts and a subtraction — every lead on the site,\n * less the closed ones — because a lead nobody has touched carries no status\n * field and Firestore cannot select on a field's absence; see\n * `openLeadsFromCounts`. Leads are host-scoped by path rather than by\n * `visibleTo`, so those two counts run on `hosts/{hostId}/leads` under the\n * site's own rules, the way the Leads section reads it.\n *\n * The widget is handed a host doc id and nothing else, so the org — needed\n * for the consent group whose tokens scope the reads — comes from\n * `useOrgPlan`, and the link base from `useConsoleHostRoute`, the way the\n * commerce glance card builds its store link. Nothing is read until the org\n * has settled: a first round of aggregates against the solo group, replaced\n * by a second against the real one, would be two reads for one answer.\n *\n * ## The organization level (AGL-2634)\n *\n * Mounted with no site beneath the org hub's mount, the card totals the\n * organization: the reads carry no scope clause — the rules admit an\n * org-wide member to every row — and the lead figure is the same two\n * counts per site, over every site the mount lists, summed. Nothing is\n * read until that list has settled, for the reason nothing is read under\n * a site until the org has. The org's dashboard has no widget slot yet, so\n * the mount is a capability rather than a page; what it needs from the\n * shell is a slot and the hub's `basePath`.\n */\nexport function CrmGlanceCard(props: CrmGlanceCardProps) {\n const { hostId, basePath } = props\n const mount = useCrmOrgMount()\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: planReady } = useOrgPlan(hostId ?? undefined)\n const { scope } = useOrgDataScope(hostId ? { hostId } : { orgId: mount?.orgId })\n const firestore = useFirestore()\n // Under a site, the site's group's tokens once the org has settled; at\n // the org level `null` — no clause — once the site list has.\n const tokens = useMemo(() => {\n if (!hostId) return null\n if (!planReady) return undefined\n const group = Aglyn.consentGroupForHost(org ?? null, hostId)\n return [\n Aglyn.ORG_SCOPE_TOKEN,\n ...group.hostIds.map((id) => Aglyn.hostScopeToken(id)),\n ].slice(0, Aglyn.MAX_SCOPE_HOSTS)\n }, [org, planReady, hostId])\n const leadSites = useMemo(\n () =>\n hostId\n ? [hostId]\n : mount?.hostsReady\n ? mount.hosts.map((host) => host.id)\n : null,\n [hostId, mount],\n )\n const leadSitesKey = leadSites?.join('\\n') ?? ''\n // Anchored once per mount so the queries are stable across renders.\n const nowMs = useMemo(() => Date.now(), [])\n\n const figures = useAggregateRead<GlanceFigures>(() => {\n if (!scope || tokens === undefined || !leadSites) return null\n const contacts = scopedCollection(firestore, scope, 'contacts')\n const deals = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.deals)\n const tasks = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks)\n const day = localDayBounds(nowMs)\n const countOf = (target: ReturnType<typeof query>) =>\n getCountFromServer(target).then((snapshot) => snapshot.data().count)\n return Promise.all([\n countOf(query(contacts, ...visibleToClause(tokens))),\n countOf(\n query(\n contacts,\n ...visibleToClause(tokens),\n where('createdAt', '>=', Timestamp.fromMillis(nowMs - WEEK_MS)),\n ),\n ),\n getAggregateFromServer(\n query(deals, ...visibleToClause(tokens), where('status', '==', 'open')),\n { amountCents: sum('amountCents') },\n ).then((snapshot) => Number(snapshot.data().amountCents ?? 0)),\n // Due today OR overdue: everything open that is due before the day ends.\n countOf(\n query(\n tasks,\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n where('dueAtMs', '<', day.end),\n ),\n ),\n // One scoped count over the org collection (AGL-3275) — see the\n // function's note on why summing per site would double-count.\n openLeadsForScope(firestore, scope[1], tokens),\n ]).then(([contacts, newThisWeek, pipelineCents, tasksDue, leadsToWork]) => ({\n contacts,\n newThisWeek,\n pipelineCents,\n tasksDue,\n leadsToWork,\n }))\n }, [firestore, scope, tokens, nowMs, leadSitesKey])\n\n const routes = useMemo(\n () =>\n hostId\n ? consoleRoute.base\n ? crmRoutes(`${consoleRoute.base}/crm`)\n : null\n : basePath\n ? crmRoutes(basePath)\n : null,\n [hostId, consoleRoute.base, basePath],\n )\n const hubHref = hostId\n ? consoleRoute.base\n ? `${consoleRoute.base}/crm`\n : undefined\n : basePath\n const value = figures.value\n\n return (\n <CardDisplay\n header={'CRM at a glance'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#crm-at-a-glance',\n excerpt:\n 'Contacts, new contacts this week, the value of every open deal, ' +\n 'the tasks due today or overdue, and the leads still to work — ' +\n 'each counted on the server and each a link into the CRM.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={hubHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open CRM'}\n </Button>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack useFlexGap direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Contacts'}\n value={value ? value.contacts.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'New this week'}\n value={value ? value.newThisWeek.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'Open pipeline'}\n value={value ? money(value.pipelineCents, 'usd') : null}\n href={routes?.section('deals')}\n />\n <ReportStatTile\n label={'Tasks due'}\n value={value ? value.tasksDue.toLocaleString() : null}\n note={'today or overdue'}\n color={value?.tasksDue ? 'warning.main' : undefined}\n href={routes?.section('tasks')}\n />\n <ReportStatTile\n label={'Leads to work'}\n value={value ? value.leadsToWork.toLocaleString() : null}\n note={hostId ? 'open on this site' : 'open across every site'}\n href={routes?.section('leads')}\n />\n </Stack>\n {figures.status === 'error' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The CRM figures could not be read. Open the CRM for the full picture.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nCrmGlanceCard.displayName = 'CrmGlanceCard'\n\nexport default CrmGlanceCard\n"],"names":["Aglyn","money","AppLink","CardDisplay","Button","Stack","Typography","getAggregateFromServer","getCountFromServer","query","sum","Timestamp","where","useMemo","useConsoleHostRoute","useFirestore","useOrgDataScope","useOrgPlan","useCrmOrgMount","crmRoutes","openLeadsForScope","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","localDayBounds","WEEK_MS","CrmGlanceCard","props","hostId","basePath","mount","consoleRoute","org","ready","planReady","undefined","scope","orgId","firestore","tokens","group","consentGroupForHost","ORG_SCOPE_TOKEN","hostIds","map","id","hostScopeToken","slice","MAX_SCOPE_HOSTS","leadSites","hostsReady","hosts","host","leadSitesKey","join","nowMs","Date","now","figures","contacts","deals","CRM_COLLECTIONS","tasks","day","countOf","target","then","snapshot","data","count","Promise","all","fromMillis","amountCents","Number","end","newThisWeek","pipelineCents","tasksDue","leadsToWork","routes","base","hubHref","value","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","size","color","spacing","useFlexGap","direction","sx","flexWrap","label","toLocaleString","section","note","status","variant","displayName"],"mappings":"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"}
@@ -8,10 +8,11 @@ import type { GridColDef } from '@mui/x-data-grid';
8
8
  * color, today emphasized — because it IS that date, seen from the record.
9
9
  * A dash for a record with nothing scheduled, and the same dash for one
10
10
  * written before the field existed: the reader's question is "is anything
11
- * planned", and to both the answer is no. Sorting is off for the reason
12
- * the custom columns' is — the value is on the row, and a sort over the
13
- * loaded window would look like a sort over the collection. Filtering is
14
- * the list's to switch on, through `CRM_NEXT_ACTIVITY_FILTER_FIELD`.
11
+ * planned", and to both the answer is no. The grid's own sort is off — a
12
+ * sort over the loaded window would look like a sort over the collection —
13
+ * and the list sorts the header instead, on its query, through its
14
+ * `columnSort` (AGL-3680). Filtering is the list's to switch on, through
15
+ * `CRM_NEXT_ACTIVITY_FILTER_FIELD`.
15
16
  */
16
17
  export declare function nextActivityColumn(nowMs: number): GridColDef;
17
18
  export { CRM_NEXT_ACTIVITY_FILTER_FIELD, CRM_NEXT_ACTIVITY_FILTER_HEADER, } from '../model/crm-next-activity';
@@ -27,10 +27,11 @@ import { CRM_NEXT_ACTIVITY_FIELD, readNextTaskAtMs } from "../model/crm-next-act
27
27
  * color, today emphasized — because it IS that date, seen from the record.
28
28
  * A dash for a record with nothing scheduled, and the same dash for one
29
29
  * written before the field existed: the reader's question is "is anything
30
- * planned", and to both the answer is no. Sorting is off for the reason
31
- * the custom columns' is — the value is on the row, and a sort over the
32
- * loaded window would look like a sort over the collection. Filtering is
33
- * the list's to switch on, through `CRM_NEXT_ACTIVITY_FILTER_FIELD`.
30
+ * planned", and to both the answer is no. The grid's own sort is off — a
31
+ * sort over the loaded window would look like a sort over the collection —
32
+ * and the list sorts the header instead, on its query, through its
33
+ * `columnSort` (AGL-3680). Filtering is the list's to switch on, through
34
+ * `CRM_NEXT_ACTIVITY_FILTER_FIELD`.
34
35
  */ export function nextActivityColumn(nowMs) {
35
36
  return {
36
37
  field: CRM_NEXT_ACTIVITY_FIELD,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-next-activity-column.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 { Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { TaskDueText } from './task-cells'\nimport { CRM_NEXT_ACTIVITY_FIELD, readNextTaskAtMs } from '../model/crm-next-activity'\n\n/**\n * The \"Next activity\" column the contacts, companies and deals lists share\n * (AGL-2661): when the record's earliest open task is due, off the\n * `nextTaskAtMs` every server-side task writer keeps on the row.\n *\n * Drawn the way the tasks list draws a due date — overdue in the warning\n * color, today emphasized — because it IS that date, seen from the record.\n * A dash for a record with nothing scheduled, and the same dash for one\n * written before the field existed: the reader's question is \"is anything\n * planned\", and to both the answer is no. Sorting is off for the reason\n * the custom columns' is — the value is on the row, and a sort over the\n * loaded window would look like a sort over the collection. Filtering is\n * the list's to switch on, through `CRM_NEXT_ACTIVITY_FILTER_FIELD`.\n */\nexport function nextActivityColumn(nowMs: number): GridColDef {\n return {\n field: CRM_NEXT_ACTIVITY_FIELD,\n headerName: 'Next activity',\n flex: 0.8,\n minWidth: 140,\n type: 'date',\n sortable: false,\n filterable: false,\n valueGetter: (_value: unknown, row: { nextTaskAtMs?: unknown }) => {\n const at = readNextTaskAtMs(row)\n return at === null ? null : new Date(at)\n },\n renderCell: ({ row }: { row: { nextTaskAtMs?: unknown } }) => {\n const at = readNextTaskAtMs(row)\n if (at === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n }\n return (\n <Tooltip title={new Date(at).toLocaleString()}>\n <span>\n <TaskDueText task={{ status: 'open', dueAtMs: at }} nowMs={nowMs} variant=\"caption\" />\n </span>\n </Tooltip>\n )\n },\n }\n}\n\nexport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n} from '../model/crm-next-activity'\n"],"names":["Tooltip","Typography","TaskDueText","CRM_NEXT_ACTIVITY_FIELD","readNextTaskAtMs","nextActivityColumn","nowMs","field","headerName","flex","minWidth","type","sortable","filterable","valueGetter","_value","row","at","Date","renderCell","variant","color","title","toLocaleString","span","task","status","dueAtMs","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,OAAO,EAAEC,UAAU,QAAQ,gBAAe;AAEnD,SAASC,WAAW,QAAQ,kBAAc;AAC1C,SAASC,uBAAuB,EAAEC,gBAAgB,QAAQ,gCAA4B;AAEtF;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,mBAAmBC,KAAa;IAC9C,OAAO;QACLC,OAAOJ;QACPK,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,MAAM;QACNC,UAAU;QACVC,YAAY;QACZC,aAAa,CAACC,QAAiBC;YAC7B,MAAMC,KAAKb,iBAAiBY;YAC5B,OAAOC,OAAO,OAAO,OAAO,IAAIC,KAAKD;QACvC;QACAE,YAAY,CAAC,EAAEH,GAAG,EAAuC;YACvD,MAAMC,KAAKb,iBAAiBY;YAC5B,IAAIC,OAAO,MAAM;gBACf,qBACE,KAAChB;oBAAWmB,SAAQ;oBAAUC,OAAM;8BACjC;;YAGP;YACA,qBACE,KAACrB;gBAAQsB,OAAO,IAAIJ,KAAKD,IAAIM,cAAc;0BACzC,cAAA,KAACC;8BACC,cAAA,KAACtB;wBAAYuB,MAAM;4BAAEC,QAAQ;4BAAQC,SAASV;wBAAG;wBAAGX,OAAOA;wBAAOc,SAAQ;;;;QAIlF;IACF;AACF;AAEA,SACEQ,8BAA8B,EAC9BC,+BAA+B,QAC1B,gCAA4B"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-next-activity-column.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 { Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { TaskDueText } from './task-cells'\nimport { CRM_NEXT_ACTIVITY_FIELD, readNextTaskAtMs } from '../model/crm-next-activity'\n\n/**\n * The \"Next activity\" column the contacts, companies and deals lists share\n * (AGL-2661): when the record's earliest open task is due, off the\n * `nextTaskAtMs` every server-side task writer keeps on the row.\n *\n * Drawn the way the tasks list draws a due date — overdue in the warning\n * color, today emphasized — because it IS that date, seen from the record.\n * A dash for a record with nothing scheduled, and the same dash for one\n * written before the field existed: the reader's question is \"is anything\n * planned\", and to both the answer is no. The grid's own sort is off — a\n * sort over the loaded window would look like a sort over the collection —\n * and the list sorts the header instead, on its query, through its\n * `columnSort` (AGL-3680). Filtering is the list's to switch on, through\n * `CRM_NEXT_ACTIVITY_FILTER_FIELD`.\n */\nexport function nextActivityColumn(nowMs: number): GridColDef {\n return {\n field: CRM_NEXT_ACTIVITY_FIELD,\n headerName: 'Next activity',\n flex: 0.8,\n minWidth: 140,\n type: 'date',\n sortable: false,\n filterable: false,\n valueGetter: (_value: unknown, row: { nextTaskAtMs?: unknown }) => {\n const at = readNextTaskAtMs(row)\n return at === null ? null : new Date(at)\n },\n renderCell: ({ row }: { row: { nextTaskAtMs?: unknown } }) => {\n const at = readNextTaskAtMs(row)\n if (at === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n }\n return (\n <Tooltip title={new Date(at).toLocaleString()}>\n <span>\n <TaskDueText task={{ status: 'open', dueAtMs: at }} nowMs={nowMs} variant=\"caption\" />\n </span>\n </Tooltip>\n )\n },\n }\n}\n\nexport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n} from '../model/crm-next-activity'\n"],"names":["Tooltip","Typography","TaskDueText","CRM_NEXT_ACTIVITY_FIELD","readNextTaskAtMs","nextActivityColumn","nowMs","field","headerName","flex","minWidth","type","sortable","filterable","valueGetter","_value","row","at","Date","renderCell","variant","color","title","toLocaleString","span","task","status","dueAtMs","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,OAAO,EAAEC,UAAU,QAAQ,gBAAe;AAEnD,SAASC,WAAW,QAAQ,kBAAc;AAC1C,SAASC,uBAAuB,EAAEC,gBAAgB,QAAQ,gCAA4B;AAEtF;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,mBAAmBC,KAAa;IAC9C,OAAO;QACLC,OAAOJ;QACPK,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,MAAM;QACNC,UAAU;QACVC,YAAY;QACZC,aAAa,CAACC,QAAiBC;YAC7B,MAAMC,KAAKb,iBAAiBY;YAC5B,OAAOC,OAAO,OAAO,OAAO,IAAIC,KAAKD;QACvC;QACAE,YAAY,CAAC,EAAEH,GAAG,EAAuC;YACvD,MAAMC,KAAKb,iBAAiBY;YAC5B,IAAIC,OAAO,MAAM;gBACf,qBACE,KAAChB;oBAAWmB,SAAQ;oBAAUC,OAAM;8BACjC;;YAGP;YACA,qBACE,KAACrB;gBAAQsB,OAAO,IAAIJ,KAAKD,IAAIM,cAAc;0BACzC,cAAA,KAACC;8BACC,cAAA,KAACtB;wBAAYuB,MAAM;4BAAEC,QAAQ;4BAAQC,SAASV;wBAAG;wBAAGX,OAAOA;wBAAOc,SAAQ;;;;QAIlF;IACF;AACF;AAEA,SACEQ,8BAA8B,EAC9BC,+BAA+B,QAC1B,gCAA4B"}
@@ -72,6 +72,7 @@ import { CrmRecordBookingZone } from "./crm-booking-zone.js";
72
72
  contentGutterY: true,
73
73
  HeaderProps: {
74
74
  action: /*#__PURE__*/ _jsxs(Stack, {
75
+ useFlexGap: true,
75
76
  direction: "row",
76
77
  spacing: 1,
77
78
  sx: {
@@ -104,6 +105,7 @@ import { CrmRecordBookingZone } from "./crm-booking-zone.js";
104
105
  spacing: 2,
105
106
  children: [
106
107
  hasChipRow ? /*#__PURE__*/ _jsxs(Stack, {
108
+ useFlexGap: true,
107
109
  direction: "row",
108
110
  spacing: 1,
109
111
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-record-header.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 { PageHeaderRecord } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay, type CardDisplayProps } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { Button, Chip, type ChipProps, Stack, Typography } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport {\n CrmRecordBookingZone,\n type CrmRecordBookingZoneProps,\n} from './crm-booking-zone'\n\nexport interface CrmRecordHeaderProps {\n /** What the record is — the lead card's heading: Contact, Company, Deal, Lead. */\n kind: string\n /**\n * The record's own name, published to the page heading and the trail.\n * Undefined until it has loaded, so the route's heading stands meanwhile.\n */\n title: string | undefined\n /** The one line under the heading — an address, a domain, a pipeline and stage. */\n subtitle?: ReactNode\n /** The record's facts as chips: a stage, a status, an owner. */\n chips?: ReactNode\n /** The way back to the section the record lives in. */\n backHref: string\n backLabel: string\n /** The primary controls, beside the menu. */\n actions?: ReactNode\n /** Everything else, behind the overflow — the destructive act lives here. */\n menuItems?: RowActionsMenuItem[]\n help?: CardDisplayProps['help']\n /** The record is still on its way; the chip row says so rather than sitting empty. */\n loading?: boolean\n /** What the card holds under the chips — a properties list, or a refusal. */\n children?: ReactNode\n /**\n * The **Book a meeting** action (AGL-2660), for a contact, a lead or a\n * deal: the record the link carries, and the site whose services are\n * offered. Drawn beside the primary controls, and only where the site has\n * a booking door — the action renders nothing otherwise.\n */\n booking?: CrmRecordBookingZoneProps\n}\n\n/**\n * THE HEADER EVERY CRM RECORD PAGE OPENS ON (AGL-2614).\n *\n * A contact, a company, a deal and a lead each had a lead card of their own\n * and no two agreed: one kept its controls in the card header and one at the\n * card's foot, one put the way back in a caption under the properties, one\n * offered Delete as a red button beside Edit and one behind a menu, and the\n * page heading was published by the page on three of them and never on the\n * fourth. A reader moving between the four records of one sale was reading\n * four products.\n *\n * ## The grammar\n *\n * ```\n * [ page heading and trail: the record's name ]\n * Kind [ Back to … ] [ primary ] [ ⋮ ]\n * subtitle line\n * [ chip ] [ chip ] [ chip ]\n * children\n * ```\n *\n * The page heading is the record's NAME — published from here, once, by\n * whichever component loaded the record, so the shell's heading and the\n * card read one document. The card is then free to say what the record IS\n * (`kind`) rather than repeating the name, and its subtitle carries the one\n * fact that identifies the record beyond its name: the address, the domain,\n * the pipeline and stage.\n *\n * Controls sit in the card HEADER, never at its foot: the header is where a\n * reader looks for what they can do to the thing they just opened, and a\n * foot of controls scrolls away under a long properties list. The way back\n * leads, because the record is the trail's last crumb and the crumb is not a\n * link. The primary act — Edit, Convert, Add to list — is a button; every\n * other act, and the destructive one in particular, is a menu item, so that\n * a delete is never one mis-click from the button beside it.\n */\nexport function CrmRecordHeader(props: CrmRecordHeaderProps) {\n const {\n kind,\n title,\n subtitle,\n chips,\n backHref,\n backLabel,\n actions,\n menuItems,\n help,\n loading,\n children,\n booking,\n } = props\n const hasChipRow = Boolean(chips) || Boolean(loading)\n return (\n <>\n <PageHeaderRecord title={title} />\n <CardDisplay\n header={kind}\n subheader={subtitle}\n help={help}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={backHref}\n size=\"small\"\n color=\"primary\"\n >\n {backLabel}\n </Button>\n {actions}\n {booking ? <CrmRecordBookingZone {...booking} /> : null}\n {menuItems?.length ? (\n <RowActionsMenu label={title || kind} items={menuItems} />\n ) : null}\n </Stack>\n ),\n }}\n >\n {hasChipRow || children ? (\n <Stack spacing={2}>\n {hasChipRow ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n {chips}\n {loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {children}\n </Stack>\n ) : null}\n </CardDisplay>\n </>\n )\n}\nCrmRecordHeader.displayName = 'CrmRecordHeader'\n\nexport interface CrmRecordChipProps {\n /** What the fact is — Owner, Industry, Amount. */\n label: string\n /** The fact. Nothing is rendered without one, so a caller can pass it blindly. */\n value?: ReactNode\n color?: ChipProps['color']\n variant?: ChipProps['variant']\n}\n\n/**\n * One fact on the chip row, in the `Label: value` form every record uses.\n *\n * A chip with no value renders nothing rather than a bare label, so a\n * record page lists every fact it might have and the row shows the ones\n * this record does.\n */\nexport function CrmRecordChip(props: CrmRecordChipProps) {\n const { label, value, color, variant = 'outlined' } = props\n if (value === null || value === undefined || value === '') return null\n return (\n <Chip\n size=\"small\"\n variant={variant}\n color={color}\n label={\n <>\n <Typography component=\"span\" variant=\"inherit\" color=\"text.secondary\">\n {`${label}: `}\n </Typography>\n {value}\n </>\n }\n />\n )\n}\nCrmRecordChip.displayName = 'CrmRecordChip'\n\nexport default CrmRecordHeader\n"],"names":["PageHeaderRecord","AppLink","CardDisplay","RowActionsMenu","Button","Chip","Stack","Typography","CrmRecordBookingZone","CrmRecordHeader","props","kind","title","subtitle","chips","backHref","backLabel","actions","menuItems","help","loading","children","booking","hasChipRow","Boolean","header","subheader","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","sx","alignItems","flexWrap","rowGap","component","componentVariant","nativeButton","href","size","color","length","label","items","variant","displayName","CrmRecordChip","value","undefined"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,gBAAgB,QAAQ,eAAc;AAC/C,SAASC,OAAO,EAAEC,WAAW,QAA+B,uBAAsB;AAClF,OAAOC,oBAEA,6DAA4D;AACnE,SAASC,MAAM,EAAEC,IAAI,EAAkBC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAE/E,SACEC,oBAAoB,QAEf,wBAAoB;AAmC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,OAAO,SAASC,gBAAgBC,KAA2B;IACzD,MAAM,EACJC,IAAI,EACJC,KAAK,EACLC,QAAQ,EACRC,KAAK,EACLC,QAAQ,EACRC,SAAS,EACTC,OAAO,EACPC,SAAS,EACTC,IAAI,EACJC,OAAO,EACPC,QAAQ,EACRC,OAAO,EACR,GAAGZ;IACJ,MAAMa,aAAaC,QAAQV,UAAUU,QAAQJ;IAC7C,qBACE;;0BACE,KAACpB;gBAAiBY,OAAOA;;0BACzB,KAACV;gBACCuB,QAAQd;gBACRe,WAAWb;gBACXM,MAAMA;gBACNQ,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACXC,sBACE,MAACxB;wBACCyB,WAAU;wBACVC,SAAS;wBACTC,IAAI;4BAAEC,YAAY;4BAAUC,UAAU;4BAAQC,QAAQ;wBAAE;;0CAExD,KAAChC;gCACCiC,WAAWpC;+BACN;gCAAEqC,kBAAkB;gCAASC,cAAc;4BAAM;gCACtDC,MAAMzB;gCACN0B,MAAK;gCACLC,OAAM;0CAEL1B;;4BAEFC;4BACAK,wBAAU,KAACd,mCAAyBc,YAAc;4BAClDJ,CAAAA,6BAAAA,UAAWyB,MAAM,kBAChB,KAACxC;gCAAeyC,OAAOhC,SAASD;gCAAMkC,OAAO3B;iCAC3C;;;gBAGV;0BAECK,cAAcF,yBACb,MAACf;oBAAM0B,SAAS;;wBACbT,2BACC,MAACjB;4BACCyB,WAAU;4BACVC,SAAS;4BACTC,IAAI;gCAAEC,YAAY;gCAAUC,UAAU;gCAAQC,QAAQ;4BAAE;;gCAEvDtB;gCACAM,wBACC,KAACb;oCAAWuC,SAAQ;oCAAQJ,OAAM;8CAC/B;qCAED;;6BAEJ;wBACHrB;;qBAED;;;;AAIZ;AACAZ,gBAAgBsC,WAAW,GAAG;AAW9B;;;;;;CAMC,GACD,OAAO,SAASC,cAActC,KAAyB;IACrD,MAAM,EAAEkC,KAAK,EAAEK,KAAK,EAAEP,KAAK,EAAEI,UAAU,UAAU,EAAE,GAAGpC;IACtD,IAAIuC,UAAU,QAAQA,UAAUC,aAAaD,UAAU,IAAI,OAAO;IAClE,qBACE,KAAC5C;QACCoC,MAAK;QACLK,SAASA;QACTJ,OAAOA;QACPE,qBACE;;8BACE,KAACrC;oBAAW8B,WAAU;oBAAOS,SAAQ;oBAAUJ,OAAM;8BAClD,GAAGE,MAAM,EAAE,CAAC;;gBAEdK;;;;AAKX;AACAD,cAAcD,WAAW,GAAG;AAE5B,eAAetC,gBAAe"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-record-header.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 { PageHeaderRecord } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay, type CardDisplayProps } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { Button, Chip, type ChipProps, Stack, Typography } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport {\n CrmRecordBookingZone,\n type CrmRecordBookingZoneProps,\n} from './crm-booking-zone'\n\nexport interface CrmRecordHeaderProps {\n /** What the record is — the lead card's heading: Contact, Company, Deal, Lead. */\n kind: string\n /**\n * The record's own name, published to the page heading and the trail.\n * Undefined until it has loaded, so the route's heading stands meanwhile.\n */\n title: string | undefined\n /** The one line under the heading — an address, a domain, a pipeline and stage. */\n subtitle?: ReactNode\n /** The record's facts as chips: a stage, a status, an owner. */\n chips?: ReactNode\n /** The way back to the section the record lives in. */\n backHref: string\n backLabel: string\n /** The primary controls, beside the menu. */\n actions?: ReactNode\n /** Everything else, behind the overflow — the destructive act lives here. */\n menuItems?: RowActionsMenuItem[]\n help?: CardDisplayProps['help']\n /** The record is still on its way; the chip row says so rather than sitting empty. */\n loading?: boolean\n /** What the card holds under the chips — a properties list, or a refusal. */\n children?: ReactNode\n /**\n * The **Book a meeting** action (AGL-2660), for a contact, a lead or a\n * deal: the record the link carries, and the site whose services are\n * offered. Drawn beside the primary controls, and only where the site has\n * a booking door — the action renders nothing otherwise.\n */\n booking?: CrmRecordBookingZoneProps\n}\n\n/**\n * THE HEADER EVERY CRM RECORD PAGE OPENS ON (AGL-2614).\n *\n * A contact, a company, a deal and a lead each had a lead card of their own\n * and no two agreed: one kept its controls in the card header and one at the\n * card's foot, one put the way back in a caption under the properties, one\n * offered Delete as a red button beside Edit and one behind a menu, and the\n * page heading was published by the page on three of them and never on the\n * fourth. A reader moving between the four records of one sale was reading\n * four products.\n *\n * ## The grammar\n *\n * ```\n * [ page heading and trail: the record's name ]\n * Kind [ Back to … ] [ primary ] [ ⋮ ]\n * subtitle line\n * [ chip ] [ chip ] [ chip ]\n * children\n * ```\n *\n * The page heading is the record's NAME — published from here, once, by\n * whichever component loaded the record, so the shell's heading and the\n * card read one document. The card is then free to say what the record IS\n * (`kind`) rather than repeating the name, and its subtitle carries the one\n * fact that identifies the record beyond its name: the address, the domain,\n * the pipeline and stage.\n *\n * Controls sit in the card HEADER, never at its foot: the header is where a\n * reader looks for what they can do to the thing they just opened, and a\n * foot of controls scrolls away under a long properties list. The way back\n * leads, because the record is the trail's last crumb and the crumb is not a\n * link. The primary act — Edit, Convert, Add to list — is a button; every\n * other act, and the destructive one in particular, is a menu item, so that\n * a delete is never one mis-click from the button beside it.\n */\nexport function CrmRecordHeader(props: CrmRecordHeaderProps) {\n const {\n kind,\n title,\n subtitle,\n chips,\n backHref,\n backLabel,\n actions,\n menuItems,\n help,\n loading,\n children,\n booking,\n } = props\n const hasChipRow = Boolean(chips) || Boolean(loading)\n return (\n <>\n <PageHeaderRecord title={title} />\n <CardDisplay\n header={kind}\n subheader={subtitle}\n help={help}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={backHref}\n size=\"small\"\n color=\"primary\"\n >\n {backLabel}\n </Button>\n {actions}\n {booking ? <CrmRecordBookingZone {...booking} /> : null}\n {menuItems?.length ? (\n <RowActionsMenu label={title || kind} items={menuItems} />\n ) : null}\n </Stack>\n ),\n }}\n >\n {hasChipRow || children ? (\n <Stack spacing={2}>\n {hasChipRow ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n {chips}\n {loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {children}\n </Stack>\n ) : null}\n </CardDisplay>\n </>\n )\n}\nCrmRecordHeader.displayName = 'CrmRecordHeader'\n\nexport interface CrmRecordChipProps {\n /** What the fact is — Owner, Industry, Amount. */\n label: string\n /** The fact. Nothing is rendered without one, so a caller can pass it blindly. */\n value?: ReactNode\n color?: ChipProps['color']\n variant?: ChipProps['variant']\n}\n\n/**\n * One fact on the chip row, in the `Label: value` form every record uses.\n *\n * A chip with no value renders nothing rather than a bare label, so a\n * record page lists every fact it might have and the row shows the ones\n * this record does.\n */\nexport function CrmRecordChip(props: CrmRecordChipProps) {\n const { label, value, color, variant = 'outlined' } = props\n if (value === null || value === undefined || value === '') return null\n return (\n <Chip\n size=\"small\"\n variant={variant}\n color={color}\n label={\n <>\n <Typography component=\"span\" variant=\"inherit\" color=\"text.secondary\">\n {`${label}: `}\n </Typography>\n {value}\n </>\n }\n />\n )\n}\nCrmRecordChip.displayName = 'CrmRecordChip'\n\nexport default CrmRecordHeader\n"],"names":["PageHeaderRecord","AppLink","CardDisplay","RowActionsMenu","Button","Chip","Stack","Typography","CrmRecordBookingZone","CrmRecordHeader","props","kind","title","subtitle","chips","backHref","backLabel","actions","menuItems","help","loading","children","booking","hasChipRow","Boolean","header","subheader","contentGutterX","contentGutterY","HeaderProps","action","useFlexGap","direction","spacing","sx","alignItems","flexWrap","rowGap","component","componentVariant","nativeButton","href","size","color","length","label","items","variant","displayName","CrmRecordChip","value","undefined"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,gBAAgB,QAAQ,eAAc;AAC/C,SAASC,OAAO,EAAEC,WAAW,QAA+B,uBAAsB;AAClF,OAAOC,oBAEA,6DAA4D;AACnE,SAASC,MAAM,EAAEC,IAAI,EAAkBC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAE/E,SACEC,oBAAoB,QAEf,wBAAoB;AAmC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,OAAO,SAASC,gBAAgBC,KAA2B;IACzD,MAAM,EACJC,IAAI,EACJC,KAAK,EACLC,QAAQ,EACRC,KAAK,EACLC,QAAQ,EACRC,SAAS,EACTC,OAAO,EACPC,SAAS,EACTC,IAAI,EACJC,OAAO,EACPC,QAAQ,EACRC,OAAO,EACR,GAAGZ;IACJ,MAAMa,aAAaC,QAAQV,UAAUU,QAAQJ;IAC7C,qBACE;;0BACE,KAACpB;gBAAiBY,OAAOA;;0BACzB,KAACV;gBACCuB,QAAQd;gBACRe,WAAWb;gBACXM,MAAMA;gBACNQ,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACXC,sBACE,MAACxB;wBACCyB,UAAU;wBACVC,WAAU;wBACVC,SAAS;wBACTC,IAAI;4BAAEC,YAAY;4BAAUC,UAAU;4BAAQC,QAAQ;wBAAE;;0CAExD,KAACjC;gCACCkC,WAAWrC;+BACN;gCAAEsC,kBAAkB;gCAASC,cAAc;4BAAM;gCACtDC,MAAM1B;gCACN2B,MAAK;gCACLC,OAAM;0CAEL3B;;4BAEFC;4BACAK,wBAAU,KAACd,mCAAyBc,YAAc;4BAClDJ,CAAAA,6BAAAA,UAAW0B,MAAM,kBAChB,KAACzC;gCAAe0C,OAAOjC,SAASD;gCAAMmC,OAAO5B;iCAC3C;;;gBAGV;0BAECK,cAAcF,yBACb,MAACf;oBAAM2B,SAAS;;wBACbV,2BACC,MAACjB;4BACCyB,UAAU;4BACVC,WAAU;4BACVC,SAAS;4BACTC,IAAI;gCAAEC,YAAY;gCAAUC,UAAU;gCAAQC,QAAQ;4BAAE;;gCAEvDvB;gCACAM,wBACC,KAACb;oCAAWwC,SAAQ;oCAAQJ,OAAM;8CAC/B;qCAED;;6BAEJ;wBACHtB;;qBAED;;;;AAIZ;AACAZ,gBAAgBuC,WAAW,GAAG;AAW9B;;;;;;CAMC,GACD,OAAO,SAASC,cAAcvC,KAAyB;IACrD,MAAM,EAAEmC,KAAK,EAAEK,KAAK,EAAEP,KAAK,EAAEI,UAAU,UAAU,EAAE,GAAGrC;IACtD,IAAIwC,UAAU,QAAQA,UAAUC,aAAaD,UAAU,IAAI,OAAO;IAClE,qBACE,KAAC7C;QACCqC,MAAK;QACLK,SAASA;QACTJ,OAAOA;QACPE,qBACE;;8BACE,KAACtC;oBAAW+B,WAAU;oBAAOS,SAAQ;oBAAUJ,OAAM;8BAClD,GAAGE,MAAM,EAAE,CAAC;;gBAEdK;;;;AAKX;AACAD,cAAcD,WAAW,GAAG;AAE5B,eAAevC,gBAAe"}
@@ -38,7 +38,7 @@ import { crmCustomDraftDocument, crmCustomDraftMissingRequired, crmCustomDraftVa
38
38
  import { CrmCustomFieldControl } from "./crm-custom-field-control.js";
39
39
  import { CrmPicklistSelect } from "./picklist-select.js";
40
40
  import { CrmSitePicker } from "./crm-site-picker.js";
41
- import { CRM_CLIENT_SEARCH_FIELDS, CRM_DEAL_CONTACT_ROLE_LIST_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
41
+ import { CRM_CLIENT_SEARCH_FIELDS, CRM_CLIENT_SORT_FIELDS, CRM_DEAL_CONTACT_ROLE_LIST_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
42
42
  import { DEAL_CURRENCIES, openStages } from "../model/deal-board-model.js";
43
43
  import { contactChoicesFor, DEAL_NOTES_MAX, DEAL_TITLE_MAX, dealDocumentFromForm, dealFormFromDoc, dealFormProblem, dealPatchFromForm, emptyDealForm, judgeDealFormPicklists } from "../model/deal-form-model.js";
44
44
  import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
@@ -51,6 +51,8 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
51
51
  /** Companies offered per keystroke; a longer list is a scroll, not a pick. */ const COMPANY_MATCHES = 8;
52
52
  /** The list fields an edit restamps: the title's search words, the two picklist keys. */ const DEAL_CLIENT_LIST_FIELDS = [
53
53
  ...CRM_CLIENT_SEARCH_FIELDS,
54
+ // The Deal, Amount and Expected close headers' sort keys (AGL-3680).
55
+ ...CRM_CLIENT_SORT_FIELDS,
54
56
  'typeKey',
55
57
  'leadSourceKey',
56
58
  // The arrays a deal's contact roles are found by (AGL-3521).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n CRM_COLLECTIONS,\n CRM_FORECAST_CATEGORIES,\n CRM_FORECAST_CATEGORY_LABELS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmForecastCategory,\n crmNewRecordListFields,\n DEAL_NEXT_STEP_MAX,\n dealHasLineItems,\n dealStageById,\n dealStageForecastCategory,\n findOrgMember,\n nameSearchKey,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n deleteField,\n doc,\n endAt,\n getDocs,\n limit,\n orderBy,\n query,\n startAt,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport {\n type CrmOrgDoc,\n crmScopeListable,\n crmVisibleToClause,\n useCrmScope,\n} from '../hooks/use-crm-scope'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { contactPrimaryGroup } from '../model/contact-record'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { CrmSitePicker } from './crm-site-picker'\nimport {\n CRM_CLIENT_SEARCH_FIELDS,\n CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n crmClientListFields,\n} from '../model/crm-list-query'\nimport {\n DEAL_CURRENCIES,\n type DealDoc,\n openStages,\n type PipelineDoc,\n} from '../model/deal-board-model'\nimport {\n type ContactChoice,\n contactChoicesFor,\n DEAL_NOTES_MAX,\n DEAL_TITLE_MAX,\n dealDocumentFromForm,\n dealFormFromDoc,\n dealFormProblem,\n type DealFormValues,\n dealPatchFromForm,\n emptyDealForm,\n judgeDealFormPicklists,\n} from '../model/deal-form-model'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/**\n * How many recent contacts the picker matches against. The window is the\n * ask — it is read when the drawer opens, not when the section does — and\n * a person outside it is still linkable by pasting their address, which\n * the match tests as a prefix of the email.\n */\nconst CONTACT_WINDOW = 300\n\n/** Companies offered per keystroke; a longer list is a scroll, not a pick. */\nconst COMPANY_MATCHES = 8\n\n/** The list fields an edit restamps: the title's search words, the two picklist keys. */\nconst DEAL_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n 'typeKey',\n 'leadSourceKey',\n // The arrays a deal's contact roles are found by (AGL-3521).\n ...CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n] as const\n\n/** A company as the picker offers it. */\ninterface CompanyChoice {\n id: string\n name: string\n}\n\nexport interface DealEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW deal asks which site captures it and an\n * edit keeps the deal's own.\n */\n hostId: string | null\n org: CrmOrgDoc\n /** The deal being edited; absent for a new one. */\n deal?: DealDoc | null\n /** Links and values a new deal opens with — a contact's page passes itself. */\n defaults?: Partial<DealFormValues>\n pipelines: PipelineDoc[]\n /** Where a new deal lands when nobody picks a pipeline. */\n defaultPipeline: PipelineDoc | null\n /** The listener's verdict on the row being edited, for the stale-seed guard. */\n unreadable?: boolean\n fromCache?: boolean\n /** After a save, with the id of the deal written. */\n onSaved?: (dealId: string, mode: 'create' | 'edit') => void\n}\n\n/**\n * The one form a deal is created and edited through (AGL-2598).\n *\n * ## What it writes, and what it does not\n *\n * Client-direct against the rules, like a contact's notes: the title, the\n * amount and its currency, the expected close, the owner, the linked\n * contact and company, and the notes. The stage and the status are shown on\n * a new deal — it has to start somewhere — and never on an existing one,\n * because a stage change is the moment an automation listens for and only\n * the server route can emit that event. A drawer that wrote `stageId`\n * directly would move a deal without anybody hearing it.\n *\n * Nor the amount of a deal that has line items (AGL-2620): it is their\n * sum, the products card on the deal's page owns it, and the field here\n * is read-only with a caption saying so. The pipeline picker on a new\n * deal lists every ACTIVE pipeline (`pipelines`) and opens on the one the\n * caller was looking at.\n *\n * ## The pickers\n *\n * The owner is picked from the org's roster, the company by a server-side\n * prefix on `nameLower` (indexed with the scope), and the contact from a\n * bounded window of the newest contacts matched in memory — see\n * `contactChoicesFor` for why the contact search cannot be a query. Both\n * pickers copy the chosen NAME onto the deal beside the id, so a board of\n * cards can caption itself without a read per card.\n *\n * ## Salesforce's Opportunity fields (AGL-3516)\n *\n * Type and Lead source are the org's picklists, judged on save as every\n * server door judges them (`judgeDealFormPicklists`) — a new deal with no\n * Type takes the list's default. The probability is this deal's own\n * override, blank for its stage's; the forecast category is one of the\n * fixed set, \"From the stage\" storing the stage's. A stage move later\n * stamps both afresh. The campaign is one of the campaigns the Campaigns\n * picker of a lead offers at this level.\n */\nexport function DealEditDrawer(props: DealEditDrawerProps) {\n const {\n open,\n onClose,\n hostId,\n org,\n deal,\n defaults,\n pipelines,\n defaultPipeline,\n unreadable = false,\n fromCache = false,\n onSaved,\n } = props\n const mode: 'create' | 'edit' = deal ? 'edit' : 'create'\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const { orgId, consentGroup, visibleTo, createHostId, createTokens } =\n useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the deal is STAMPED, which at the hub is a\n // site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n const roster = useOrgMemberDirectory(open ? orgId : null)\n\n const [values, setValues] = useState<DealFormValues>(() =>\n emptyDealForm(defaultPipeline, defaults),\n )\n const owner = findOrgMember(roster.members, values.ownerUid)\n const [busy, setBusy] = useState(false)\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's deal definitions, and the keys\n * the reader has touched over what the deal stores — saved as the\n * difference (`crm-custom-draft`) in the same write as the fixed fields.\n */\n const fields = useContactFieldDefinitions(open ? orgId : null, 'deal')\n // The org's lists for the two picklist fields, and its campaigns (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', open ? orgId : null)\n const leadSourceList = useCrmPicklist('leadSource', open ? orgId : null)\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: open })\n const storedCustom = useMemo(() => deal?.custom ?? {}, [deal?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n\n // Re-seeded on every open: an existing deal's stored values, or a blank\n // form aimed at the default pipeline with whatever the opener preselected.\n useEffect(() => {\n if (!open) return\n setValues(deal ? dealFormFromDoc(deal) : emptyDealForm(defaultPipeline, defaults))\n setCustom({})\n }, [open, deal, defaultPipeline, defaults])\n\n const update = useCallback(\n (patch: Partial<DealFormValues>) =>\n setValues((current) => ({ ...current, ...patch })),\n [],\n )\n\n const pipeline = useMemo(\n () =>\n pipelines.find((entry) => entry.$id === values.pipelineId) ??\n defaultPipeline,\n [pipelines, values.pipelineId, defaultPipeline],\n )\n const stages = useMemo(() => openStages(pipeline), [pipeline])\n /*\n * The stage the deal is in — the one picked on a new deal, the stored one\n * on an edit, looked up in the deal's OWN pipeline and never the default's\n * — whose probability and forecast category the blank fields stand for.\n */\n const currentStage = useMemo(\n () =>\n dealStageById(\n pipelines.find((entry) => entry.$id === values.pipelineId),\n values.stageId,\n ),\n [pipelines, values.pipelineId, values.stageId],\n )\n const campaignOptions = useMemo(() => {\n const options = campaigns.options.map((option) => ({ value: option.value, label: option.label }))\n // A campaign the deal names but the picker no longer offers stays itself.\n return values.campaignId && !options.some((option) => option.value === values.campaignId)\n ? [...options, { value: values.campaignId, label: values.campaignId }]\n : options\n }, [campaigns.options, values.campaignId])\n\n /*\n * THE CONTACT WINDOW: the newest contacts this viewer may see, read only\n * while the drawer is open. Scoped and ordered like the contacts list —\n * the same `(visibleTo, updatedAt DESC)` index — and bounded, because the\n * picker's job is to find one person, not to list the audience.\n */\n const { data: contactRows } = useFirestoreCollection<Record<string, unknown>>(\n () =>\n open && orgId && crmScopeListable(visibleTo)\n ? query(\n collection(firestore, 'orgs', orgId, 'contacts'),\n ...crmVisibleToClause(visibleTo),\n orderBy('updatedAt', 'desc'),\n limit(CONTACT_WINDOW),\n )\n : null,\n [firestore, open, orgId, visibleTo],\n { idField: '$id' },\n )\n const [contactQuery, setContactQuery] = useState('')\n // Named through the viewing group under a site; at the organization level\n // through each person's own primary holder.\n const contactChoices = useMemo(\n () =>\n contactChoicesFor(\n contactQuery,\n contactRows ?? [],\n consentGroup?.groupId ??\n ((row) => contactPrimaryGroup(row, org as Record<string, unknown>).groupId),\n ),\n [contactQuery, contactRows, consentGroup, org],\n )\n const contactValue: ContactChoice | null = values.contactId\n ? {\n id: values.contactId,\n name: values.contactName,\n email:\n contactChoices.find((choice) => choice.id === values.contactId)?.email ?? '',\n }\n : null\n\n /*\n * THE COMPANY SEARCH: a prefix on `nameLower` with the scope, which the\n * companies collection indexes. Debounced so a name typed at speed is one\n * query rather than one per letter.\n */\n const [companyQuery, setCompanyQuery] = useState('')\n const [companyChoices, setCompanyChoices] = useState<CompanyChoice[]>([])\n useEffect(() => {\n if (!open || !orgId || !crmScopeListable(visibleTo)) return\n const key = nameSearchKey(companyQuery)\n let active = true\n const timer = setTimeout(() => {\n void getDocs(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.companies),\n ...crmVisibleToClause(visibleTo),\n orderBy('nameLower'),\n ...(key ? [startAt(key), endAt(`${key}\\uf8ff`)] : []),\n limit(COMPANY_MATCHES),\n ),\n )\n .then((snapshot) => {\n if (!active) return\n setCompanyChoices(\n snapshot.docs.map((entry) => ({\n id: entry.id,\n name: String(entry.get('name') ?? ''),\n })),\n )\n })\n .catch(() => {\n if (active) setCompanyChoices([])\n })\n }, 200)\n return () => {\n active = false\n clearTimeout(timer)\n }\n }, [firestore, open, orgId, visibleTo, companyQuery])\n const companyValue: CompanyChoice | null = values.companyId\n ? { id: values.companyId, name: values.companyName }\n : null\n\n const customMissing = crmCustomDraftMissingRequired(fields.active, storedCustom, custom, mode)\n const problem =\n dealFormProblem(values, mode) ??\n (customMissing.length ? `${customMissing.join(', ')} is required.` : null)\n const amountDerived = Boolean(deal && dealHasLineItems(deal))\n\n /*\n * THE RECORDS BAND (AGL-2611), read only while a CREATE is open: a deal\n * is a record of the band the contacts list is banded by, and on an org\n * at a full hard band this drawer refuses the way `upsertHostContact`\n * refuses a capture — same number, same sentence. Memoized, because the\n * tuple is an effect dependency and a fresh one per render would re-read\n * the three aggregates on every keystroke.\n */\n const recordsScope = useMemo(\n () => (open && mode === 'create' && orgId ? (['orgs', orgId] as const) : null),\n [open, mode, orgId],\n )\n const records = useCrmRecordsQuota(recordsScope, org)\n const bandFull = mode === 'create' && records.ready && !records.quota.allowed\n\n const handleSave = useCallback(async () => {\n if (problem || !orgId || !user?.uid) return\n if (bandFull) {\n enqueueSnackbar(CRM_RECORDS_BAND_FULL_MESSAGE, { variant: 'warning', persist: false })\n return\n }\n const judged = judgeDealFormPicklists(\n values,\n { type: typeList.picklist, leadSource: leadSourceList.picklist },\n { current: deal ?? null, created: !deal },\n )\n if (judged.ok === false) {\n enqueueSnackbar(judged.error, { variant: 'warning', persist: false })\n return\n }\n const saved = judged.values\n setBusy(true)\n try {\n const nowMs = Date.now()\n if (deal) {\n const verdict = await writeGuardedBySeed(\n { subject: 'deal', unreadable, fromCache },\n async () => {\n const { set, clear } = dealPatchFromForm(saved, nowMs, {\n amountDerived,\n stage: currentStage,\n current: deal,\n })\n await updateDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, deal.$id),\n {\n ...set,\n ...Object.fromEntries(clear.map((key) => [key, deleteField()])),\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n // The title is what the Deals table searches (AGL-3321), and\n // the Type and Lead source what it filters by (AGL-3516),\n // from the listener's row with the edit over it.\n ...crmClientListFields(\n 'deals',\n deal as unknown as Record<string, unknown>,\n { ...set, ...Object.fromEntries(clear.map((key) => [key, null])) },\n DEAL_CLIENT_LIST_FIELDS,\n ),\n },\n )\n },\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n followUpSharing('deals', [deal.$id])\n enqueueSnackbar('Deal saved', { variant: 'success', persist: false })\n onSaved?.(deal.$id, 'edit')\n } else {\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n const customDocument = crmCustomDraftDocument(custom)\n const record: Record<string, unknown> = {\n ...dealDocumentFromForm(saved, {\n visibleTo: [...createTokens],\n hostId: createHostId,\n uid: user.uid,\n nowMs,\n stage: currentStage,\n }),\n ...(customDocument ? { custom: customDocument } : {}),\n }\n const created = await addDoc(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n // What the Deals table searches and filters by (AGL-3321), and\n // nothing scheduled against a deal that did not exist.\n { ...record, ...crmNewRecordListFields('deals', record) },\n )\n // Setup → Activity shows CRM work (AGL-2622): the deal is org data,\n // but the act happened in this site's console and belongs in its\n // feed. Creation only — an edit is a save the card reports.\n followUpSharing('deals', [created.id])\n logActivity('Added deal', { type: 'deal', id: created.id, name: values.title })\n enqueueSnackbar('Deal created', { variant: 'success', persist: false })\n onSaved?.(created.id, 'create')\n }\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n problem,\n bandFull,\n orgId,\n followUpSharing,\n user,\n deal,\n values,\n unreadable,\n fromCache,\n firestore,\n createTokens,\n createHostId,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n storedCustom,\n custom,\n typeList.picklist,\n leadSourceList.picklist,\n currentStage,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={busy ? undefined : onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton color=\"inherit\" edge=\"start\" onClick={onClose} sx={{ mr: 2 }}>\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>close drawer</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {deal ? 'Edit deal' : 'New deal'}\n </Typography>\n </>\n }\n appBarRight={\n <Button variant=\"outlined\" color=\"inherit\" onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n }\n >\n <Container gutterY>\n <Stack spacing={2} sx={{ width: { xs: '100%', sm: 420 } }}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new deal names the site that captures\n it. Nothing under a site, and nothing on an edit — a deal keeps\n the site it was made on (AGL-2630).\n */}\n {mode === 'create' ? (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this deal belongs to — it decides which of your sites may see it.\"\n />\n ) : null}\n <TextField\n label=\"Title\"\n value={values.title}\n onChange={(event) => update({ title: event.target.value })}\n autoFocus\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_TITLE_MAX } }}\n />\n {mode === 'create' ? (\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n label=\"Pipeline\"\n value={pipelines.some((entry) => entry.$id === values.pipelineId) ? values.pipelineId : ''}\n onChange={(event) => {\n const next = pipelines.find(\n (entry) => entry.$id === event.target.value,\n )\n update({\n pipelineId: event.target.value,\n stageId: openStages(next)[0]?.id ?? '',\n })\n }}\n sx={{ flex: 1 }}\n >\n {pipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n label=\"Stage\"\n value={stages.some((stage) => stage.id === values.stageId) ? values.stageId : ''}\n onChange={(event) => update({ stageId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {stages.map((stage) => (\n <MenuItem key={stage.id} value={stage.id}>\n {stage.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Amount\"\n value={values.amount}\n onChange={(event) => update({ amount: event.target.value })}\n placeholder=\"0.00\"\n sx={{ flex: 1 }}\n helperText={\n amountDerived\n ? \"The sum of the deal's line items — edit them on the Products card.\"\n : undefined\n }\n slotProps={{\n htmlInput: { inputMode: 'decimal', readOnly: amountDerived },\n }}\n />\n <TextField\n select\n label=\"Currency\"\n value={values.currency}\n onChange={(event) => update({ currency: event.target.value })}\n disabled={amountDerived}\n sx={{ width: 120 }}\n >\n {DEAL_CURRENCIES.map((code) => (\n <MenuItem key={code} value={code}>\n {code.toUpperCase()}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <TextField\n type=\"date\"\n label=\"Expected close\"\n value={values.expectedClose}\n onChange={(event) => update({ expectedClose: event.target.value })}\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <CrmPicklistSelect\n picklistId=\"opportunityType\"\n picklist={typeList.picklist}\n value={values.type}\n stored={deal?.type ?? ''}\n onChange={(type) => update({ type })}\n disabled={busy}\n />\n <CrmPicklistSelect\n picklistId=\"leadSource\"\n picklist={leadSourceList.picklist}\n value={values.leadSource}\n stored={deal?.leadSource ?? ''}\n onChange={(leadSource) => update({ leadSource })}\n disabled={busy}\n />\n </Stack>\n <TextField\n label=\"Next step\"\n value={values.nextStep}\n onChange={(event) => update({ nextStep: event.target.value })}\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_NEXT_STEP_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Probability\"\n value={values.probability}\n onChange={(event) => update({ probability: event.target.value })}\n placeholder={currentStage ? String(currentStage.probability) : ''}\n helperText={\n currentStage\n ? `From stage: ${currentStage.probability}%`\n : 'Blank uses the stage’s'\n }\n sx={{ flex: 1 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Probability' },\n input: { endAdornment: '%' },\n inputLabel: { shrink: true },\n }}\n />\n <TextField\n select\n label=\"Forecast category\"\n value={values.forecastCategory}\n onChange={(event) =>\n update({ forecastCategory: event.target.value as '' | CrmForecastCategory })\n }\n helperText=\"A stage move sets it from the new stage.\"\n sx={{ flex: 1 }}\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">\n {currentStage\n ? `From the stage (${CRM_FORECAST_CATEGORY_LABELS[dealStageForecastCategory(currentStage)]})`\n : 'From the stage'}\n </MenuItem>\n {CRM_FORECAST_CATEGORIES.map((category) => (\n <MenuItem key={category} value={category}>\n {CRM_FORECAST_CATEGORY_LABELS[category]}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <TextField\n select\n label=\"Owner\"\n // The stored owner resolved to a member — by uid, or by an\n // address the roster has — so the picker highlights the person\n // and a save writes their uid.\n value={owner ? owner.uid : values.ownerUid ? '__keep' : ''}\n onChange={(event) =>\n update({\n ownerUid: event.target.value === '__keep' ? values.ownerUid : event.target.value,\n })\n }\n fullWidth\n helperText={\n !roster.loading && !roster.members.length\n ? 'The roster could not be read; the owner stays as stored.'\n : undefined\n }\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {values.ownerUid && !owner ? (\n // The stored owner is not on the roster the route returned —\n // a member who left, or a roster that failed to load. Shown by\n // uid so the field is never blank while the deal has an owner.\n <MenuItem value=\"__keep\">{values.ownerUid}</MenuItem>\n ) : null}\n {roster.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete<ContactChoice, false, false, false>\n options={contactChoices}\n value={contactValue}\n inputValue={contactQuery}\n onInputChange={(_event, next, reason) => {\n if (reason !== 'reset') setContactQuery(next)\n }}\n onChange={(_event, picked) =>\n update({\n contactId: picked?.id ?? '',\n contactName: picked ? picked.name || picked.email : '',\n })\n }\n getOptionLabel={(choice) => choice.name || choice.email}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n // Matching is `contactChoicesFor`'s — the list's grammar over the\n // window — and never MUI's own substring filter over labels.\n filterOptions={(options) => options}\n renderOption={(optionProps, choice) => (\n <li {...optionProps} key={choice.id}>\n <Stack sx={{ lineHeight: 1.25 }}>\n <Typography variant=\"body2\">{choice.name || choice.email}</Typography>\n {choice.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {choice.email}\n </Typography>\n ) : null}\n </Stack>\n </li>\n )}\n noOptionsText=\"No recent contact matches\"\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Contact\"\n placeholder=\"Name or email\"\n helperText=\"Searches your most recent contacts by name or email.\"\n />\n )}\n />\n <Autocomplete<CompanyChoice, false, false, false>\n options={companyChoices}\n value={companyValue}\n inputValue={companyQuery}\n onInputChange={(_event, next, reason) => {\n if (reason !== 'reset') setCompanyQuery(next)\n }}\n onChange={(_event, picked) =>\n update({\n companyId: picked?.id ?? '',\n companyName: picked?.name ?? '',\n })\n }\n getOptionLabel={(choice) => choice.name}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n filterOptions={(options) => options}\n noOptionsText=\"No company matches\"\n renderInput={(params) => (\n <TextField {...params} label=\"Company\" placeholder=\"Company name\" />\n )}\n />\n <TextField\n select\n label=\"Campaign\"\n value={values.campaignId}\n onChange={(event) => update({ campaignId: event.target.value })}\n fullWidth\n helperText={\n campaigns.ready && !campaignOptions.length\n ? 'There are no campaigns yet. Create one from Marketing.'\n : 'The campaign this deal is attributed to.'\n }\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {campaignOptions.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Notes\"\n value={values.notes}\n onChange={(event) => update({ notes: event.target.value })}\n multiline\n minRows={3}\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_NOTES_MAX } }}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n {problem && values.title ? <Alert severity=\"warning\">{problem}</Alert> : null}\n <Button\n variant=\"contained\"\n disabled={\n busy ||\n Boolean(problem) ||\n !orgId ||\n bandFull ||\n // A new deal waits for its capturing site.\n (mode === 'create' && !createHostId)\n }\n onClick={() => void handleSave()}\n >\n {deal ? 'Save deal' : 'Create deal'}\n </Button>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nDealEditDrawer.displayName = 'DealEditDrawer'\n\nexport default DealEditDrawer\n"],"names":["CRM_COLLECTIONS","CRM_FORECAST_CATEGORIES","CRM_FORECAST_CATEGORY_LABELS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","DEAL_NEXT_STEP_MAX","dealHasLineItems","dealStageById","dealStageForecastCategory","findOrgMember","nameSearchKey","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useFirestoreCollection","useUser","writeGuardedBySeed","Alert","Autocomplete","Button","IconButton","MenuItem","Stack","TextField","Typography","addDoc","collection","deleteField","doc","endAt","getDocs","limit","orderBy","query","startAt","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmCampaigns","useCrmPicklist","useCrmRecordsQuota","crmScopeListable","crmVisibleToClause","useCrmScope","useOrgMemberDirectory","contactPrimaryGroup","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmPicklistSelect","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS","crmClientListFields","DEAL_CURRENCIES","openStages","contactChoicesFor","DEAL_NOTES_MAX","DEAL_TITLE_MAX","dealDocumentFromForm","dealFormFromDoc","dealFormProblem","dealPatchFromForm","emptyDealForm","judgeDealFormPicklists","useCrmSharingFollowUp","CONTACT_WINDOW","COMPANY_MATCHES","DEAL_CLIENT_LIST_FIELDS","DealEditDrawer","props","contactChoices","open","onClose","hostId","org","deal","defaults","pipelines","defaultPipeline","unreadable","fromCache","onSaved","mode","firestore","enqueueSnackbar","data","user","orgId","consentGroup","visibleTo","createHostId","createTokens","followUpSharing","logActivity","roster","values","setValues","owner","members","ownerUid","busy","setBusy","fields","typeList","leadSourceList","campaigns","enabled","storedCustom","custom","setCustom","update","patch","current","pipeline","find","entry","$id","pipelineId","stages","currentStage","stageId","campaignOptions","options","map","option","value","label","campaignId","some","contactRows","idField","contactQuery","setContactQuery","groupId","row","contactValue","contactId","id","name","contactName","email","choice","companyQuery","setCompanyQuery","companyChoices","setCompanyChoices","key","active","timer","setTimeout","companies","then","snapshot","docs","String","get","catch","clearTimeout","companyValue","companyId","companyName","customMissing","problem","length","join","amountDerived","Boolean","recordsScope","records","bandFull","ready","quota","allowed","handleSave","uid","variant","persist","judged","type","picklist","leadSource","created","ok","error","saved","nowMs","Date","now","verdict","subject","set","clear","stage","deals","Object","fromEntries","message","customDocument","record","title","console","allowDuplicate","anchor","undefined","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","appBarRight","disabled","gutterY","spacing","width","xs","sm","helperText","onChange","event","target","autoFocus","fullWidth","slotProps","htmlInput","maxLength","direction","select","next","flex","alignItems","amount","placeholder","inputMode","readOnly","currency","code","toUpperCase","expectedClose","inputLabel","shrink","picklistId","stored","nextStep","probability","input","endAdornment","forecastCategory","displayEmpty","category","loading","member","inputValue","onInputChange","_event","reason","picked","getOptionLabel","isOptionEqualToValue","filterOptions","renderOption","optionProps","li","lineHeight","noOptionsText","renderInput","params","notes","multiline","minRows","definition","severity","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n CRM_COLLECTIONS,\n CRM_FORECAST_CATEGORIES,\n CRM_FORECAST_CATEGORY_LABELS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmForecastCategory,\n crmNewRecordListFields,\n DEAL_NEXT_STEP_MAX,\n dealHasLineItems,\n dealStageById,\n dealStageForecastCategory,\n findOrgMember,\n nameSearchKey,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n addDoc,\n collection,\n deleteField,\n doc,\n endAt,\n getDocs,\n limit,\n orderBy,\n query,\n startAt,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport {\n type CrmOrgDoc,\n crmScopeListable,\n crmVisibleToClause,\n useCrmScope,\n} from '../hooks/use-crm-scope'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { contactPrimaryGroup } from '../model/contact-record'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmPicklistSelect } from './picklist-select'\nimport { CrmSitePicker } from './crm-site-picker'\nimport {\n CRM_CLIENT_SEARCH_FIELDS,\n CRM_CLIENT_SORT_FIELDS,\n CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n crmClientListFields,\n} from '../model/crm-list-query'\nimport {\n DEAL_CURRENCIES,\n type DealDoc,\n openStages,\n type PipelineDoc,\n} from '../model/deal-board-model'\nimport {\n type ContactChoice,\n contactChoicesFor,\n DEAL_NOTES_MAX,\n DEAL_TITLE_MAX,\n dealDocumentFromForm,\n dealFormFromDoc,\n dealFormProblem,\n type DealFormValues,\n dealPatchFromForm,\n emptyDealForm,\n judgeDealFormPicklists,\n} from '../model/deal-form-model'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/**\n * How many recent contacts the picker matches against. The window is the\n * ask — it is read when the drawer opens, not when the section does — and\n * a person outside it is still linkable by pasting their address, which\n * the match tests as a prefix of the email.\n */\nconst CONTACT_WINDOW = 300\n\n/** Companies offered per keystroke; a longer list is a scroll, not a pick. */\nconst COMPANY_MATCHES = 8\n\n/** The list fields an edit restamps: the title's search words, the two picklist keys. */\nconst DEAL_CLIENT_LIST_FIELDS = [\n ...CRM_CLIENT_SEARCH_FIELDS,\n // The Deal, Amount and Expected close headers' sort keys (AGL-3680).\n ...CRM_CLIENT_SORT_FIELDS,\n 'typeKey',\n 'leadSourceKey',\n // The arrays a deal's contact roles are found by (AGL-3521).\n ...CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n] as const\n\n/** A company as the picker offers it. */\ninterface CompanyChoice {\n id: string\n name: string\n}\n\nexport interface DealEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW deal asks which site captures it and an\n * edit keeps the deal's own.\n */\n hostId: string | null\n org: CrmOrgDoc\n /** The deal being edited; absent for a new one. */\n deal?: DealDoc | null\n /** Links and values a new deal opens with — a contact's page passes itself. */\n defaults?: Partial<DealFormValues>\n pipelines: PipelineDoc[]\n /** Where a new deal lands when nobody picks a pipeline. */\n defaultPipeline: PipelineDoc | null\n /** The listener's verdict on the row being edited, for the stale-seed guard. */\n unreadable?: boolean\n fromCache?: boolean\n /** After a save, with the id of the deal written. */\n onSaved?: (dealId: string, mode: 'create' | 'edit') => void\n}\n\n/**\n * The one form a deal is created and edited through (AGL-2598).\n *\n * ## What it writes, and what it does not\n *\n * Client-direct against the rules, like a contact's notes: the title, the\n * amount and its currency, the expected close, the owner, the linked\n * contact and company, and the notes. The stage and the status are shown on\n * a new deal — it has to start somewhere — and never on an existing one,\n * because a stage change is the moment an automation listens for and only\n * the server route can emit that event. A drawer that wrote `stageId`\n * directly would move a deal without anybody hearing it.\n *\n * Nor the amount of a deal that has line items (AGL-2620): it is their\n * sum, the products card on the deal's page owns it, and the field here\n * is read-only with a caption saying so. The pipeline picker on a new\n * deal lists every ACTIVE pipeline (`pipelines`) and opens on the one the\n * caller was looking at.\n *\n * ## The pickers\n *\n * The owner is picked from the org's roster, the company by a server-side\n * prefix on `nameLower` (indexed with the scope), and the contact from a\n * bounded window of the newest contacts matched in memory — see\n * `contactChoicesFor` for why the contact search cannot be a query. Both\n * pickers copy the chosen NAME onto the deal beside the id, so a board of\n * cards can caption itself without a read per card.\n *\n * ## Salesforce's Opportunity fields (AGL-3516)\n *\n * Type and Lead source are the org's picklists, judged on save as every\n * server door judges them (`judgeDealFormPicklists`) — a new deal with no\n * Type takes the list's default. The probability is this deal's own\n * override, blank for its stage's; the forecast category is one of the\n * fixed set, \"From the stage\" storing the stage's. A stage move later\n * stamps both afresh. The campaign is one of the campaigns the Campaigns\n * picker of a lead offers at this level.\n */\nexport function DealEditDrawer(props: DealEditDrawerProps) {\n const {\n open,\n onClose,\n hostId,\n org,\n deal,\n defaults,\n pipelines,\n defaultPipeline,\n unreadable = false,\n fromCache = false,\n onSaved,\n } = props\n const mode: 'create' | 'edit' = deal ? 'edit' : 'create'\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const { orgId, consentGroup, visibleTo, createHostId, createTokens } =\n useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the deal is STAMPED, which at the hub is a\n // site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n const roster = useOrgMemberDirectory(open ? orgId : null)\n\n const [values, setValues] = useState<DealFormValues>(() =>\n emptyDealForm(defaultPipeline, defaults),\n )\n const owner = findOrgMember(roster.members, values.ownerUid)\n const [busy, setBusy] = useState(false)\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's deal definitions, and the keys\n * the reader has touched over what the deal stores — saved as the\n * difference (`crm-custom-draft`) in the same write as the fixed fields.\n */\n const fields = useContactFieldDefinitions(open ? orgId : null, 'deal')\n // The org's lists for the two picklist fields, and its campaigns (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', open ? orgId : null)\n const leadSourceList = useCrmPicklist('leadSource', open ? orgId : null)\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: open })\n const storedCustom = useMemo(() => deal?.custom ?? {}, [deal?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n\n // Re-seeded on every open: an existing deal's stored values, or a blank\n // form aimed at the default pipeline with whatever the opener preselected.\n useEffect(() => {\n if (!open) return\n setValues(deal ? dealFormFromDoc(deal) : emptyDealForm(defaultPipeline, defaults))\n setCustom({})\n }, [open, deal, defaultPipeline, defaults])\n\n const update = useCallback(\n (patch: Partial<DealFormValues>) =>\n setValues((current) => ({ ...current, ...patch })),\n [],\n )\n\n const pipeline = useMemo(\n () =>\n pipelines.find((entry) => entry.$id === values.pipelineId) ??\n defaultPipeline,\n [pipelines, values.pipelineId, defaultPipeline],\n )\n const stages = useMemo(() => openStages(pipeline), [pipeline])\n /*\n * The stage the deal is in — the one picked on a new deal, the stored one\n * on an edit, looked up in the deal's OWN pipeline and never the default's\n * — whose probability and forecast category the blank fields stand for.\n */\n const currentStage = useMemo(\n () =>\n dealStageById(\n pipelines.find((entry) => entry.$id === values.pipelineId),\n values.stageId,\n ),\n [pipelines, values.pipelineId, values.stageId],\n )\n const campaignOptions = useMemo(() => {\n const options = campaigns.options.map((option) => ({ value: option.value, label: option.label }))\n // A campaign the deal names but the picker no longer offers stays itself.\n return values.campaignId && !options.some((option) => option.value === values.campaignId)\n ? [...options, { value: values.campaignId, label: values.campaignId }]\n : options\n }, [campaigns.options, values.campaignId])\n\n /*\n * THE CONTACT WINDOW: the newest contacts this viewer may see, read only\n * while the drawer is open. Scoped and ordered like the contacts list —\n * the same `(visibleTo, updatedAt DESC)` index — and bounded, because the\n * picker's job is to find one person, not to list the audience.\n */\n const { data: contactRows } = useFirestoreCollection<Record<string, unknown>>(\n () =>\n open && orgId && crmScopeListable(visibleTo)\n ? query(\n collection(firestore, 'orgs', orgId, 'contacts'),\n ...crmVisibleToClause(visibleTo),\n orderBy('updatedAt', 'desc'),\n limit(CONTACT_WINDOW),\n )\n : null,\n [firestore, open, orgId, visibleTo],\n { idField: '$id' },\n )\n const [contactQuery, setContactQuery] = useState('')\n // Named through the viewing group under a site; at the organization level\n // through each person's own primary holder.\n const contactChoices = useMemo(\n () =>\n contactChoicesFor(\n contactQuery,\n contactRows ?? [],\n consentGroup?.groupId ??\n ((row) => contactPrimaryGroup(row, org as Record<string, unknown>).groupId),\n ),\n [contactQuery, contactRows, consentGroup, org],\n )\n const contactValue: ContactChoice | null = values.contactId\n ? {\n id: values.contactId,\n name: values.contactName,\n email:\n contactChoices.find((choice) => choice.id === values.contactId)?.email ?? '',\n }\n : null\n\n /*\n * THE COMPANY SEARCH: a prefix on `nameLower` with the scope, which the\n * companies collection indexes. Debounced so a name typed at speed is one\n * query rather than one per letter.\n */\n const [companyQuery, setCompanyQuery] = useState('')\n const [companyChoices, setCompanyChoices] = useState<CompanyChoice[]>([])\n useEffect(() => {\n if (!open || !orgId || !crmScopeListable(visibleTo)) return\n const key = nameSearchKey(companyQuery)\n let active = true\n const timer = setTimeout(() => {\n void getDocs(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.companies),\n ...crmVisibleToClause(visibleTo),\n orderBy('nameLower'),\n ...(key ? [startAt(key), endAt(`${key}\\uf8ff`)] : []),\n limit(COMPANY_MATCHES),\n ),\n )\n .then((snapshot) => {\n if (!active) return\n setCompanyChoices(\n snapshot.docs.map((entry) => ({\n id: entry.id,\n name: String(entry.get('name') ?? ''),\n })),\n )\n })\n .catch(() => {\n if (active) setCompanyChoices([])\n })\n }, 200)\n return () => {\n active = false\n clearTimeout(timer)\n }\n }, [firestore, open, orgId, visibleTo, companyQuery])\n const companyValue: CompanyChoice | null = values.companyId\n ? { id: values.companyId, name: values.companyName }\n : null\n\n const customMissing = crmCustomDraftMissingRequired(fields.active, storedCustom, custom, mode)\n const problem =\n dealFormProblem(values, mode) ??\n (customMissing.length ? `${customMissing.join(', ')} is required.` : null)\n const amountDerived = Boolean(deal && dealHasLineItems(deal))\n\n /*\n * THE RECORDS BAND (AGL-2611), read only while a CREATE is open: a deal\n * is a record of the band the contacts list is banded by, and on an org\n * at a full hard band this drawer refuses the way `upsertHostContact`\n * refuses a capture — same number, same sentence. Memoized, because the\n * tuple is an effect dependency and a fresh one per render would re-read\n * the three aggregates on every keystroke.\n */\n const recordsScope = useMemo(\n () => (open && mode === 'create' && orgId ? (['orgs', orgId] as const) : null),\n [open, mode, orgId],\n )\n const records = useCrmRecordsQuota(recordsScope, org)\n const bandFull = mode === 'create' && records.ready && !records.quota.allowed\n\n const handleSave = useCallback(async () => {\n if (problem || !orgId || !user?.uid) return\n if (bandFull) {\n enqueueSnackbar(CRM_RECORDS_BAND_FULL_MESSAGE, { variant: 'warning', persist: false })\n return\n }\n const judged = judgeDealFormPicklists(\n values,\n { type: typeList.picklist, leadSource: leadSourceList.picklist },\n { current: deal ?? null, created: !deal },\n )\n if (judged.ok === false) {\n enqueueSnackbar(judged.error, { variant: 'warning', persist: false })\n return\n }\n const saved = judged.values\n setBusy(true)\n try {\n const nowMs = Date.now()\n if (deal) {\n const verdict = await writeGuardedBySeed(\n { subject: 'deal', unreadable, fromCache },\n async () => {\n const { set, clear } = dealPatchFromForm(saved, nowMs, {\n amountDerived,\n stage: currentStage,\n current: deal,\n })\n await updateDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, deal.$id),\n {\n ...set,\n ...Object.fromEntries(clear.map((key) => [key, deleteField()])),\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n // The title is what the Deals table searches (AGL-3321), and\n // the Type and Lead source what it filters by (AGL-3516),\n // from the listener's row with the edit over it.\n ...crmClientListFields(\n 'deals',\n deal as unknown as Record<string, unknown>,\n { ...set, ...Object.fromEntries(clear.map((key) => [key, null])) },\n DEAL_CLIENT_LIST_FIELDS,\n ),\n },\n )\n },\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n followUpSharing('deals', [deal.$id])\n enqueueSnackbar('Deal saved', { variant: 'success', persist: false })\n onSaved?.(deal.$id, 'edit')\n } else {\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n const customDocument = crmCustomDraftDocument(custom)\n const record: Record<string, unknown> = {\n ...dealDocumentFromForm(saved, {\n visibleTo: [...createTokens],\n hostId: createHostId,\n uid: user.uid,\n nowMs,\n stage: currentStage,\n }),\n ...(customDocument ? { custom: customDocument } : {}),\n }\n const created = await addDoc(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n // What the Deals table searches and filters by (AGL-3321), and\n // nothing scheduled against a deal that did not exist.\n { ...record, ...crmNewRecordListFields('deals', record) },\n )\n // Setup → Activity shows CRM work (AGL-2622): the deal is org data,\n // but the act happened in this site's console and belongs in its\n // feed. Creation only — an edit is a save the card reports.\n followUpSharing('deals', [created.id])\n logActivity('Added deal', { type: 'deal', id: created.id, name: values.title })\n enqueueSnackbar('Deal created', { variant: 'success', persist: false })\n onSaved?.(created.id, 'create')\n }\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n problem,\n bandFull,\n orgId,\n followUpSharing,\n user,\n deal,\n values,\n unreadable,\n fromCache,\n firestore,\n createTokens,\n createHostId,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n storedCustom,\n custom,\n typeList.picklist,\n leadSourceList.picklist,\n currentStage,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={busy ? undefined : onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton color=\"inherit\" edge=\"start\" onClick={onClose} sx={{ mr: 2 }}>\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>close drawer</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {deal ? 'Edit deal' : 'New deal'}\n </Typography>\n </>\n }\n appBarRight={\n <Button variant=\"outlined\" color=\"inherit\" onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n }\n >\n <Container gutterY>\n <Stack spacing={2} sx={{ width: { xs: '100%', sm: 420 } }}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new deal names the site that captures\n it. Nothing under a site, and nothing on an edit — a deal keeps\n the site it was made on (AGL-2630).\n */}\n {mode === 'create' ? (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this deal belongs to — it decides which of your sites may see it.\"\n />\n ) : null}\n <TextField\n label=\"Title\"\n value={values.title}\n onChange={(event) => update({ title: event.target.value })}\n autoFocus\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_TITLE_MAX } }}\n />\n {mode === 'create' ? (\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n label=\"Pipeline\"\n value={pipelines.some((entry) => entry.$id === values.pipelineId) ? values.pipelineId : ''}\n onChange={(event) => {\n const next = pipelines.find(\n (entry) => entry.$id === event.target.value,\n )\n update({\n pipelineId: event.target.value,\n stageId: openStages(next)[0]?.id ?? '',\n })\n }}\n sx={{ flex: 1 }}\n >\n {pipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n label=\"Stage\"\n value={stages.some((stage) => stage.id === values.stageId) ? values.stageId : ''}\n onChange={(event) => update({ stageId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {stages.map((stage) => (\n <MenuItem key={stage.id} value={stage.id}>\n {stage.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Amount\"\n value={values.amount}\n onChange={(event) => update({ amount: event.target.value })}\n placeholder=\"0.00\"\n sx={{ flex: 1 }}\n helperText={\n amountDerived\n ? \"The sum of the deal's line items — edit them on the Products card.\"\n : undefined\n }\n slotProps={{\n htmlInput: { inputMode: 'decimal', readOnly: amountDerived },\n }}\n />\n <TextField\n select\n label=\"Currency\"\n value={values.currency}\n onChange={(event) => update({ currency: event.target.value })}\n disabled={amountDerived}\n sx={{ width: 120 }}\n >\n {DEAL_CURRENCIES.map((code) => (\n <MenuItem key={code} value={code}>\n {code.toUpperCase()}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <TextField\n type=\"date\"\n label=\"Expected close\"\n value={values.expectedClose}\n onChange={(event) => update({ expectedClose: event.target.value })}\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <CrmPicklistSelect\n picklistId=\"opportunityType\"\n picklist={typeList.picklist}\n value={values.type}\n stored={deal?.type ?? ''}\n onChange={(type) => update({ type })}\n disabled={busy}\n />\n <CrmPicklistSelect\n picklistId=\"leadSource\"\n picklist={leadSourceList.picklist}\n value={values.leadSource}\n stored={deal?.leadSource ?? ''}\n onChange={(leadSource) => update({ leadSource })}\n disabled={busy}\n />\n </Stack>\n <TextField\n label=\"Next step\"\n value={values.nextStep}\n onChange={(event) => update({ nextStep: event.target.value })}\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_NEXT_STEP_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Probability\"\n value={values.probability}\n onChange={(event) => update({ probability: event.target.value })}\n placeholder={currentStage ? String(currentStage.probability) : ''}\n helperText={\n currentStage\n ? `From stage: ${currentStage.probability}%`\n : 'Blank uses the stage’s'\n }\n sx={{ flex: 1 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Probability' },\n input: { endAdornment: '%' },\n inputLabel: { shrink: true },\n }}\n />\n <TextField\n select\n label=\"Forecast category\"\n value={values.forecastCategory}\n onChange={(event) =>\n update({ forecastCategory: event.target.value as '' | CrmForecastCategory })\n }\n helperText=\"A stage move sets it from the new stage.\"\n sx={{ flex: 1 }}\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">\n {currentStage\n ? `From the stage (${CRM_FORECAST_CATEGORY_LABELS[dealStageForecastCategory(currentStage)]})`\n : 'From the stage'}\n </MenuItem>\n {CRM_FORECAST_CATEGORIES.map((category) => (\n <MenuItem key={category} value={category}>\n {CRM_FORECAST_CATEGORY_LABELS[category]}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <TextField\n select\n label=\"Owner\"\n // The stored owner resolved to a member — by uid, or by an\n // address the roster has — so the picker highlights the person\n // and a save writes their uid.\n value={owner ? owner.uid : values.ownerUid ? '__keep' : ''}\n onChange={(event) =>\n update({\n ownerUid: event.target.value === '__keep' ? values.ownerUid : event.target.value,\n })\n }\n fullWidth\n helperText={\n !roster.loading && !roster.members.length\n ? 'The roster could not be read; the owner stays as stored.'\n : undefined\n }\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {values.ownerUid && !owner ? (\n // The stored owner is not on the roster the route returned —\n // a member who left, or a roster that failed to load. Shown by\n // uid so the field is never blank while the deal has an owner.\n <MenuItem value=\"__keep\">{values.ownerUid}</MenuItem>\n ) : null}\n {roster.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete<ContactChoice, false, false, false>\n options={contactChoices}\n value={contactValue}\n inputValue={contactQuery}\n onInputChange={(_event, next, reason) => {\n if (reason !== 'reset') setContactQuery(next)\n }}\n onChange={(_event, picked) =>\n update({\n contactId: picked?.id ?? '',\n contactName: picked ? picked.name || picked.email : '',\n })\n }\n getOptionLabel={(choice) => choice.name || choice.email}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n // Matching is `contactChoicesFor`'s — the list's grammar over the\n // window — and never MUI's own substring filter over labels.\n filterOptions={(options) => options}\n renderOption={(optionProps, choice) => (\n <li {...optionProps} key={choice.id}>\n <Stack sx={{ lineHeight: 1.25 }}>\n <Typography variant=\"body2\">{choice.name || choice.email}</Typography>\n {choice.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {choice.email}\n </Typography>\n ) : null}\n </Stack>\n </li>\n )}\n noOptionsText=\"No recent contact matches\"\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Contact\"\n placeholder=\"Name or email\"\n helperText=\"Searches your most recent contacts by name or email.\"\n />\n )}\n />\n <Autocomplete<CompanyChoice, false, false, false>\n options={companyChoices}\n value={companyValue}\n inputValue={companyQuery}\n onInputChange={(_event, next, reason) => {\n if (reason !== 'reset') setCompanyQuery(next)\n }}\n onChange={(_event, picked) =>\n update({\n companyId: picked?.id ?? '',\n companyName: picked?.name ?? '',\n })\n }\n getOptionLabel={(choice) => choice.name}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n filterOptions={(options) => options}\n noOptionsText=\"No company matches\"\n renderInput={(params) => (\n <TextField {...params} label=\"Company\" placeholder=\"Company name\" />\n )}\n />\n <TextField\n select\n label=\"Campaign\"\n value={values.campaignId}\n onChange={(event) => update({ campaignId: event.target.value })}\n fullWidth\n helperText={\n campaigns.ready && !campaignOptions.length\n ? 'There are no campaigns yet. Create one from Marketing.'\n : 'The campaign this deal is attributed to.'\n }\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {campaignOptions.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Notes\"\n value={values.notes}\n onChange={(event) => update({ notes: event.target.value })}\n multiline\n minRows={3}\n fullWidth\n slotProps={{ htmlInput: { maxLength: DEAL_NOTES_MAX } }}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n {problem && values.title ? <Alert severity=\"warning\">{problem}</Alert> : null}\n <Button\n variant=\"contained\"\n disabled={\n busy ||\n Boolean(problem) ||\n !orgId ||\n bandFull ||\n // A new deal waits for its capturing site.\n (mode === 'create' && !createHostId)\n }\n onClick={() => void handleSave()}\n >\n {deal ? 'Save deal' : 'Create deal'}\n </Button>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nDealEditDrawer.displayName = 'DealEditDrawer'\n\nexport default DealEditDrawer\n"],"names":["CRM_COLLECTIONS","CRM_FORECAST_CATEGORIES","CRM_FORECAST_CATEGORY_LABELS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","DEAL_NEXT_STEP_MAX","dealHasLineItems","dealStageById","dealStageForecastCategory","findOrgMember","nameSearchKey","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useFirestoreCollection","useUser","writeGuardedBySeed","Alert","Autocomplete","Button","IconButton","MenuItem","Stack","TextField","Typography","addDoc","collection","deleteField","doc","endAt","getDocs","limit","orderBy","query","startAt","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmCampaigns","useCrmPicklist","useCrmRecordsQuota","crmScopeListable","crmVisibleToClause","useCrmScope","useOrgMemberDirectory","contactPrimaryGroup","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmPicklistSelect","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","CRM_CLIENT_SORT_FIELDS","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS","crmClientListFields","DEAL_CURRENCIES","openStages","contactChoicesFor","DEAL_NOTES_MAX","DEAL_TITLE_MAX","dealDocumentFromForm","dealFormFromDoc","dealFormProblem","dealPatchFromForm","emptyDealForm","judgeDealFormPicklists","useCrmSharingFollowUp","CONTACT_WINDOW","COMPANY_MATCHES","DEAL_CLIENT_LIST_FIELDS","DealEditDrawer","props","contactChoices","open","onClose","hostId","org","deal","defaults","pipelines","defaultPipeline","unreadable","fromCache","onSaved","mode","firestore","enqueueSnackbar","data","user","orgId","consentGroup","visibleTo","createHostId","createTokens","followUpSharing","logActivity","roster","values","setValues","owner","members","ownerUid","busy","setBusy","fields","typeList","leadSourceList","campaigns","enabled","storedCustom","custom","setCustom","update","patch","current","pipeline","find","entry","$id","pipelineId","stages","currentStage","stageId","campaignOptions","options","map","option","value","label","campaignId","some","contactRows","idField","contactQuery","setContactQuery","groupId","row","contactValue","contactId","id","name","contactName","email","choice","companyQuery","setCompanyQuery","companyChoices","setCompanyChoices","key","active","timer","setTimeout","companies","then","snapshot","docs","String","get","catch","clearTimeout","companyValue","companyId","companyName","customMissing","problem","length","join","amountDerived","Boolean","recordsScope","records","bandFull","ready","quota","allowed","handleSave","uid","variant","persist","judged","type","picklist","leadSource","created","ok","error","saved","nowMs","Date","now","verdict","subject","set","clear","stage","deals","Object","fromEntries","message","customDocument","record","title","console","allowDuplicate","anchor","undefined","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","appBarRight","disabled","gutterY","spacing","width","xs","sm","helperText","onChange","event","target","autoFocus","fullWidth","slotProps","htmlInput","maxLength","direction","select","next","flex","alignItems","amount","placeholder","inputMode","readOnly","currency","code","toUpperCase","expectedClose","inputLabel","shrink","picklistId","stored","nextStep","probability","input","endAdornment","forecastCategory","displayEmpty","category","loading","member","inputValue","onInputChange","_event","reason","picked","getOptionLabel","isOptionEqualToValue","filterOptions","renderOption","optionProps","li","lineHeight","noOptionsText","renderInput","params","notes","multiline","minRows","definition","severity","displayName"],"mappings":"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"}