@aglyn/plugins-crm 1.0.0 → 1.2.0

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 (166) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/activity-link-fields.d.ts +34 -0
  3. package/src/lib/components/activity-link-fields.js +141 -0
  4. package/src/lib/components/activity-link-fields.js.map +1 -0
  5. package/src/lib/components/activity-list.d.ts +8 -0
  6. package/src/lib/components/activity-list.js +4 -2
  7. package/src/lib/components/activity-list.js.map +1 -1
  8. package/src/lib/components/activity-queries.d.ts +7 -2
  9. package/src/lib/components/activity-queries.js +7 -3
  10. package/src/lib/components/activity-queries.js.map +1 -1
  11. package/src/lib/components/company-contact-roles-card.d.ts +42 -0
  12. package/src/lib/components/company-contact-roles-card.js +137 -0
  13. package/src/lib/components/company-contact-roles-card.js.map +1 -0
  14. package/src/lib/components/company-detail-page.js +12 -1
  15. package/src/lib/components/company-detail-page.js.map +1 -1
  16. package/src/lib/components/company-edit-drawer.js +9 -6
  17. package/src/lib/components/company-edit-drawer.js.map +1 -1
  18. package/src/lib/components/company-picker.js +4 -2
  19. package/src/lib/components/company-picker.js.map +1 -1
  20. package/src/lib/components/contact-associations-card.js +15 -1
  21. package/src/lib/components/contact-associations-card.js.map +1 -1
  22. package/src/lib/components/contact-detail-page.js +13 -14
  23. package/src/lib/components/contact-detail-page.js.map +1 -1
  24. package/src/lib/components/contact-known-by-card.d.ts +2 -1
  25. package/src/lib/components/contact-known-by-card.js +16 -3
  26. package/src/lib/components/contact-known-by-card.js.map +1 -1
  27. package/src/lib/components/contact-list-columns.js +5 -4
  28. package/src/lib/components/contact-list-columns.js.map +1 -1
  29. package/src/lib/components/contact-properties-card.js +41 -20
  30. package/src/lib/components/contact-properties-card.js.map +1 -1
  31. package/src/lib/components/contact-roles-editor.d.ts +54 -0
  32. package/src/lib/components/contact-roles-editor.js +270 -0
  33. package/src/lib/components/contact-roles-editor.js.map +1 -0
  34. package/src/lib/components/contacts-section.js +6 -4
  35. package/src/lib/components/contacts-section.js.map +1 -1
  36. package/src/lib/components/crm-console-sections.js +1 -1
  37. package/src/lib/components/crm-console-sections.js.map +1 -1
  38. package/src/lib/components/crm-record-picker.d.ts +19 -0
  39. package/src/lib/components/crm-record-picker.js +40 -11
  40. package/src/lib/components/crm-record-picker.js.map +1 -1
  41. package/src/lib/components/crm-related-to-picker.d.ts +33 -0
  42. package/src/lib/components/crm-related-to-picker.js +135 -0
  43. package/src/lib/components/crm-related-to-picker.js.map +1 -0
  44. package/src/lib/components/crm-site-picker.d.ts +13 -1
  45. package/src/lib/components/crm-site-picker.js +15 -6
  46. package/src/lib/components/crm-site-picker.js.map +1 -1
  47. package/src/lib/components/crm-transfer-buttons.d.ts +5 -0
  48. package/src/lib/components/crm-transfer-buttons.js +9 -6
  49. package/src/lib/components/crm-transfer-buttons.js.map +1 -1
  50. package/src/lib/components/deal-contact-roles-card.js +21 -239
  51. package/src/lib/components/deal-contact-roles-card.js.map +1 -1
  52. package/src/lib/components/deal-detail-page.js +2 -1
  53. package/src/lib/components/deal-detail-page.js.map +1 -1
  54. package/src/lib/components/deal-edit-drawer.js +11 -7
  55. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  56. package/src/lib/components/deals-section.js +5 -4
  57. package/src/lib/components/deals-section.js.map +1 -1
  58. package/src/lib/components/log-activity-dialog.d.ts +27 -9
  59. package/src/lib/components/log-activity-dialog.js +96 -32
  60. package/src/lib/components/log-activity-dialog.js.map +1 -1
  61. package/src/lib/components/new-contact-drawer.d.ts +7 -3
  62. package/src/lib/components/new-contact-drawer.js +27 -15
  63. package/src/lib/components/new-contact-drawer.js.map +1 -1
  64. package/src/lib/components/pipeline-stages-dialog.js +5 -1
  65. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  66. package/src/lib/components/record-activity-card.d.ts +11 -2
  67. package/src/lib/components/record-activity-card.js +90 -5
  68. package/src/lib/components/record-activity-card.js.map +1 -1
  69. package/src/lib/components/settings-section.d.ts +4 -2
  70. package/src/lib/components/settings-section.js +7 -5
  71. package/src/lib/components/settings-section.js.map +1 -1
  72. package/src/lib/hooks/crm-activity-client-reads.d.ts +8 -0
  73. package/src/lib/hooks/crm-activity-client-reads.js +50 -0
  74. package/src/lib/hooks/crm-activity-client-reads.js.map +1 -0
  75. package/src/lib/hooks/use-campaign-filing-log.d.ts +2 -1
  76. package/src/lib/hooks/use-campaign-filing-log.js +10 -3
  77. package/src/lib/hooks/use-campaign-filing-log.js.map +1 -1
  78. package/src/lib/hooks/use-crm-org-mount.d.ts +8 -6
  79. package/src/lib/hooks/use-crm-org-mount.js +2 -1
  80. package/src/lib/hooks/use-crm-org-mount.js.map +1 -1
  81. package/src/lib/hooks/use-crm-record-names.d.ts +3 -2
  82. package/src/lib/hooks/use-crm-record-names.js +4 -4
  83. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  84. package/src/lib/hooks/use-crm-scope.d.ts +17 -5
  85. package/src/lib/hooks/use-crm-scope.js +12 -5
  86. package/src/lib/hooks/use-crm-scope.js.map +1 -1
  87. package/src/lib/model/contact-holder.d.ts +12 -0
  88. package/src/lib/model/contact-holder.js +20 -1
  89. package/src/lib/model/contact-holder.js.map +1 -1
  90. package/src/lib/model/contact-record.d.ts +5 -0
  91. package/src/lib/model/contact-record.js +7 -1
  92. package/src/lib/model/contact-record.js.map +1 -1
  93. package/src/lib/model/contact-update.d.ts +6 -0
  94. package/src/lib/model/contact-update.js +1 -0
  95. package/src/lib/model/contact-update.js.map +1 -1
  96. package/src/lib/model/crm-activity-link.d.ts +117 -0
  97. package/src/lib/model/crm-activity-link.js +113 -0
  98. package/src/lib/model/crm-activity-link.js.map +1 -0
  99. package/src/lib/model/crm-api.d.ts +4 -1
  100. package/src/lib/model/crm-api.js +9 -5
  101. package/src/lib/model/crm-api.js.map +1 -1
  102. package/src/lib/model/deal-board-model.js +5 -3
  103. package/src/lib/model/deal-board-model.js.map +1 -1
  104. package/src/lib/model/deal-contact-roles.d.ts +25 -1
  105. package/src/lib/model/deal-contact-roles.js +30 -4
  106. package/src/lib/model/deal-contact-roles.js.map +1 -1
  107. package/src/lib/model/native-vocabulary.d.ts +9 -0
  108. package/src/lib/model/native-vocabulary.js +13 -0
  109. package/src/lib/model/native-vocabulary.js.map +1 -1
  110. package/src/lib/model/settle-stage-names.d.ts +25 -0
  111. package/src/lib/model/settle-stage-names.js +29 -0
  112. package/src/lib/model/settle-stage-names.js.map +1 -0
  113. package/src/lib/native-nav.js +1 -1
  114. package/src/lib/native-nav.js.map +1 -1
  115. package/src/lib/server/add-contact-by-hand.d.ts +84 -0
  116. package/src/lib/server/add-contact-by-hand.js +212 -0
  117. package/src/lib/server/add-contact-by-hand.js.map +1 -0
  118. package/src/lib/server/api-v1/contacts.js +61 -23
  119. package/src/lib/server/api-v1/contacts.js.map +1 -1
  120. package/src/lib/server/api-v1/crm-activities.js +13 -3
  121. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  122. package/src/lib/server/api-v1/crm-pipelines.d.ts +1 -1
  123. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  124. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  125. package/src/lib/server/api-v1/crm-shared.d.ts +16 -9
  126. package/src/lib/server/api-v1/crm-shared.js +23 -11
  127. package/src/lib/server/api-v1/crm-shared.js.map +1 -1
  128. package/src/lib/server/api-v1/openapi.js +3 -3
  129. package/src/lib/server/api-v1/openapi.js.map +1 -1
  130. package/src/lib/server/automation-steps.js +11 -9
  131. package/src/lib/server/automation-steps.js.map +1 -1
  132. package/src/lib/server/campaign-filing-activity.js +7 -2
  133. package/src/lib/server/campaign-filing-activity.js.map +1 -1
  134. package/src/lib/server/contact-update.d.ts +4 -0
  135. package/src/lib/server/contact-update.js +71 -10
  136. package/src/lib/server/contact-update.js.map +1 -1
  137. package/src/lib/server/convert-host-lead.js +13 -2
  138. package/src/lib/server/convert-host-lead.js.map +1 -1
  139. package/src/lib/server/convert-open-lead.js +8 -1
  140. package/src/lib/server/convert-open-lead.js.map +1 -1
  141. package/src/lib/server/crm-activity-company.d.ts +25 -0
  142. package/src/lib/server/crm-activity-company.js +34 -0
  143. package/src/lib/server/crm-activity-company.js.map +1 -0
  144. package/src/lib/server/crm-inbound-email.js +21 -15
  145. package/src/lib/server/crm-inbound-email.js.map +1 -1
  146. package/src/lib/server/crm-sharing.js +9 -2
  147. package/src/lib/server/crm-sharing.js.map +1 -1
  148. package/src/lib/server/deal-contact-roles.d.ts +2 -0
  149. package/src/lib/server/deal-contact-roles.js +38 -1
  150. package/src/lib/server/deal-contact-roles.js.map +1 -1
  151. package/src/lib/server/email-send.js +10 -8
  152. package/src/lib/server/email-send.js.map +1 -1
  153. package/src/lib/server/merge-contacts.js +6 -2
  154. package/src/lib/server/merge-contacts.js.map +1 -1
  155. package/src/lib/server/org-caller.d.ts +8 -0
  156. package/src/lib/server/org-caller.js +10 -0
  157. package/src/lib/server/org-caller.js.map +1 -1
  158. package/src/lib/server/record-timeline.d.ts +3 -1
  159. package/src/lib/server/record-timeline.js +11 -3
  160. package/src/lib/server/record-timeline.js.map +1 -1
  161. package/src/lib/server.js +152 -43
  162. package/src/lib/server.js.map +1 -1
  163. package/src/lib/transfer/contacts.js +83 -48
  164. package/src/lib/transfer/contacts.js.map +1 -1
  165. package/src/lib/transfer/fields.js +1 -2
  166. package/src/lib/transfer/fields.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n type CrmDealStatus,\n type CrmPicklist,\n CRM_COLLECTIONS,\n crmPicklistRank,\n dealStageById,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport {\n DEAL_FILTER_CODECS,\n DEAL_LIST_DECLARATION,\n DEAL_LIST_FILTER_FIELDS,\n DEAL_LIST_SORTS,\n DEAL_PICKLIST_FILTER_NONE,\n DEAL_PREFIX_SEARCH,\n dealPipelineBase,\n dealQueryClause,\n} from '../constants/deal-filters'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n useDealsByStatus,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_DEALS_RESOURCE } from '../transfer/fields'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the status, \"No next activity\", the Type and the Lead source. */\nconst DEAL_GRID_FILTER_FIELDS = DEAL_LIST_FILTER_FIELDS\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n type: 'Type',\n leadSource: 'Lead source',\n}\nconst DEAL_STATUS_FILTER_OPTIONS = (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n}))\n\n/** Salesforce's Type and Lead source columns are offered, not shown, until a view turns them on (AGL-3516). */\nconst DEAL_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { type: false, leadSource: false }\n\n/** A picklist's choices for its filter: every value the org keeps, inactive ones marked, then \"none\". */\nfunction picklistFilterOptions(picklist: CrmPicklist, none: string): ListFilterOption[] {\n return [\n ...picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: DEAL_PICKLIST_FILTER_NONE, label: none },\n ]\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by a query that carries its status, \"No next activity\" and search\n * word (AGL-3321); it is where the closed history is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Import and Export open the console's import wizard and export\n * dialog on `crm.deals` (AGL-3528): the export over the selection, the\n * list's own query, or every deal.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // Salesforce's Type and Lead source lists, for their columns and filters (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', scope.orgId)\n const leadSourceList = useCrmPicklist('leadSource', scope.orgId)\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // Every clause is the query's, the status and \"No next activity\" alike.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n /*\n * THE TABLE (AGL-3321): the pipeline's deals, every clause and the search\n * word on one query — the search reads the title's word prefixes every\n * deal writer stamps, so a deal on page four is found by any word of its\n * title — paged by that query, and read only while the table is showing.\n */\n const [searchWords, setSearchWords] = useState<string[]>([])\n const searching = searchWords.some((word) => word.trim())\n const searchKey = searchWords.join(' ')\n const foldsScope = useCrmFoldsScope(scope.orgId, scope.visibleTo)\n const pipelineBase = useMemo(() => dealPipelineBase(pipeline?.$id ?? null), [pipeline?.$id])\n // Each stored clause asked through the field its writer keeps — a Type\n // or a Lead source by its key (AGL-3516).\n const asked = useMemo(() => crmAskClauses(viewFilters, dealQueryClause), [viewFilters])\n // The header order the view asks (AGL-3680), read before the query it orders.\n const dealSort = useMemo(\n () => crmViewQuerySort(DEAL_LIST_SORTS, views.state.sort, DEAL_LIST_SORTS[0]),\n [views.state.sort],\n )\n const paged = useCrmListQuery<DealDoc>({\n scope: scope.scope,\n collection: CRM_COLLECTIONS.deals,\n visibleTo: scope.visibleTo,\n foldsScope,\n declaration: DEAL_LIST_DECLARATION,\n clauses: asked.clauses,\n search: searchWords,\n base: pipelineBase,\n sort: dealSort.sort,\n prefixSearch: DEAL_PREFIX_SEARCH,\n enabled: view === 'table' && Boolean(pipeline),\n })\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [paged.plan, paged.page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n const exportFilter = useMemo(\n () => (paged.plan.served.length || paged.plan.searched ? { label: 'what the list shows', plan: paged.plan } : null),\n [paged.plan],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Type and Lead source, optional, in their lists' order (AGL-3516).\n picklistColumn('type', 'Type', typeList.picklist),\n picklistColumn('leadSource', 'Lead source', leadSourceList.picklist),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs, typeList.picklist, leadSourceList.picklist],\n )\n /** The table's rows: the query's page. */\n const tableRows = paged.rows\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313),\n * every one of them asked of the query (AGL-3321).\n */\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n viewFilters\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const types = picklistFilterOptions(typeList.picklist, 'No type')\n const sources = picklistFilterOptions(leadSourceList.picklist, 'No lead source')\n return {\n status: DEAL_STATUS_FILTER_OPTIONS,\n type: [...types, ...stale('type', types)],\n leadSource: [...sources, ...stale('leadSource', sources)],\n }\n }, [viewFilters, typeList.picklist, leadSourceList.picklist])\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, DEAL_GRID_FILTER_FIELDS, filterOptions, DEAL_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, DEAL_HIDDEN_COLUMNS)\n /*\n * EVERY HEADER SORTS (AGL-3680). The grid used to sort the view's order\n * over the one page on screen while the footer paged the query, which read\n * as the whole pipeline's order. Deal, Amount, Expected close, Status and\n * Next activity sort the QUERY now (`DEAL_LIST_SORTS`); the rest sort the\n * page and say so — Stage in the pipeline's order, Type and Lead source in\n * the order the org keeps their values.\n */\n const pageSorts = useMemo(() => {\n const rank = (list: CrmPicklist, label: unknown) =>\n String(label ?? '').trim() ? crmPicklistRank(list, label) : null\n return {\n stageId: (row: DealDoc) => {\n const stages = pipelineState.pipelineById(row.pipelineId)?.stages ?? []\n const at = stages.findIndex((stage) => stage.id === row.stageId)\n return at === -1 ? null : at\n },\n type: (row: DealDoc) => rank(typeList.picklist, row.type),\n leadSource: (row: DealDoc) => rank(leadSourceList.picklist, row.leadSource),\n }\n }, [pipelineState, typeList.picklist, leadSourceList.picklist])\n const columnSort = useCrmColumnSort<DealDoc>({\n views,\n sorts: DEAL_LIST_SORTS,\n defaultSort: DEAL_LIST_SORTS[0],\n asked: dealSort,\n orderBy: paged.plan.orderBy,\n rows: tableRows,\n columns: grid.columns,\n pageSorts,\n })\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status', 'type', 'leadSource'],\n codecs: DEAL_FILTER_CODECS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: DEAL_GRID_FILTER_FIELDS,\n headers: DEAL_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_DEALS_RESOURCE} noun=\"deals\" hostId={hostId} mappingZone=\"deals\" />\n <CrmExportButton resource={CRM_DEALS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Figure label=\"Open deals\" value={summary.openCount.toLocaleString()} />\n <Figure label=\"Pipeline value\" value={formatAmountByCurrency(summary.valueByCurrency)} />\n <Figure label=\"Weighted value\" value={formatAmountByCurrency(summary.weightedByCurrency)} />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <ListFilterChips\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices\n refused={refused}\n notices={[...paged.plan.notices, ...columnSort.notices]}\n />\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={columnSort.rows}\n columnSort={columnSort}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel and the search are the query's (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n loading={paged.status === 'loading'}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={\n searching\n ? `No deals match “${searchKey.trim()}”`\n : statusFilter === 'all'\n ? 'No deals yet'\n : `No ${statusFilter} deals`\n }\n noRowsDescription={\n statusFilter === 'all' && !searching\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' && !searching ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns are the view's, controlled (AGL-2617); so is the\n // header order, which `columnSort` keeps in it (AGL-3680).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\n/** An optional column of a picklist field: its label, sorted in the list's order. */\nfunction picklistColumn(field: 'type' | 'leadSource', headerName: string, picklist: CrmPicklist): GridColDef {\n return {\n field,\n headerName,\n flex: 0.8,\n minWidth: 130,\n valueGetter: (_value: unknown, row: DealDoc) => String(row[field] ?? ''),\n sortComparator: (a: string, b: string) =>\n crmPicklistRank(picklist, a) - crmPicklistRank(picklist, b) || a.localeCompare(b),\n }\n}\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["CRM_COLLECTIONS","crmPicklistRank","dealStageById","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","ListQueryNotices","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","DEAL_FILTER_CODECS","DEAL_LIST_DECLARATION","DEAL_LIST_FILTER_FIELDS","DEAL_LIST_SORTS","DEAL_PICKLIST_FILTER_NONE","DEAL_PREFIX_SEARCH","dealPipelineBase","dealQueryClause","useCrmFoldsScope","useCrmListQuery","crmViewQuerySort","useCrmColumnSort","useCrmPicklist","crmAskClauses","crmQueryRefusals","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","useDealsByStatus","useOrgMemberDirectory","usePipeline","crmRoutes","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","CrmExportButton","CrmImportButton","CRM_DEALS_RESOURCE","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","DEAL_GRID_FILTER_HEADERS","status","column","type","leadSource","DEAL_STATUS_FILTER_OPTIONS","map","value","label","DEAL_HIDDEN_COLUMNS","picklistFilterOptions","picklist","none","values","active","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","typeList","leadSourceList","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","closedPipelineId","won","lost","searchWords","setSearchWords","searching","some","word","trim","searchKey","join","foldsScope","pipelineBase","asked","dealSort","sort","paged","collection","deals","declaration","clauses","search","base","prefixSearch","enabled","Boolean","summary","data","moving","setMoving","Set","track","deal","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","plan","page","exportFilter","served","length","searched","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","filter","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","pipelineById","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","expectedCloseAtMs","toLocaleDateString","picklistColumn","tableRows","rows","filterOptions","stale","known","op","flatMap","split","option","types","sources","filterColumns","grid","pageSorts","rank","list","String","stages","at","findIndex","id","columnSort","sorts","defaultSort","orderBy","gridFilter","onChange","setFilters","selectFields","codecs","words","refused","fields","headers","options","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","startIcon","path","disabled","onClick","useFlexGap","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","loading","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","sortComparator","a","b","localeCompare","component"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deals-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n type CrmDealStatus,\n type CrmPicklist,\n CRM_COLLECTIONS,\n crmPicklistRank,\n dealStageById,\n ORG_SCOPE_TOKEN,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n mdiCogOutline,\n mdiPlus,\n mdiTableLarge,\n mdiViewColumnOutline,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport {\n DEAL_FILTER_CODECS,\n DEAL_LIST_DECLARATION,\n DEAL_LIST_FILTER_FIELDS,\n DEAL_LIST_SORTS,\n DEAL_PICKLIST_FILTER_NONE,\n DEAL_PREFIX_SEARCH,\n dealPipelineBase,\n dealQueryClause,\n} from '../constants/deal-filters'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n BOARD_CLOSED_LIMIT,\n BOARD_OPEN_LIMIT,\n useDealsByStatus,\n} from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n boardSummary,\n DEAL_STATUS_LABELS,\n type DealDoc,\n formatAmountByCurrency,\n formatMoney,\n} from '../model/deal-board-model'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { DealBoard } from './deal-board'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_DEALS_RESOURCE } from '../transfer/fields'\nimport DealsBulkBar from './deals-bulk-bar'\nimport { LostReasonDialog } from './lost-reason-dialog'\nimport { OwnerAvatar } from './owner-avatar'\nimport { PipelinesDialog } from './pipelines-dialog'\n\ntype View = 'board' | 'table'\ntype StatusFilter = CrmDealStatus | 'all'\n\n/** What the deals table's panel offers: the status, \"No next activity\", the Type and the Lead source. */\nconst DEAL_GRID_FILTER_FIELDS = DEAL_LIST_FILTER_FIELDS\nconst DEAL_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n type: 'Type',\n leadSource: 'Lead source',\n}\nconst DEAL_STATUS_FILTER_OPTIONS = (['open', 'won', 'lost'] as const).map((value) => ({\n value,\n label: DEAL_STATUS_LABELS[value],\n}))\n\n/** Salesforce's Type and Lead source columns are offered, not shown, until a view turns them on (AGL-3516). */\nconst DEAL_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { type: false, leadSource: false }\n\n/** A picklist's choices for its filter: every value the org keeps, inactive ones marked, then \"none\". */\nfunction picklistFilterOptions(picklist: CrmPicklist, none: string): ListFilterOption[] {\n return [\n ...picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: DEAL_PICKLIST_FILTER_NONE, label: none },\n ]\n}\n\n\n/** What a pipeline made at the organization level is stamped with — the org's own token. */\nconst ORG_PIPELINE_TOKENS: readonly string[] = [ORG_SCOPE_TOKEN]\n\n/**\n * `/crm/deals` — the pipeline (AGL-2598).\n *\n * ## Two views of one pipeline\n *\n * The BOARD is one pipeline's open deals, one column per open stage, read\n * in one bounded listener over `(visibleTo, pipelineId, status, updatedAt)`\n * and sorted into columns here — a listener per column would be five\n * subscriptions for one board. The TABLE is the same pipeline's deals,\n * paged by a query that carries its status, \"No next activity\" and search\n * word (AGL-3321); it is where the closed history is found. The three figures above both come from the board's read, so\n * they describe what is in play whichever view is showing.\n *\n * ## Which pipeline\n *\n * The switcher in the header (AGL-2620) picks among the ACTIVE pipelines\n * and opens on the default; the board, the table, the figures and the New\n * deal drawer all follow it. It is section state rather than a route: a\n * pipeline is a lens on the section, and a saved view that names one\n * composes with it. **Pipelines** opens the dialog that creates, renames,\n * defaults, archives and — through it — edits the stages of each.\n *\n * ## Every move goes through the route\n *\n * A drag, a card's \"Move to\", \"Mark won\" and \"Mark lost\" all end in\n * `useDealStageApi`, never a Firestore write: the stage is the fact the\n * automations listen for, and only the server emits the event. The section\n * marks the card pending while the request runs and lets the listener land\n * it — see `DealBoard` for why a move is not applied optimistically.\n *\n * ## Creation is a button\n *\n * \"New deal\" opens a drawer; there is no form above the list. The drawer\n * writes client-direct against the rules, stamped with this console's scope.\n *\n * ## The table selects and exports (AGL-2621)\n *\n * The table's rows are selectable, and a selection raises `DealsBulkBar`\n * over it — whose stage moves go through the same `useDealStageApi` as a\n * drag. Import and Export open the console's import wizard and export\n * dialog on `crm.deals` (AGL-3528): the export over the selection, the\n * list's own query, or every deal.\n */\nexport function DealsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath ?? '')\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const { activePipelines } = pipelineState\n const [pipelineId, setPipelineId] = useState<string | null>(null)\n // The chosen pipeline while it is still active; else the default. A\n // pipeline archived from another tab falls back rather than leaving the\n // board on a pipeline the picker no longer lists.\n const pipeline =\n (pipelineId ? activePipelines.find((entry) => entry.$id === pipelineId) : null) ??\n pipelineState.pipeline\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n // The org's deal fields, for the table's optional columns (AGL-2661).\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // Salesforce's Type and Lead source lists, for their columns and filters (AGL-3516).\n const typeList = useCrmPicklist('opportunityType', scope.orgId)\n const leadSourceList = useCrmPicklist('leadSource', scope.orgId)\n\n const [view, setView] = useState<View>('board')\n const [closedExpanded, setClosedExpanded] = useState(false)\n /*\n * The table's status filter is the saved VIEW'S (AGL-2617): a view of\n * deals is a view of the table, and it holds the status beside the\n * columns and the sort. Opening one shows the table, because the board\n * has no columns to arrange and answers to the pipeline's stages alone.\n */\n const views = useCrmSavedView({\n section: 'deals',\n hostId,\n org,\n basePath: basePath ?? '',\n })\n const statusFilter: StatusFilter = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'status')?.value\n return value === 'open' || value === 'won' || value === 'lost' ? value : 'all'\n }, [views.state.filters])\n // Every clause is the query's, the status and \"No next activity\" alike.\n const viewFilters = views.state.filters\n useEffect(() => {\n if (views.currentId) setView('table')\n }, [views.currentId])\n\n // The board's read stays on in the table view too: it is what the summary\n // above both views is computed from, and it is already bounded.\n const open = useDealsByStatus(\n scope.orgId,\n scope.visibleTo,\n pipeline?.$id ?? null,\n 'open',\n BOARD_OPEN_LIMIT,\n )\n const dealsLoaded = open.status !== 'loading'\n const closedPipelineId =\n view === 'board' && closedExpanded ? (pipeline?.$id ?? null) : null\n const won = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'won', BOARD_CLOSED_LIMIT)\n const lost = useDealsByStatus(scope.orgId, scope.visibleTo, closedPipelineId, 'lost', BOARD_CLOSED_LIMIT)\n /*\n * THE TABLE (AGL-3321): the pipeline's deals, every clause and the search\n * word on one query — the search reads the title's word prefixes every\n * deal writer stamps, so a deal on page four is found by any word of its\n * title — paged by that query, and read only while the table is showing.\n */\n const [searchWords, setSearchWords] = useState<string[]>([])\n const searching = searchWords.some((word) => word.trim())\n const searchKey = searchWords.join(' ')\n const foldsScope = useCrmFoldsScope(scope.orgId, scope.visibleTo)\n const pipelineBase = useMemo(() => dealPipelineBase(pipeline?.$id ?? null), [pipeline?.$id])\n // Each stored clause asked through the field its writer keeps — a Type\n // or a Lead source by its key (AGL-3516).\n const asked = useMemo(() => crmAskClauses(viewFilters, dealQueryClause), [viewFilters])\n // The header order the view asks (AGL-3680), read before the query it orders.\n const dealSort = useMemo(\n () => crmViewQuerySort(DEAL_LIST_SORTS, views.state.sort, DEAL_LIST_SORTS[0]),\n [views.state.sort],\n )\n const paged = useCrmListQuery<DealDoc>({\n scope: scope.scope,\n collection: CRM_COLLECTIONS.deals,\n visibleTo: scope.visibleTo,\n foldsScope,\n declaration: DEAL_LIST_DECLARATION,\n clauses: asked.clauses,\n search: searchWords,\n base: pipelineBase,\n sort: dealSort.sort,\n prefixSearch: DEAL_PREFIX_SEARCH,\n enabled: view === 'table' && Boolean(pipeline),\n })\n\n const summary = useMemo(() => boardSummary(open.data, pipeline), [open.data, pipeline])\n\n /*\n * MOVES IN FLIGHT. A card in this set is dimmed and cannot be dragged\n * again until the route answers; the listener is what moves it.\n */\n const [moving, setMoving] = useState<ReadonlySet<string>>(() => new Set())\n const track = useCallback(\n async (deal: DealDoc, request: () => Promise<unknown>, done: string) => {\n setMoving((current) => new Set(current).add(deal.$id))\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setMoving((current) => {\n const next = new Set(current)\n next.delete(deal.$id)\n return next\n })\n }\n },\n [enqueueSnackbar],\n )\n const handleMove = useCallback(\n (deal: DealDoc, stageId: string) => {\n const stage = dealStageById(pipeline, stageId)\n void track(\n deal,\n () => api.moveToStage(deal, stageId),\n `Moved to ${stage?.name ?? 'stage'}`,\n )\n },\n [api, pipeline, track],\n )\n const handleWon = useCallback(\n (deal: DealDoc) => void track(deal, () => api.markWon(deal), 'Deal won'),\n [api, track],\n )\n const [losing, setLosing] = useState<DealDoc | null>(null)\n const handleLost = useCallback((deal: DealDoc) => setLosing(deal), [])\n const confirmLost = useCallback(\n (reason: string) => {\n const deal = losing\n if (!deal) return\n setLosing(null)\n void track(deal, () => api.markLost(deal, reason), 'Deal marked lost')\n },\n [api, losing, track],\n )\n\n const openDeal = useCallback(\n (deal: DealDoc) => router.push(routes.deal(deal.$id)),\n [router, routes],\n )\n\n const [creating, setCreating] = useState(false)\n const [managingPipelines, setManagingPipelines] = useState(false)\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A page or a status is a different set of rows; a selection made on\n // the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [paged.plan, paged.page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3528).\n const exportFilter = useMemo(\n () => (paged.plan.served.length || paged.plan.searched ? { label: 'what the list shows', plan: paged.plan } : null),\n [paged.plan],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'title',\n headerName: 'Deal',\n flex: 1.6,\n minWidth: 220,\n renderCell: ({ row }: { row: DealDoc }) => {\n const withWhom = [row.contactName, row.companyName].filter(Boolean).join(' · ')\n return (\n <Stack sx={{ justifyContent: 'center', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.title || 'Untitled deal'}\n </Typography>\n {withWhom ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap sx={{ lineHeight: 1.25 }}>\n {withWhom}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n {\n field: 'stageId',\n headerName: 'Stage',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) =>\n dealStageById(pipelineState.pipelineById(row.pipelineId), row.stageId)?.name ??\n row.stageId,\n },\n {\n field: 'amountCents',\n headerName: 'Amount',\n flex: 0.8,\n minWidth: 120,\n align: 'right',\n headerAlign: 'right',\n valueGetter: (_value: unknown, row: DealDoc) => row.amountCents ?? null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.amountCents === 'number' ? formatMoney(row.amountCents, row.currency) : '—',\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value: unknown, row: DealDoc) => roster.nameOf(row.ownerUid),\n renderCell: ({ row }: { row: DealDoc }) => {\n const label = roster.nameOf(row.ownerUid)\n return label ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <OwnerAvatar label={label} size={22} />\n <Typography variant=\"body2\" noWrap>\n {label}\n </Typography>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'expectedCloseAtMs',\n headerName: 'Expected close',\n flex: 0.8,\n minWidth: 130,\n type: 'date',\n valueGetter: (_value: unknown, row: DealDoc) =>\n typeof row.expectedCloseAtMs === 'number' ? new Date(row.expectedCloseAtMs) : null,\n renderCell: ({ row }: { row: DealDoc }) =>\n typeof row.expectedCloseAtMs === 'number'\n ? new Date(row.expectedCloseAtMs).toLocaleDateString()\n : '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.6,\n minWidth: 100,\n renderCell: ({ row }: { row: DealDoc }) => (\n <Chip\n size=\"small\"\n label={DEAL_STATUS_LABELS[row.status] ?? row.status}\n color={row.status === 'won' ? 'success' : row.status === 'lost' ? 'default' : 'primary'}\n variant={row.status === 'open' ? 'outlined' : 'filled'}\n />\n ),\n },\n // When the earliest open task against the deal is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Type and Lead source, optional, in their lists' order (AGL-3516).\n picklistColumn('type', 'Type', typeList.picklist),\n picklistColumn('leadSource', 'Lead source', leadSourceList.picklist),\n // The org's deal fields as optional columns (AGL-2661).\n ...customFieldColumns(dealFields.active),\n ],\n [pipelineState, roster, dealFields.active, nowMs, typeList.picklist, leadSourceList.picklist],\n )\n /** The table's rows: the query's page. */\n const tableRows = paged.rows\n /*\n * The table's column and sort models are the view's (AGL-2617); its\n * filters are the grid's own panel over the view's clauses (AGL-3313),\n * every one of them asked of the query (AGL-3321).\n */\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed stays a choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo<Record<string, ListFilterOption[]>>(() => {\n const stale = (field: string, known: readonly { value: string }[]) =>\n viewFilters\n .filter((clause) => clause.field === field && clause.op !== 'isEmpty')\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n const types = picklistFilterOptions(typeList.picklist, 'No type')\n const sources = picklistFilterOptions(leadSourceList.picklist, 'No lead source')\n return {\n status: DEAL_STATUS_FILTER_OPTIONS,\n type: [...types, ...stale('type', types)],\n leadSource: [...sources, ...stale('leadSource', sources)],\n }\n }, [viewFilters, typeList.picklist, leadSourceList.picklist])\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, DEAL_GRID_FILTER_FIELDS, filterOptions, DEAL_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, DEAL_HIDDEN_COLUMNS)\n /*\n * EVERY HEADER SORTS (AGL-3680). The grid used to sort the view's order\n * over the one page on screen while the footer paged the query, which read\n * as the whole pipeline's order. Deal, Amount, Expected close, Status and\n * Next activity sort the QUERY now (`DEAL_LIST_SORTS`); the rest sort the\n * page and say so — Stage in the pipeline's order, Type and Lead source in\n * the order the org keeps their values.\n */\n const pageSorts = useMemo(() => {\n const rank = (list: CrmPicklist, label: unknown) =>\n String(label ?? '').trim() ? crmPicklistRank(list, label) : null\n return {\n stageId: (row: DealDoc) => {\n const stages = pipelineState.pipelineById(row.pipelineId)?.stages ?? []\n const at = stages.findIndex((stage) => stage.id === row.stageId)\n return at === -1 ? null : at\n },\n type: (row: DealDoc) => rank(typeList.picklist, row.type),\n leadSource: (row: DealDoc) => rank(leadSourceList.picklist, row.leadSource),\n }\n }, [pipelineState, typeList.picklist, leadSourceList.picklist])\n const columnSort = useCrmColumnSort<DealDoc>({\n views,\n sorts: DEAL_LIST_SORTS,\n defaultSort: DEAL_LIST_SORTS[0],\n asked: dealSort,\n orderBy: paged.plan.orderBy,\n rows: tableRows,\n columns: grid.columns,\n pageSorts,\n })\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['status', 'type', 'leadSource'],\n codecs: DEAL_FILTER_CODECS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: DEAL_GRID_FILTER_FIELDS,\n headers: DEAL_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n\n const noOrg = scope.ready && !scope.orgId\n const pipelineLoading =\n !noOrg && (pipelineState.status === 'loading' || pipelineState.seeding || (!pipeline && pipelineState.status !== 'error'))\n\n return (\n <>\n <CardDisplay\n header={'Deals / Opportunities'}\n help={pluginDocsHelp('deals', { anchor: '#the-board-and-the-table' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_DEALS_RESOURCE} noun=\"deals\" hostId={hostId} mappingZone=\"deals\" />\n <CrmExportButton resource={CRM_DEALS_RESOURCE} hostId={hostId} filter={exportFilter} />\n <Button\n size=\"small\"\n startIcon={<MdiIcon path={mdiCogOutline.path} size={0.8} />}\n disabled={!scope.orgId || pipelineState.status === 'loading'}\n onClick={() => setManagingPipelines(true)}\n >\n {'Pipelines'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={3}\n sx={{ alignItems: 'flex-end', flexWrap: 'wrap', rowGap: 1 }}\n >\n {/* A dash until the deals have loaded: a slow read is not an empty pipeline. */}\n <Figure label=\"Open deals\" value={dealsLoaded ? summary.openCount.toLocaleString() : '—'} />\n <Figure\n label=\"Pipeline value\"\n value={dealsLoaded ? formatAmountByCurrency(summary.valueByCurrency) : '—'}\n />\n <Figure\n label=\"Weighted value\"\n value={dealsLoaded ? formatAmountByCurrency(summary.weightedByCurrency) : '—'}\n />\n <Stack sx={{ flex: 1 }} />\n {/* Which pipeline the board and table show, above them rather than under (AGL-3311). */}\n {activePipelines.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Pipeline\"\n value={pipeline?.$id ?? ''}\n onChange={(event) => setPipelineId(event.target.value)}\n sx={{ minWidth: 160 }}\n slotProps={{ select: { 'aria-label': 'Pipeline' } }}\n >\n {activePipelines.map((entry) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {entry.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as View)\n }}\n aria-label=\"View\"\n >\n <ToggleButton value=\"board\" aria-label=\"Board\">\n <MdiIcon path={mdiViewColumnOutline.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Board'}\n </Typography>\n </ToggleButton>\n <ToggleButton value=\"table\" aria-label=\"Table\">\n <MdiIcon path={mdiTableLarge.path} size={0.8} />\n <Typography variant=\"button\" sx={{ ml: 0.5 }}>\n {'Table'}\n </Typography>\n </ToggleButton>\n </ToggleButtonGroup>\n </Stack>\n\n {noOrg ? (\n <Alert severity=\"info\">\n {'This site has no organization, so it has no pipeline.'}\n </Alert>\n ) : pipelineState.status === 'error' ? (\n <Alert severity=\"warning\">\n {'The pipeline could not be read. Deals need the \"Manage data\" permission.'}\n </Alert>\n ) : pipelineLoading ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {pipelineState.seeding ? 'Setting up your Sales pipeline…' : 'Loading the pipeline…'}\n </Typography>\n </Stack>\n ) : view === 'board' && pipeline ? (\n <>\n {open.status === 'success' && open.data.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No open deals yet'}\n description={'A deal is a sale in progress, moved across the stages below as it advances.'}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n }\n />\n ) : null}\n <DealBoard\n pipeline={pipeline}\n deals={open.data}\n won={closedExpanded && won.status !== 'loading' ? won.data : null}\n lost={closedExpanded && lost.status !== 'loading' ? lost.data : null}\n closedExpanded={closedExpanded}\n onToggleClosed={() => setClosedExpanded((current) => !current)}\n labelFor={roster.nameOf}\n moving={moving}\n onOpen={openDeal}\n onMove={handleMove}\n onWon={handleWon}\n onLost={handleLost}\n nowMs={nowMs}\n />\n </>\n ) : (\n <Stack spacing={1.5}>\n {/*\n The view this table is showing, and the clauses narrowing it\n as chips (AGL-2617, AGL-3313). Status and \"No next activity\"\n are set in the grid's own Filters panel.\n */}\n <CrmListToolbar label=\"Deal filters\">\n <CrmViewsControl controller={views} allLabel=\"All deals\" />\n <ListFilterChips\n fields={DEAL_GRID_FILTER_FIELDS}\n headers={DEAL_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices\n refused={refused}\n notices={[...paged.plan.notices, ...columnSort.notices]}\n />\n <>\n <DealsBulkBar\n hostId={hostId}\n scope={scope.scope}\n rows={tableRows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n pipelineById={pipelineState.pipelineById}\n roster={roster}\n api={api}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={columnSort.rows}\n columnSort={columnSort}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n onOpen={(_id, row) => openDeal(row as DealDoc)}\n // The panel and the search are the query's (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n loading={paged.status === 'loading'}\n // An empty table is said inside the grid, so its toolbar\n // stays to change the status that emptied it.\n noRowsLabel={\n searching\n ? `No deals match “${searchKey.trim()}”`\n : statusFilter === 'all'\n ? 'No deals yet'\n : `No ${statusFilter} deals`\n }\n noRowsDescription={\n statusFilter === 'all' && !searching\n ? 'A deal is a sale in progress, moved across the pipeline as it advances.'\n : undefined\n }\n noRowsAction={\n statusFilter === 'all' && !searching ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n disabled={!pipeline || !scope.orgId}\n onClick={() => setCreating(true)}\n >\n {'New deal'}\n </Button>\n ) : undefined\n }\n // Columns are the view's, controlled (AGL-2617); so is the\n // header order, which `columnSort` keeps in it (AGL-3680).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </>\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n <DealEditDrawer\n open={creating}\n onClose={() => setCreating(false)}\n hostId={hostId}\n org={org}\n pipelines={activePipelines}\n defaultPipeline={pipeline}\n />\n <PipelinesDialog\n open={managingPipelines}\n onClose={() => setManagingPipelines(false)}\n orgId={scope.orgId ?? ''}\n // A pipeline made here is stamped the way the seeded default is\n // (`usePipeline`): the site's own tokens under a site; at the\n // organization level the org token, with the picked site as its\n // provenance (AGL-2630).\n hostId={scope.createHostId}\n pipelines={pipelineState.pipelines}\n fromCache={pipelineState.fromCache}\n unreadable={pipelineState.status === 'error'}\n visibleToTokens={scope.visibleTo}\n createTokens={scope.level === 'site' ? scope.createTokens : ORG_PIPELINE_TOKENS}\n />\n <LostReasonDialog\n open={Boolean(losing)}\n dealTitle={losing?.title ?? ''}\n onClose={() => setLosing(null)}\n onConfirm={confirmLost}\n />\n </>\n )\n}\nDealsSection.displayName = 'DealsSection'\n\n/** An optional column of a picklist field: its label, sorted in the list's order. */\nfunction picklistColumn(field: 'type' | 'leadSource', headerName: string, picklist: CrmPicklist): GridColDef {\n return {\n field,\n headerName,\n flex: 0.8,\n minWidth: 130,\n valueGetter: (_value: unknown, row: DealDoc) => String(row[field] ?? ''),\n sortComparator: (a: string, b: string) =>\n crmPicklistRank(picklist, a) - crmPicklistRank(picklist, b) || a.localeCompare(b),\n }\n}\n\nfunction Figure(props: { label: string; value: string }) {\n return (\n <Stack sx={{ lineHeight: 1.2 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.label}\n </Typography>\n <Typography variant=\"h6\" component=\"span\">\n {props.value}\n </Typography>\n </Stack>\n )\n}\nFigure.displayName = 'Figure'\n\nexport default DealsSection\n"],"names":["CRM_COLLECTIONS","crmPicklistRank","dealStageById","ORG_SCOPE_TOKEN","pluginDocsHelp","mdiCogOutline","mdiPlus","mdiTableLarge","mdiViewColumnOutline","CardDisplay","MdiIcon","ListPagination","ListTable","ListQueryNotices","useCrmSavedView","useCrmViewGrid","CrmListActions","CrmListToolbar","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","DEAL_FILTER_CODECS","DEAL_LIST_DECLARATION","DEAL_LIST_FILTER_FIELDS","DEAL_LIST_SORTS","DEAL_PICKLIST_FILTER_NONE","DEAL_PREFIX_SEARCH","dealPipelineBase","dealQueryClause","useCrmFoldsScope","useCrmListQuery","crmViewQuerySort","useCrmColumnSort","useCrmPicklist","crmAskClauses","crmQueryRefusals","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","EmptyStateComponent","useSnackbar","Alert","Button","Chip","CircularProgress","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useRouter","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmScope","useDealStageApi","BOARD_CLOSED_LIMIT","BOARD_OPEN_LIMIT","useDealsByStatus","useOrgMemberDirectory","usePipeline","crmRoutes","boardSummary","DEAL_STATUS_LABELS","formatAmountByCurrency","formatMoney","customFieldColumns","DealBoard","DealEditDrawer","CrmExportButton","CrmImportButton","CRM_DEALS_RESOURCE","DealsBulkBar","LostReasonDialog","OwnerAvatar","PipelinesDialog","DEAL_GRID_FILTER_FIELDS","DEAL_GRID_FILTER_HEADERS","status","column","type","leadSource","DEAL_STATUS_FILTER_OPTIONS","map","value","label","DEAL_HIDDEN_COLUMNS","picklistFilterOptions","picklist","none","values","active","ORG_PIPELINE_TOKENS","DealsSection","props","pipelineId","scope","hostId","org","basePath","routes","router","enqueueSnackbar","pipelineState","orgId","activePipelines","setPipelineId","pipeline","find","entry","$id","roster","api","nowMs","Date","now","dealFields","typeList","leadSourceList","view","setView","closedExpanded","setClosedExpanded","views","section","statusFilter","state","filters","clause","field","viewFilters","currentId","open","visibleTo","dealsLoaded","closedPipelineId","won","lost","searchWords","setSearchWords","searching","some","word","trim","searchKey","join","foldsScope","pipelineBase","asked","dealSort","sort","paged","collection","deals","declaration","clauses","search","base","prefixSearch","enabled","Boolean","summary","data","moving","setMoving","Set","track","deal","request","done","current","add","variant","persist","error","Error","message","allowDuplicate","next","delete","handleMove","stageId","stage","moveToStage","name","handleWon","markWon","losing","setLosing","handleLost","confirmLost","reason","markLost","openDeal","push","creating","setCreating","managingPipelines","setManagingPipelines","selectedIds","setSelectedIds","plan","page","exportFilter","served","length","searched","columns","headerName","flex","minWidth","renderCell","row","withWhom","contactName","companyName","filter","sx","justifyContent","lineHeight","title","color","noWrap","valueGetter","_value","pipelineById","align","headerAlign","amountCents","currency","nameOf","ownerUid","direction","spacing","alignItems","size","expectedCloseAtMs","toLocaleDateString","picklistColumn","tableRows","rows","filterOptions","stale","known","op","flatMap","split","option","types","sources","filterColumns","grid","pageSorts","rank","list","String","stages","at","findIndex","id","columnSort","sorts","defaultSort","orderBy","gridFilter","onChange","setFilters","selectFields","codecs","words","refused","fields","headers","options","noOrg","ready","pipelineLoading","seeding","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","mappingZone","startIcon","path","disabled","onClick","useFlexGap","flexWrap","rowGap","Figure","openCount","toLocaleString","valueByCurrency","weightedByCurrency","select","event","target","slotProps","exclusive","_event","aria-label","ml","severity","compact","description","onToggleClosed","labelFor","onOpen","onMove","onWon","onLost","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","slots","selectable","_id","filterMode","filterModel","onFilterModelChange","quickFilter","loading","noRowsLabel","noRowsDescription","undefined","noRowsAction","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","pipelines","defaultPipeline","createHostId","fromCache","unreadable","visibleToTokens","createTokens","level","dealTitle","onConfirm","displayName","sortComparator","a","b","localeCompare","component"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAIEA,eAAe,EACfC,eAAe,EACfC,aAAa,EACbC,eAAe,EACfC,cAAc,QACT,eAAc;AACrB,SACEC,aAAa,EACbC,OAAO,EACPC,aAAa,EACbC,oBAAoB,QACf,yBAAwB;AAC/B,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,OAAOC,sBAAsB,+DAA8D;AAC3F,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,cAAc,EAAEC,cAAc,QAAQ,wBAAoB;AACnE,SAASC,cAAc,EAAEC,sBAAsB,QAAQ,uBAAmB;AAC1E,SACEC,8BAA8B,EAC9BC,+BAA+B,EAC/BC,kBAAkB,QACb,gCAA4B;AACnC,SACEC,kBAAkB,EAClBC,qBAAqB,EACrBC,uBAAuB,EACvBC,eAAe,EACfC,yBAAyB,EACzBC,kBAAkB,EAClBC,gBAAgB,EAChBC,eAAe,QACV,+BAA2B;AAClC,SAASC,gBAAgB,EAAEC,eAAe,QAAQ,iCAA6B;AAC/E,SAASC,gBAAgB,EAAEC,gBAAgB,QAAQ,kCAA8B;AACjF,SAASC,cAAc,QAAQ,+BAA2B;AAC1D,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,6BAAyB;AACzE,OAAOC,qBAAqB,8DAA6D;AACzF,SAEEC,qBAAqB,QAChB,8CAA6C;AACpD,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,OAAOC,qBAAqB,yBAAqB;AACjD,OAAOC,yBAAyB,wDAAuD;AACvF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,KAAK,EACLC,MAAM,EACNC,IAAI,EACJC,gBAAgB,EAChBC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,QACL,gBAAe;AAEtB,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,WAAW,QAAQ,4BAAwB;AACpD,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,gBAAgB,QACX,wBAAoB;AAC3B,SAASC,qBAAqB,QAAQ,uCAAmC;AACzE,SAASC,WAAW,QAAQ,2BAAuB;AACnD,SAASC,SAAS,QAAQ,yBAAqB;AAC/C,SACEC,YAAY,EACZC,kBAAkB,EAElBC,sBAAsB,EACtBC,WAAW,QACN,+BAA2B;AAClC,SAASC,kBAAkB,QAAQ,8BAA0B;AAC7D,SAASC,SAAS,QAAQ,kBAAc;AACxC,SAASC,cAAc,QAAQ,wBAAoB;AACnD,SAASC,eAAe,EAAEC,eAAe,QAAQ,4BAAwB;AACzE,SAASC,kBAAkB,QAAQ,wBAAoB;AACvD,OAAOC,kBAAkB,sBAAkB;AAC3C,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,SAASC,WAAW,QAAQ,oBAAgB;AAC5C,SAASC,eAAe,QAAQ,wBAAoB;AAKpD,uGAAuG,GACvG,MAAMC,0BAA0BzD;AAChC,MAAM0D,2BAA6D;IACjEC,QAAQ;IACR,CAAChE,+BAA+BiE,MAAM,CAAC,EAAEhE;IACzCiE,MAAM;IACNC,YAAY;AACd;AACA,MAAMC,6BAA6B,AAAC;IAAC;IAAQ;IAAO;CAAO,CAAWC,GAAG,CAAC,CAACC,QAAW,CAAA;QACpFA;QACAC,OAAOtB,kBAAkB,CAACqB,MAAM;IAClC,CAAA;AAEA,6GAA6G,GAC7G,MAAME,sBAAyD;IAAEN,MAAM;IAAOC,YAAY;AAAM;AAEhG,uGAAuG,GACvG,SAASM,sBAAsBC,QAAqB,EAAEC,IAAY;IAChE,OAAO;WACFD,SAASE,MAAM,CAACP,GAAG,CAAC,CAACC,QAAW,CAAA;gBACjCA,OAAOA,MAAMC,KAAK;gBAClBA,OAAOD,MAAMO,MAAM,GAAGP,MAAMC,KAAK,GAAG,GAAGD,MAAMC,KAAK,CAAC,WAAW,CAAC;YACjE,CAAA;QACA;YAAED,OAAO/D;YAA2BgE,OAAOI;QAAK;KACjD;AACH;AAGA,0FAA0F,GAC1F,MAAMG,sBAAyC;IAAC/F;CAAgB;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CC,GACD,OAAO,SAASgG,aAAaC,KAA6B;QAgBrDC,2BAolBUC;IAnmBb,MAAM,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGL;IAClC,MAAMM,SAASvC,UAAUsC,mBAAAA,WAAY;IACrC,MAAME,SAASrD;IACf,MAAM,EAAEsD,eAAe,EAAE,GAAGjE;IAC5B,MAAM2D,QAAQ1C,YAAY;QAAE2C;QAAQC;IAAI;IACxC,MAAMK,gBAAgB3C,YAAYoC,MAAMQ,KAAK,EAAE;QAC7CP;QACAC,GAAG,EAAGA,cAAAA,MAAO;IACf;IACA,MAAM,EAAEO,eAAe,EAAE,GAAGF;IAC5B,MAAM,CAACR,YAAYW,cAAc,GAAGtD,SAAwB;IAC5D,oEAAoE;IACpE,wEAAwE;IACxE,kDAAkD;IAClD,MAAMuD,YACHZ,OAAAA,aAAaU,gBAAgBG,IAAI,CAAC,CAACC,QAAUA,MAAMC,GAAG,KAAKf,cAAc,gBAAzEA,OACDQ,cAAcI,QAAQ;IACxB,MAAMI,SAASpD,sBAAsBqC,MAAMQ,KAAK;IAChD,MAAMQ,MAAMzD,gBAAgB0C;IAC5B,MAAMgB,QAAQ9D,QAAQ,IAAM+D,KAAKC,GAAG,IAAI,EAAE;IAC1C,sEAAsE;IACtE,MAAMC,aAAa/D,2BAA2B2C,MAAMQ,KAAK,EAAE;IAC3D,qFAAqF;IACrF,MAAMa,WAAWxF,eAAe,mBAAmBmE,MAAMQ,KAAK;IAC9D,MAAMc,iBAAiBzF,eAAe,cAAcmE,MAAMQ,KAAK;IAE/D,MAAM,CAACe,MAAMC,QAAQ,GAAGpE,SAAe;IACvC,MAAM,CAACqE,gBAAgBC,kBAAkB,GAAGtE,SAAS;IACrD;;;;;GAKC,GACD,MAAMuE,QAAQnH,gBAAgB;QAC5BoH,SAAS;QACT3B;QACAC;QACAC,QAAQ,EAAEA,mBAAAA,WAAY;IACxB;IACA,MAAM0B,eAA6B1E,QAAQ;YAC3BwE;QAAd,MAAMvC,SAAQuC,4BAAAA,MAAMG,KAAK,CAACC,OAAO,CAACnB,IAAI,CAAC,CAACoB,SAAWA,OAAOC,KAAK,KAAK,8BAAtDN,0BAAiEvC,KAAK;QACpF,OAAOA,UAAU,UAAUA,UAAU,SAASA,UAAU,SAASA,QAAQ;IAC3E,GAAG;QAACuC,MAAMG,KAAK,CAACC,OAAO;KAAC;IACxB,wEAAwE;IACxE,MAAMG,cAAcP,MAAMG,KAAK,CAACC,OAAO;IACvC7E,UAAU;QACR,IAAIyE,MAAMQ,SAAS,EAAEX,QAAQ;IAC/B,GAAG;QAACG,MAAMQ,SAAS;KAAC;IAEpB,0EAA0E;IAC1E,gEAAgE;IAChE,MAAMC,OAAO1E,iBACXsC,MAAMQ,KAAK,EACXR,MAAMqC,SAAS,WACf1B,4BAAAA,SAAUG,GAAG,oBAAI,MACjB,QACArD;IAEF,MAAM6E,cAAcF,KAAKtD,MAAM,KAAK;IACpC,MAAMyD,mBACJhB,SAAS,WAAWE,0BAAkBd,4BAAAA,SAAUG,GAAG,oBAAI,OAAQ;IACjE,MAAM0B,MAAM9E,iBAAiBsC,MAAMQ,KAAK,EAAER,MAAMqC,SAAS,EAAEE,kBAAkB,OAAO/E;IACpF,MAAMiF,OAAO/E,iBAAiBsC,MAAMQ,KAAK,EAAER,MAAMqC,SAAS,EAAEE,kBAAkB,QAAQ/E;IACtF;;;;;GAKC,GACD,MAAM,CAACkF,aAAaC,eAAe,GAAGvF,SAAmB,EAAE;IAC3D,MAAMwF,YAAYF,YAAYG,IAAI,CAAC,CAACC,OAASA,KAAKC,IAAI;IACtD,MAAMC,YAAYN,YAAYO,IAAI,CAAC;IACnC,MAAMC,aAAazH,iBAAiBuE,MAAMQ,KAAK,EAAER,MAAMqC,SAAS;IAChE,MAAMc,eAAehG,QAAQ;;eAAM5B,yBAAiBoF,4BAAAA,SAAUG,GAAG,mBAAI;OAAO;QAACH,4BAAAA,SAAUG,GAAG;KAAC;IAC3F,uEAAuE;IACvE,0CAA0C;IAC1C,MAAMsC,QAAQjG,QAAQ,IAAMrB,cAAcoG,aAAa1G,kBAAkB;QAAC0G;KAAY;IACtF,8EAA8E;IAC9E,MAAMmB,WAAWlG,QACf,IAAMxB,iBAAiBP,iBAAiBuG,MAAMG,KAAK,CAACwB,IAAI,EAAElI,eAAe,CAAC,EAAE,GAC5E;QAACuG,MAAMG,KAAK,CAACwB,IAAI;KAAC;IAEpB,MAAMC,QAAQ7H,gBAAyB;QACrCsE,OAAOA,MAAMA,KAAK;QAClBwD,YAAY9J,gBAAgB+J,KAAK;QACjCpB,WAAWrC,MAAMqC,SAAS;QAC1Ba;QACAQ,aAAaxI;QACbyI,SAASP,MAAMO,OAAO;QACtBC,QAAQlB;QACRmB,MAAMV;QACNG,MAAMD,SAASC,IAAI;QACnBQ,cAAcxI;QACdyI,SAASxC,SAAS,WAAWyC,QAAQrD;IACvC;IAEA,MAAMsD,UAAU9G,QAAQ,IAAMW,aAAasE,KAAK8B,IAAI,EAAEvD,WAAW;QAACyB,KAAK8B,IAAI;QAAEvD;KAAS;IAEtF;;;GAGC,GACD,MAAM,CAACwD,QAAQC,UAAU,GAAGhH,SAA8B,IAAM,IAAIiH;IACpE,MAAMC,QAAQrH,YACZ,OAAOsH,MAAeC,SAAiCC;QACrDL,UAAU,CAACM,UAAY,IAAIL,IAAIK,SAASC,GAAG,CAACJ,KAAKzD,GAAG;QACpD,IAAI;YACF,MAAM0D;YACNlE,gBAAgBmE,MAAM;gBAAEG,SAAS;gBAAWC,SAAS;YAAM;YAC3D,OAAO;QACT,EAAE,OAAOC,OAAO;YACdxE,gBACEwE,iBAAiBC,QAAQD,MAAME,OAAO,GAAG,gCACzC;gBAAEJ,SAAS;gBAAWK,gBAAgB;YAAK;YAE7C,OAAO;QACT,SAAU;YACRb,UAAU,CAACM;gBACT,MAAMQ,OAAO,IAAIb,IAAIK;gBACrBQ,KAAKC,MAAM,CAACZ,KAAKzD,GAAG;gBACpB,OAAOoE;YACT;QACF;IACF,GACA;QAAC5E;KAAgB;IAEnB,MAAM8E,aAAanI,YACjB,CAACsH,MAAec;;QACd,MAAMC,QAAQ1L,cAAc+G,UAAU0E;QACtC,KAAKf,MACHC,MACA,IAAMvD,IAAIuE,WAAW,CAAChB,MAAMc,UAC5B,CAAC,SAAS,UAAEC,yBAAAA,MAAOE,IAAI,mBAAI,SAAS;IAExC,GACA;QAACxE;QAAKL;QAAU2D;KAAM;IAExB,MAAMmB,YAAYxI,YAChB,CAACsH,OAAkB,KAAKD,MAAMC,MAAM,IAAMvD,IAAI0E,OAAO,CAACnB,OAAO,aAC7D;QAACvD;QAAKsD;KAAM;IAEd,MAAM,CAACqB,QAAQC,UAAU,GAAGxI,SAAyB;IACrD,MAAMyI,aAAa5I,YAAY,CAACsH,OAAkBqB,UAAUrB,OAAO,EAAE;IACrE,MAAMuB,cAAc7I,YAClB,CAAC8I;QACC,MAAMxB,OAAOoB;QACb,IAAI,CAACpB,MAAM;QACXqB,UAAU;QACV,KAAKtB,MAAMC,MAAM,IAAMvD,IAAIgF,QAAQ,CAACzB,MAAMwB,SAAS;IACrD,GACA;QAAC/E;QAAK2E;QAAQrB;KAAM;IAGtB,MAAM2B,WAAWhJ,YACf,CAACsH,OAAkBlE,OAAO6F,IAAI,CAAC9F,OAAOmE,IAAI,CAACA,KAAKzD,GAAG,IACnD;QAACT;QAAQD;KAAO;IAGlB,MAAM,CAAC+F,UAAUC,YAAY,GAAGhJ,SAAS;IACzC,MAAM,CAACiJ,mBAAmBC,qBAAqB,GAAGlJ,SAAS;IAE3D,MAAM,CAACmJ,aAAaC,eAAe,GAAGpJ,SAAmB,EAAE;IAC3D,qEAAqE;IACrE,+DAA+D;IAC/DF,UAAU,IAAMsJ,eAAe,EAAE,GAAG;QAACjD,MAAMkD,IAAI;QAAElD,MAAMmD,IAAI;KAAC;IAC5D,0EAA0E;IAC1E,oCAAoC;IACpC,MAAMC,eAAexJ,QACnB,IAAOoG,MAAMkD,IAAI,CAACG,MAAM,CAACC,MAAM,IAAItD,MAAMkD,IAAI,CAACK,QAAQ,GAAG;YAAEzH,OAAO;YAAuBoH,MAAMlD,MAAMkD,IAAI;QAAC,IAAI,MAC9G;QAAClD,MAAMkD,IAAI;KAAC;IAGd,MAAMM,UAAwB5J,QAC5B,IAAM;YACJ;gBACE8E,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAAoB;oBACpC,MAAMC,WAAW;wBAACD,IAAIE,WAAW;wBAAEF,IAAIG,WAAW;qBAAC,CAACC,MAAM,CAACxD,SAASf,IAAI,CAAC;oBACzE,qBACE,MAACtG;wBAAM8K,IAAI;4BAAEC,gBAAgB;4BAAUC,YAAY;wBAAK;;0CACtD,KAAC5K;gCAAW6H,SAAQ;gCAAQ6C,IAAI;oCAAEE,YAAY;gCAAK;0CAChDP,IAAIQ,KAAK,IAAI;;4BAEfP,yBACC,KAACtK;gCAAW6H,SAAQ;gCAAUiD,OAAM;gCAAiBC,MAAM;gCAACL,IAAI;oCAAEE,YAAY;gCAAK;0CAChFN;iCAED;;;gBAGV;YACF;YACA;gBACEpF,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVa,aAAa,CAACC,QAAiBZ;;wBAC7BxN;oCAAAA,iBAAAA,cAAc2G,cAAc0H,YAAY,CAACb,IAAIrH,UAAU,GAAGqH,IAAI/B,OAAO,sBAArEzL,eAAwE4L,IAAI,mBAC5E4B,IAAI/B,OAAO;;YACf;YACA;gBACEpD,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVgB,OAAO;gBACPC,aAAa;gBACbJ,aAAa,CAACC,QAAiBZ;wBAAiBA;4BAAAA,mBAAAA,IAAIgB,WAAW,YAAfhB,mBAAmB;;gBACnED,YAAY,CAAC,EAAEC,GAAG,EAAoB,GACpC,OAAOA,IAAIgB,WAAW,KAAK,WAAWnK,YAAYmJ,IAAIgB,WAAW,EAAEhB,IAAIiB,QAAQ,IAAI;YACvF;YACA;gBACEpG,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVa,aAAa,CAACC,QAAiBZ,MAAiBrG,OAAOuH,MAAM,CAAClB,IAAImB,QAAQ;gBAC1EpB,YAAY,CAAC,EAAEC,GAAG,EAAoB;oBACpC,MAAM/H,QAAQ0B,OAAOuH,MAAM,CAAClB,IAAImB,QAAQ;oBACxC,OAAOlJ,sBACL,MAAC1C;wBAAM6L,WAAU;wBAAMC,SAAS;wBAAGhB,IAAI;4BAAEiB,YAAY;wBAAS;;0CAC5D,KAAChK;gCAAYW,OAAOA;gCAAOsJ,MAAM;;0CACjC,KAAC5L;gCAAW6H,SAAQ;gCAAQkD,MAAM;0CAC/BzI;;;uCAIL,KAACtC;wBAAW6H,SAAQ;wBAAQiD,OAAM;kCAC/B;;gBAGP;YACF;YACA;gBACE5F,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVlI,MAAM;gBACN+I,aAAa,CAACC,QAAiBZ,MAC7B,OAAOA,IAAIwB,iBAAiB,KAAK,WAAW,IAAI1H,KAAKkG,IAAIwB,iBAAiB,IAAI;gBAChFzB,YAAY,CAAC,EAAEC,GAAG,EAAoB,GACpC,OAAOA,IAAIwB,iBAAiB,KAAK,WAC7B,IAAI1H,KAAKkG,IAAIwB,iBAAiB,EAAEC,kBAAkB,KAClD;YACR;YACA;gBACE5G,OAAO;gBACP+E,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAAoB;wBAG3BrJ;yCAFT,KAACvB;wBACCmM,MAAK;wBACLtJ,KAAK,GAAEtB,iCAAAA,kBAAkB,CAACqJ,IAAItI,MAAM,CAAC,YAA9Bf,iCAAkCqJ,IAAItI,MAAM;wBACnD+I,OAAOT,IAAItI,MAAM,KAAK,QAAQ,YAAYsI,IAAItI,MAAM,KAAK,SAAS,YAAY;wBAC9E8F,SAASwC,IAAItI,MAAM,KAAK,SAAS,aAAa;;;YAGpD;YACA,kEAAkE;YAClE9D,mBAAmBiG;YACnB,iFAAiF;YACjF6H,eAAe,QAAQ,QAAQzH,SAAS7B,QAAQ;YAChDsJ,eAAe,cAAc,eAAexH,eAAe9B,QAAQ;YACnE,wDAAwD;eACrDtB,mBAAmBkD,WAAWzB,MAAM;SACxC,EACD;QAACY;QAAeQ;QAAQK,WAAWzB,MAAM;QAAEsB;QAAOI,SAAS7B,QAAQ;QAAE8B,eAAe9B,QAAQ;KAAC;IAE/F,wCAAwC,GACxC,MAAMuJ,YAAYxF,MAAMyF,IAAI;IAC5B;;;;GAIC,GACD;;;GAGC,GACD,MAAMC,gBAAgB9L,QAA4C;QAChE,MAAM+L,QAAQ,CAACjH,OAAekH,QAC5BjH,YACGsF,MAAM,CAAC,CAACxF,SAAWA,OAAOC,KAAK,KAAKA,SAASD,OAAOoH,EAAE,KAAK,WAC3DC,OAAO,CAAC,CAACrH,SAAWA,OAAO5C,KAAK,CAACkK,KAAK,CAAC,MACvCnK,GAAG,CAAC,CAACC,QAAUA,MAAM2D,IAAI,IACzByE,MAAM,CAAC,CAACpI,QAAUA,SAAS,CAAC+J,MAAMtG,IAAI,CAAC,CAAC0G,SAAWA,OAAOnK,KAAK,KAAKA,QACpED,GAAG,CAAC,CAACC,QAAW,CAAA;oBAAEA;oBAAOC,OAAOD;gBAAM,CAAA;QAC3C,MAAMoK,QAAQjK,sBAAsB8B,SAAS7B,QAAQ,EAAE;QACvD,MAAMiK,UAAUlK,sBAAsB+B,eAAe9B,QAAQ,EAAE;QAC/D,OAAO;YACLV,QAAQI;YACRF,MAAM;mBAAIwK;mBAAUN,MAAM,QAAQM;aAAO;YACzCvK,YAAY;mBAAIwK;mBAAYP,MAAM,cAAcO;aAAS;QAC3D;IACF,GAAG;QAACvH;QAAab,SAAS7B,QAAQ;QAAE8B,eAAe9B,QAAQ;KAAC;IAC5D,MAAMkK,gBAAgBvM,QACpB,IAAMlB,sBAAsB8K,SAASnI,yBAAyBqK,eAAepK,2BAC7E;QAACkI;QAASkC;KAAc;IAE1B,MAAMU,OAAOlP,eAAekH,OAAO+H,eAAepK;IAClD;;;;;;;GAOC,GACD,MAAMsK,YAAYzM,QAAQ;QACxB,MAAM0M,OAAO,CAACC,MAAmBzK,QAC/B0K,OAAO1K,gBAAAA,QAAS,IAAI0D,IAAI,KAAKpJ,gBAAgBmQ,MAAMzK,SAAS;QAC9D,OAAO;YACLgG,SAAS,CAAC+B;;oBACO7G;gBAAf,MAAMyJ,kBAASzJ,8BAAAA,cAAc0H,YAAY,CAACb,IAAIrH,UAAU,sBAAzCQ,4BAA4CyJ,MAAM,mBAAI,EAAE;gBACvE,MAAMC,KAAKD,OAAOE,SAAS,CAAC,CAAC5E,QAAUA,MAAM6E,EAAE,KAAK/C,IAAI/B,OAAO;gBAC/D,OAAO4E,OAAO,CAAC,IAAI,OAAOA;YAC5B;YACAjL,MAAM,CAACoI,MAAiByC,KAAKxI,SAAS7B,QAAQ,EAAE4H,IAAIpI,IAAI;YACxDC,YAAY,CAACmI,MAAiByC,KAAKvI,eAAe9B,QAAQ,EAAE4H,IAAInI,UAAU;QAC5E;IACF,GAAG;QAACsB;QAAec,SAAS7B,QAAQ;QAAE8B,eAAe9B,QAAQ;KAAC;IAC9D,MAAM4K,aAAaxO,iBAA0B;QAC3C+F;QACA0I,OAAOjP;QACPkP,aAAalP,eAAe,CAAC,EAAE;QAC/BgI,OAAOC;QACPkH,SAAShH,MAAMkD,IAAI,CAAC8D,OAAO;QAC3BvB,MAAMD;QACNhC,SAAS4C,KAAK5C,OAAO;QACrB6C;IACF;IACA,MAAMY,aAAatO,kBAAkB;QACnCyH,SAASzB;QACTuI,UAAU9I,MAAM+I,UAAU;QAC1BC,cAAc;YAAC;YAAU;YAAQ;SAAa;QAC9CC,QAAQ3P;QACR2I,QAAQ;YAAEiH,OAAOnI;YAAa+H,UAAU9H;QAAe;IACzD;IACA,qEAAqE;IACrE,MAAMmI,UAAU3N,QACd,IACEpB,iBAAiBwH,MAAMkD,IAAI,EAAErD,OAAO;YAClC2H,QAAQnM;YACRoM,SAASnM;YACToM,SAAShC;QACX,IACF;QAAC1F,MAAMkD,IAAI;QAAErD;QAAO6F;KAAc;IAGpC,MAAMiC,QAAQlL,MAAMmL,KAAK,IAAI,CAACnL,MAAMQ,KAAK;IACzC,MAAM4K,kBACJ,CAACF,SAAU3K,CAAAA,cAAczB,MAAM,KAAK,aAAayB,cAAc8K,OAAO,IAAK,CAAC1K,YAAYJ,cAAczB,MAAM,KAAK,OAAO;IAE1H,qBACE;;0BACE,KAAC3E;gBACCmR,QAAQ;gBACRC,MAAMzR,eAAe,SAAS;oBAAE0R,QAAQ;gBAA2B;gBACnEC,cAAc;gBACdC,cAAc;gBACdC,aAAa;oBACX,8DAA8D;oBAC9DC,sBACE,MAAClR;;0CACC,KAAC4D;gCAAgBuN,UAAUtN;gCAAoBuN,MAAK;gCAAQ7L,QAAQA;gCAAQ8L,aAAY;;0CACxF,KAAC1N;gCAAgBwN,UAAUtN;gCAAoB0B,QAAQA;gCAAQuH,QAAQb;;0CACvE,KAACpK;gCACCoM,MAAK;gCACLqD,yBAAW,KAAC5R;oCAAQ6R,MAAMlS,cAAckS,IAAI;oCAAEtD,MAAM;;gCACpDuD,UAAU,CAAClM,MAAMQ,KAAK,IAAID,cAAczB,MAAM,KAAK;gCACnDqN,SAAS,IAAM7F,qBAAqB;0CAEnC;;0CAEH,KAAC/J;gCACCoM,MAAK;gCACL/D,SAAQ;gCACRoH,yBAAW,KAAC5R;oCAAQ6R,MAAMjS,QAAQiS,IAAI;oCAAEtD,MAAM;;gCAC9CuD,UAAU,CAACvL,YAAY,CAACX,MAAMQ,KAAK;gCACnC2L,SAAS,IAAM/F,YAAY;0CAE1B;;;;gBAIT;0BAEA,cAAA,MAACzJ;oBAAM8L,SAAS;;sCACd,MAAC9L;4BACCyP,UAAU;4BACV5D,WAAU;4BACVC,SAAS;4BACThB,IAAI;gCAAEiB,YAAY;gCAAY2D,UAAU;gCAAQC,QAAQ;4BAAE;;8CAG1D,KAACC;oCAAOlN,OAAM;oCAAaD,OAAOkD,cAAc2B,QAAQuI,SAAS,CAACC,cAAc,KAAK;;8CACrF,KAACF;oCACClN,OAAM;oCACND,OAAOkD,cAActE,uBAAuBiG,QAAQyI,eAAe,IAAI;;8CAEzE,KAACH;oCACClN,OAAM;oCACND,OAAOkD,cAActE,uBAAuBiG,QAAQ0I,kBAAkB,IAAI;;8CAE5E,KAAChQ;oCAAM8K,IAAI;wCAAER,MAAM;oCAAE;;gCAEpBxG,gBAAgBoG,MAAM,GAAG,kBACxB,KAACjK;oCACCgQ,MAAM;oCACNjE,MAAK;oCACLtJ,OAAM;oCACND,KAAK,WAAEuB,4BAAAA,SAAUG,GAAG,oBAAI;oCACxB2J,UAAU,CAACoC,QAAUnM,cAAcmM,MAAMC,MAAM,CAAC1N,KAAK;oCACrDqI,IAAI;wCAAEP,UAAU;oCAAI;oCACpB6F,WAAW;wCAAEH,QAAQ;4CAAE,cAAc;wCAAW;oCAAE;8CAEjDnM,gBAAgBtB,GAAG,CAAC,CAAC0B,sBACpB,KAACnE;4CAAyB0C,OAAOyB,MAAMC,GAAG;sDACvCD,MAAM2E,IAAI;2CADE3E,MAAMC,GAAG;qCAK1B;8CACJ,MAAChE;oCACCkQ,SAAS;oCACTrE,MAAK;oCACLd,OAAM;oCACNzI,OAAOmC;oCACPkJ,UAAU,CAACwC,QAAQ/H;wCACjB,IAAIA,MAAM1D,QAAQ0D;oCACpB;oCACAgI,cAAW;;sDAEX,MAACrQ;4CAAauC,OAAM;4CAAQ8N,cAAW;;8DACrC,KAAC9S;oDAAQ6R,MAAM/R,qBAAqB+R,IAAI;oDAAEtD,MAAM;;8DAChD,KAAC5L;oDAAW6H,SAAQ;oDAAS6C,IAAI;wDAAE0F,IAAI;oDAAI;8DACxC;;;;sDAGL,MAACtQ;4CAAauC,OAAM;4CAAQ8N,cAAW;;8DACrC,KAAC9S;oDAAQ6R,MAAMhS,cAAcgS,IAAI;oDAAEtD,MAAM;;8DACzC,KAAC5L;oDAAW6H,SAAQ;oDAAS6C,IAAI;wDAAE0F,IAAI;oDAAI;8DACxC;;;;;;;;wBAMRjC,sBACC,KAAC5O;4BAAM8Q,UAAS;sCACb;6BAED7M,cAAczB,MAAM,KAAK,wBAC3B,KAACxC;4BAAM8Q,UAAS;sCACb;6BAEDhC,gCACF,MAACzO;4BAAM6L,WAAU;4BAAMC,SAAS;4BAAGhB,IAAI;gCAAEiB,YAAY;4BAAS;;8CAC5D,KAACjM;oCAAiBkM,MAAM;;8CACxB,KAAC5L;oCAAW6H,SAAQ;oCAAQiD,OAAM;8CAC/BtH,cAAc8K,OAAO,GAAG,oCAAoC;;;6BAG/D9J,SAAS,WAAWZ,yBACtB;;gCACGyB,KAAKtD,MAAM,KAAK,aAAasD,KAAK8B,IAAI,CAAC2C,MAAM,KAAK,kBACjD,KAACzK;oCACCiR,OAAO;oCACPhO,OAAO;oCACPiO,aAAa;oCACb1B,sBACE,KAACrP;wCACCoM,MAAK;wCACL/D,SAAQ;wCACRoH,yBAAW,KAAC5R;4CAAQ6R,MAAMjS,QAAQiS,IAAI;4CAAEtD,MAAM;;wCAC9CuD,UAAU,CAACvL,YAAY,CAACX,MAAMQ,KAAK;wCACnC2L,SAAS,IAAM/F,YAAY;kDAE1B;;qCAIL;8CACJ,KAACjI;oCACCwC,UAAUA;oCACV8C,OAAOrB,KAAK8B,IAAI;oCAChB1B,KAAKf,kBAAkBe,IAAI1D,MAAM,KAAK,YAAY0D,IAAI0B,IAAI,GAAG;oCAC7DzB,MAAMhB,kBAAkBgB,KAAK3D,MAAM,KAAK,YAAY2D,KAAKyB,IAAI,GAAG;oCAChEzC,gBAAgBA;oCAChB8L,gBAAgB,IAAM7L,kBAAkB,CAACgD,UAAY,CAACA;oCACtD8I,UAAUzM,OAAOuH,MAAM;oCACvBnE,QAAQA;oCACRsJ,QAAQxH;oCACRyH,QAAQtI;oCACRuI,OAAOlI;oCACPmI,QAAQ/H;oCACR5E,OAAOA;;;2CAIX,MAACtE;4BAAM8L,SAAS;;8CAMd,MAAC9N;oCAAe0E,OAAM;;sDACpB,KAAClD;4CAAgB0R,YAAYlM;4CAAOmM,UAAS;;sDAC7C,KAAC9R;4CACC+O,QAAQnM;4CACRoM,SAASnM;4CACT8E,SAASzB;4CACTuI,UAAU9I,MAAM+I,UAAU;4CAC1BO,SAAShC;4CACT8E,aAAa;;;;8CAGjB,KAACxT;oCACCuQ,SAASA;oCACTkD,SAAS;2CAAIzK,MAAMkD,IAAI,CAACuH,OAAO;2CAAK5D,WAAW4D,OAAO;qCAAC;;8CAEvD;;sDACE,KAACxP;4CACCyB,QAAQA;4CACRD,OAAOA,MAAMA,KAAK;4CAClBgJ,MAAMD;4CACNkF,UAAU1H;4CACV2H,kBAAkB1H;4CAClByB,cAAc1H,cAAc0H,YAAY;4CACxClH,QAAQA;4CACRC,KAAKA;;sDAEP,KAACnG;4CAAuBuE,OAAOuK,KAAKwE,WAAW;sDAC7C,cAAA,KAAC7T;gDACC0O,MAAMoB,WAAWpB,IAAI;gDACrBoB,YAAYA;gDACZrD,SAAS4C,KAAK5C,OAAO;gDACrBqH,OAAOxT;gDACPyT,YAAY;oDAAEJ,UAAU1H;oDAAakE,UAAUjE;gDAAe;gDAC9DiH,QAAQ,CAACa,KAAKlH,MAAQnB,SAASmB;gDAC/B,uDAAuD;gDACvDmH,YAAW;gDACXC,aAAahE,WAAWgE,WAAW;gDACnCC,qBAAqBjE,WAAWiE,mBAAmB;gDACnDC,WAAW;gDACXC,SAASpL,MAAMzE,MAAM,KAAK;gDAC1B,yDAAyD;gDACzD,8CAA8C;gDAC9C8P,aACEhM,YACI,CAAC,gBAAgB,EAAEI,UAAUD,IAAI,GAAG,CAAC,CAAC,GACtClB,iBAAiB,QACf,iBACA,CAAC,GAAG,EAAEA,aAAa,MAAM,CAAC;gDAElCgN,mBACEhN,iBAAiB,SAAS,CAACe,YACvB,4EACAkM;gDAENC,cACElN,iBAAiB,SAAS,CAACe,0BACzB,KAACrG;oDACCoM,MAAK;oDACL/D,SAAQ;oDACRoH,yBAAW,KAAC5R;wDAAQ6R,MAAMjS,QAAQiS,IAAI;wDAAEtD,MAAM;;oDAC9CuD,UAAU,CAACvL,YAAY,CAACX,MAAMQ,KAAK;oDACnC2L,SAAS,IAAM/F,YAAY;8DAE1B;qDAED0I;gDAEN,2DAA2D;gDAC3D,2DAA2D;gDAC3DE,uBAAuBrF,KAAKqF,qBAAqB;gDACjDC,+BAA+BtF,KAAKsF,6BAA6B;gDACjEC,UAAU;;;sDAGd,KAAC7U;4CACCqM,MAAMnD,MAAMmD,IAAI;4CAChByI,UAAU5L,MAAM4L,QAAQ;4CACxBC,UAAU7L,MAAMyF,IAAI,CAACnC,MAAM;4CAC3BwI,SAAS9L,MAAM8L,OAAO;4CACtBC,cAAc/L,MAAMgM,OAAO;4CAC3BC,kBAAkBjM,MAAMkM,WAAW;;;;;;;;;0BAOjD,KAACrR;gBACCgE,MAAM+D;gBACNuJ,SAAS,IAAMtJ,YAAY;gBAC3BnG,QAAQA;gBACRC,KAAKA;gBACLyP,WAAWlP;gBACXmP,iBAAiBjP;;0BAEnB,KAAChC;gBACCyD,MAAMiE;gBACNqJ,SAAS,IAAMpJ,qBAAqB;gBACpC9F,KAAK,GAAER,eAAAA,MAAMQ,KAAK,YAAXR,eAAe;gBACtB,gEAAgE;gBAChE,8DAA8D;gBAC9D,gEAAgE;gBAChE,yBAAyB;gBACzBC,QAAQD,MAAM6P,YAAY;gBAC1BF,WAAWpP,cAAcoP,SAAS;gBAClCG,WAAWvP,cAAcuP,SAAS;gBAClCC,YAAYxP,cAAczB,MAAM,KAAK;gBACrCkR,iBAAiBhQ,MAAMqC,SAAS;gBAChC4N,cAAcjQ,MAAMkQ,KAAK,KAAK,SAASlQ,MAAMiQ,YAAY,GAAGrQ;;0BAE9D,KAACnB;gBACC2D,MAAM4B,QAAQ2B;gBACdwK,SAAS,WAAExK,0BAAAA,OAAQiC,KAAK,oBAAI;gBAC5B8H,SAAS,IAAM9J,UAAU;gBACzBwK,WAAWtK;;;;AAInB;AACAjG,aAAawQ,WAAW,GAAG;AAE3B,mFAAmF,GACnF,SAASvH,eAAe7G,KAA4B,EAAE+E,UAAkB,EAAExH,QAAqB;IAC7F,OAAO;QACLyC;QACA+E;QACAC,MAAM;QACNC,UAAU;QACVa,aAAa,CAACC,QAAiBZ;gBAAwBA;mBAAP2C,QAAO3C,aAAAA,GAAG,CAACnF,MAAM,YAAVmF,aAAc;;QACrEkJ,gBAAgB,CAACC,GAAWC,IAC1B7W,gBAAgB6F,UAAU+Q,KAAK5W,gBAAgB6F,UAAUgR,MAAMD,EAAEE,aAAa,CAACD;IACnF;AACF;AAEA,SAASjE,OAAOzM,KAAuC;IACrD,qBACE,MAACnD;QAAM8K,IAAI;YAAEE,YAAY;QAAI;;0BAC3B,KAAC5K;gBAAW6H,SAAQ;gBAAUiD,OAAM;0BACjC/H,MAAMT,KAAK;;0BAEd,KAACtC;gBAAW6H,SAAQ;gBAAK8L,WAAU;0BAChC5Q,MAAMV,KAAK;;;;AAIpB;AACAmN,OAAO8D,WAAW,GAAG;AAErB,eAAexQ,aAAY"}
@@ -6,16 +6,23 @@ export interface LogActivityDialogProps {
6
6
  /** Where the activity is written, from `useActivityScope`. */
7
7
  scope: ActivityScope;
8
8
  /**
9
- * The record the activity is filed against, FIXED by the caller. A
10
- * contact's page logs against that contact; the dialog offers no picker,
11
- * because an activity logged from a record page is about that record and
12
- * a picker would be a way to file it somewhere the reader is not looking.
9
+ * The record the dialog was opened on, which prefills **Name** and
10
+ * **Related To** (AGL-4123). A lead's own log offers neither: the lead is
11
+ * the entry's only link until it converts.
13
12
  */
14
13
  link: ActivityRecordLink;
14
+ /**
15
+ * The deal the dialog was opened on, as its page read it: **Name** opens on
16
+ * its Primary and its contact roles lead the list.
17
+ */
18
+ deal?: {
19
+ contactId?: unknown;
20
+ contactRoles?: unknown;
21
+ } | null;
15
22
  /**
16
23
  * When set, the dialog EDITS this activity rather than logging a new one.
17
- * The link, the author and the scope stay what they were; only what was
18
- * said, when, and how it went can change.
24
+ * The author and the scope stay what they were; what was said, when, how
25
+ * it went, and who and what it is about can change.
19
26
  */
20
27
  activity?: CrmActivityRow | null;
21
28
  /**
@@ -30,6 +37,15 @@ export interface LogActivityDialogProps {
30
37
  * Log a call, an email, a meeting, a note or something else against a
31
38
  * record (AGL-2600) — or correct one already logged.
32
39
  *
40
+ * ## Who, what and which account
41
+ *
42
+ * **Name** and **Related To** are Salesforce's Who and What (AGL-4123), and
43
+ * the company an entry rolls up to is stamped from them when it is saved —
44
+ * `model/crm-activity-link.ts` has the rule, shared with every other writer.
45
+ * The fields open on the record the dialog was opened from and the reader may
46
+ * change them: an entry named for a deal shows on the deal, on the person's
47
+ * timeline and on the company, whichever page it was logged from.
48
+ *
33
49
  * ## What is written
34
50
  *
35
51
  * A new activity carries the whole `CrmScoped` stamp: `visibleTo` from
@@ -42,9 +58,11 @@ export interface LogActivityDialogProps {
42
58
  * An edit rewrites only the fields the dialog shows. `visibleTo` is never
43
59
  * touched — the rules refuse a scoped member changing it, and widening a
44
60
  * record is an act for an org-wide member on the record itself — and neither
45
- * is the author or the link. A kind that stops taking an outcome takes the
46
- * outcome and the duration off the document rather than leaving a stale
47
- * "left a voicemail" on what is now a note.
61
+ * is the author. The links are rewritten only when **Name** or **Related To**
62
+ * changed, and then the company is worked out again, so an edit that corrects
63
+ * a typo cannot move an entry off a timeline it was filed on. A kind that
64
+ * stops taking an outcome takes the outcome and the duration off the document
65
+ * rather than leaving a stale "left a voicemail" on what is now a note.
48
66
  *
49
67
  * ## Who may write
50
68
  *
@@ -21,8 +21,11 @@ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
21
21
  import { useUser, useUserName } from "@aglyn/tenant-feature-instance";
22
22
  import { Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField } from "@mui/material";
23
23
  import { addDoc, collection, deleteField, doc, getCountFromServer, query, updateDoc, where } from "firebase/firestore";
24
- import { useCallback, useEffect, useState } from "react";
24
+ import { useCallback, useEffect, useRef, useState } from "react";
25
+ import { crmActivityClientReads } from "../hooks/crm-activity-client-reads.js";
25
26
  import { crmVisibleToClause } from "../hooks/use-crm-scope.js";
27
+ import { crmActivityLinkFromPicks, crmActivityPicksEqual, crmActivityPicksFrom, resolveCrmActivityCompany } from "../model/crm-activity-link.js";
28
+ import { ActivityLinkFields } from "./activity-link-fields.js";
26
29
  import { CrmSitePicker } from "./crm-site-picker.js";
27
30
  /** The longest body a single activity stores — a call summary, not a document. */ const BODY_MAX = 4000;
28
31
  const OUTCOME_MAX = 200;
@@ -43,14 +46,15 @@ const DURATION_MAX_MINUTES = 24 * 60;
43
46
  return Number.isFinite(ms) ? ms : null;
44
47
  }
45
48
  /** The kind a new activity opens on when the caller names none. */ const DEFAULT_KIND = 'call';
46
- const freshDraft = (kind = DEFAULT_KIND)=>({
49
+ const freshDraft = (kind = DEFAULT_KIND, picks = crmActivityPicksFrom({}))=>({
47
50
  kind,
48
51
  body: '',
49
52
  at: toLocalInput(Date.now()),
50
53
  outcome: '',
51
54
  durationMinutes: '',
52
55
  // Most logged calls and emails are the team's own.
53
- direction: 'outbound'
56
+ direction: 'outbound',
57
+ picks
54
58
  });
55
59
  const draftFrom = (activity)=>{
56
60
  var _activity_body, _activity_outcome, _Aglyn_crmActivityDirection;
@@ -60,13 +64,23 @@ const draftFrom = (activity)=>{
60
64
  at: toLocalInput(activity.atMs),
61
65
  outcome: (_activity_outcome = activity.outcome) != null ? _activity_outcome : '',
62
66
  durationMinutes: typeof activity.durationMinutes === 'number' ? String(activity.durationMinutes) : '',
63
- direction: (_Aglyn_crmActivityDirection = Aglyn.crmActivityDirection(activity.kind, activity.direction)) != null ? _Aglyn_crmActivityDirection : ''
67
+ direction: (_Aglyn_crmActivityDirection = Aglyn.crmActivityDirection(activity.kind, activity.direction)) != null ? _Aglyn_crmActivityDirection : '',
68
+ picks: crmActivityPicksFrom(activity)
64
69
  };
65
70
  };
66
71
  /**
67
72
  * Log a call, an email, a meeting, a note or something else against a
68
73
  * record (AGL-2600) — or correct one already logged.
69
74
  *
75
+ * ## Who, what and which account
76
+ *
77
+ * **Name** and **Related To** are Salesforce's Who and What (AGL-4123), and
78
+ * the company an entry rolls up to is stamped from them when it is saved —
79
+ * `model/crm-activity-link.ts` has the rule, shared with every other writer.
80
+ * The fields open on the record the dialog was opened from and the reader may
81
+ * change them: an entry named for a deal shows on the deal, on the person's
82
+ * timeline and on the company, whichever page it was logged from.
83
+ *
70
84
  * ## What is written
71
85
  *
72
86
  * A new activity carries the whole `CrmScoped` stamp: `visibleTo` from
@@ -79,9 +93,11 @@ const draftFrom = (activity)=>{
79
93
  * An edit rewrites only the fields the dialog shows. `visibleTo` is never
80
94
  * touched — the rules refuse a scoped member changing it, and widening a
81
95
  * record is an act for an org-wide member on the record itself — and neither
82
- * is the author or the link. A kind that stops taking an outcome takes the
83
- * outcome and the duration off the document rather than leaving a stale
84
- * "left a voicemail" on what is now a note.
96
+ * is the author. The links are rewritten only when **Name** or **Related To**
97
+ * changed, and then the company is worked out again, so an edit that corrects
98
+ * a typo cannot move an entry off a timeline it was filed on. A kind that
99
+ * stops taking an outcome takes the outcome and the duration off the document
100
+ * rather than leaving a stale "left a voicemail" on what is now a note.
85
101
  *
86
102
  * ## Who may write
87
103
  *
@@ -91,8 +107,9 @@ const draftFrom = (activity)=>{
91
107
  * so what reaches here has already passed the console's verdict.
92
108
  */ export function LogActivityDialog(props) {
93
109
  var _Aglyn_CRM_ACTIVITY_DIRECTIONS_draft_kind;
94
- const { open, onClose, scope, link, activity, kind: presetKind } = props;
95
- const { firestore, dataScope, hostId, mountedHostId, readTokens, writeTokens } = scope;
110
+ const { open, onClose, scope, link, deal, activity, kind: presetKind } = props;
111
+ const { firestore, dataScope, hostId, mountedHostId, readTokens, writeTokens, needsSite } = scope;
112
+ const { consentGroup, org } = scope;
96
113
  const { data: user } = useUser();
97
114
  const authorName = useUserName();
98
115
  const { enqueueSnackbar } = useSnackbar();
@@ -101,9 +118,27 @@ const draftFrom = (activity)=>{
101
118
  /*
102
119
  * Seeded on OPEN, not on mount: the dialog stays mounted between uses so
103
120
  * its state survives a close, and "log another call" must start from now
104
- * rather than from the last call's timestamp.
105
- */ useEffect(()=>{
106
- if (open) setDraft(activity ? draftFrom(activity) : freshDraft(presetKind));
121
+ * rather than from the last call's timestamp. The record is read through a
122
+ * ref, because a deal is a live document and a colleague's save to it must
123
+ * not wipe what the reader has typed so far.
124
+ */ const openedOn = useRef({
125
+ link,
126
+ deal
127
+ });
128
+ useEffect(()=>{
129
+ openedOn.current = {
130
+ link,
131
+ deal
132
+ };
133
+ }, [
134
+ link,
135
+ deal
136
+ ]);
137
+ useEffect(()=>{
138
+ if (!open) return;
139
+ const seed = openedOn.current;
140
+ const primary = seed.deal ? Aglyn.dealPrimaryContactId(Aglyn.dealContactRolesOf(seed.deal)) : null;
141
+ setDraft(activity ? draftFrom(activity) : freshDraft(presetKind, crmActivityPicksFrom(seed.link, primary)));
107
142
  }, [
108
143
  open,
109
144
  activity,
@@ -114,21 +149,36 @@ const draftFrom = (activity)=>{
114
149
  const direction = Aglyn.crmActivityDirection(draft.kind, draft.direction);
115
150
  const atMs = fromLocalInput(draft.at);
116
151
  const body = draft.body.trim();
152
+ /*
153
+ * A lead's own log shows neither field: a lead is not a contact **Name**
154
+ * could hold, and in Salesforce's model too it names no What. A contact
155
+ * beside the lead means it converted, and the entry reads like any other.
156
+ */ const filedOn = activity != null ? activity : link;
157
+ const leadOnly = Boolean(filedOn.leadId) && !filedOn.contactId;
158
+ const { picks } = draft;
159
+ // An activity attached to nothing is a sentence nobody can find (AGL-2606).
160
+ const linked = leadOnly || Boolean(picks.contactId || picks.related);
117
161
  const duration = draft.durationMinutes.trim() ? Number(draft.durationMinutes) : null;
118
162
  const durationValid = duration === null || Number.isInteger(duration) && duration >= 0 && duration <= DURATION_MAX_MINUTES;
119
- const canSave = Boolean(dataScope) && Boolean(user == null ? void 0 : user.uid) && // A NEW activity is stamped with a site's scope and provenance; at the
120
- // organization level that is the picked site, and there is nothing to
121
- // write until there is one (AGL-2630). An edit never touches either.
122
- (Boolean(activity) || Boolean(hostId)) && body.length > 0 && atMs !== null && durationValid && !saving;
163
+ const canSave = Boolean(dataScope) && Boolean(user == null ? void 0 : user.uid) && // A NEW activity is stamped with a scope: the whole org where every site
164
+ // sees the CRM's records, else the picked site's — and in an org that
165
+ // keeps its sites apart there is nothing to write until one is picked
166
+ // (AGL-2630). An edit never touches either.
167
+ (Boolean(activity) || writeTokens.length > 0) && body.length > 0 && atMs !== null && durationValid && linked && !saving;
123
168
  const handleSave = useCallback(async ()=>{
124
169
  // Every guard the button reads, again — a callback can outlive the
125
170
  // render that disabled it.
126
- if (!dataScope || !(user == null ? void 0 : user.uid) || !body || atMs === null || !durationValid) {
171
+ if (!dataScope || !(user == null ? void 0 : user.uid) || !body || atMs === null || !durationValid || !linked) {
127
172
  return;
128
173
  }
129
- if (!activity && !hostId) return;
174
+ if (!activity && !writeTokens.length) return;
130
175
  setSaving(true);
131
176
  try {
177
+ const named = crmActivityLinkFromPicks(picks, filedOn.leadId);
178
+ const stamped = ()=>resolveCrmActivityCompany(named, {
179
+ groupId: consentGroup == null ? void 0 : consentGroup.groupId,
180
+ org
181
+ }, crmActivityClientReads(firestore, dataScope[1]));
132
182
  const said = {
133
183
  kind: draft.kind,
134
184
  body: body.slice(0, BODY_MAX),
@@ -137,7 +187,13 @@ const draftFrom = (activity)=>{
137
187
  const outcome = hasOutcome ? draft.outcome.trim().slice(0, OUTCOME_MAX) : '';
138
188
  const durationMinutes = hasOutcome && duration !== null ? duration : null;
139
189
  if (activity) {
140
- await updateDoc(doc(firestore, dataScope[0], dataScope[1], Aglyn.CRM_COLLECTIONS.activities, activity.$id), _extends({}, said, {
190
+ var _relinked_contactId, _relinked_dealId, _relinked_companyId;
191
+ const relinked = crmActivityPicksEqual(picks, crmActivityPicksFrom(activity)) ? null : await stamped();
192
+ await updateDoc(doc(firestore, dataScope[0], dataScope[1], Aglyn.CRM_COLLECTIONS.activities, activity.$id), _extends({}, said, relinked ? {
193
+ contactId: (_relinked_contactId = relinked.contactId) != null ? _relinked_contactId : deleteField(),
194
+ dealId: (_relinked_dealId = relinked.dealId) != null ? _relinked_dealId : deleteField(),
195
+ companyId: (_relinked_companyId = relinked.companyId) != null ? _relinked_companyId : deleteField()
196
+ } : {}, {
141
197
  // Cleared rather than left: a call turned into a note must not
142
198
  // keep the call's outcome, and Firestore refuses `undefined`.
143
199
  outcome: outcome ? outcome : deleteField(),
@@ -157,7 +213,7 @@ const draftFrom = (activity)=>{
157
213
  * Scoped to the tokens this viewer may list, as every activity
158
214
  * listener is, so the rules admit the aggregate for a scoped
159
215
  * member exactly as they admit the timeline.
160
- */ const lead = Aglyn.crmActivityCeilingLink(link);
216
+ */ const lead = Aglyn.crmActivityCeilingLink(named);
161
217
  if (lead) {
162
218
  const logged = (await getCountFromServer(query(collection(firestore, dataScope[0], dataScope[1], Aglyn.CRM_COLLECTIONS.activities), ...crmVisibleToClause(readTokens), where(lead.field, '==', lead.id)))).data().count;
163
219
  if (!Aglyn.crmActivityLogHasRoom(logged)) {
@@ -168,21 +224,14 @@ const draftFrom = (activity)=>{
168
224
  return;
169
225
  }
170
226
  }
227
+ const links = await stamped();
171
228
  await addDoc(collection(firestore, dataScope[0], dataScope[1], Aglyn.CRM_COLLECTIONS.activities), _extends({}, said, outcome ? {
172
229
  outcome
173
230
  } : {}, durationMinutes !== null ? {
174
231
  durationMinutes
175
232
  } : {}, direction ? {
176
233
  direction
177
- } : {}, link.contactId ? {
178
- contactId: link.contactId
179
- } : {}, link.companyId ? {
180
- companyId: link.companyId
181
- } : {}, link.dealId ? {
182
- dealId: link.dealId
183
- } : {}, link.leadId ? {
184
- leadId: link.leadId
185
- } : {}, {
234
+ } : {}, links, {
186
235
  visibleTo: writeTokens,
187
236
  hostId,
188
237
  byUid: user.uid
@@ -221,7 +270,11 @@ const draftFrom = (activity)=>{
221
270
  activity,
222
271
  firestore,
223
272
  enqueueSnackbar,
224
- link,
273
+ linked,
274
+ picks,
275
+ filedOn.leadId,
276
+ consentGroup,
277
+ org,
225
278
  readTokens,
226
279
  writeTokens,
227
280
  hostId,
@@ -247,7 +300,8 @@ const draftFrom = (activity)=>{
247
300
  activity ? null : /*#__PURE__*/ _jsx(CrmSitePicker, {
248
301
  hostId: mountedHostId,
249
302
  disabled: saving,
250
- helperText: "The site this activity is logged from — it decides which of your sites may see it."
303
+ optional: needsSite === false,
304
+ helperText: needsSite === false ? undefined : 'The site this activity is logged from — it decides which of your sites may see it.'
251
305
  }),
252
306
  /*#__PURE__*/ _jsxs(Stack, {
253
307
  direction: "row",
@@ -380,7 +434,17 @@ const draftFrom = (activity)=>{
380
434
  }
381
435
  })
382
436
  ]
383
- }) : null
437
+ }) : null,
438
+ leadOnly ? null : /*#__PURE__*/ _jsx(ActivityLinkFields, {
439
+ scope: scope,
440
+ picks: picks,
441
+ onChange: (next)=>setDraft((prev)=>_extends({}, prev, {
442
+ picks: next
443
+ })),
444
+ link: link,
445
+ deal: deal,
446
+ disabled: saving
447
+ })
384
448
  ]
385
449
  }),
386
450
  /*#__PURE__*/ _jsxs(DialogActions, {