@aglyn/plugins-crm 1.0.0-beta.217 → 1.0.0-beta.219

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 (249) hide show
  1. package/package.json +14 -13
  2. package/src/index.d.ts +1 -0
  3. package/src/index.js.map +1 -1
  4. package/src/lib/components/companies-bulk-bar.js +12 -4
  5. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  6. package/src/lib/components/company-detail-page.js +11 -0
  7. package/src/lib/components/company-detail-page.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +12 -2
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/company-import-drawer.d.ts +1 -1
  11. package/src/lib/components/company-picker.js +10 -3
  12. package/src/lib/components/company-picker.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -4
  14. package/src/lib/components/contact-detail-page.js +50 -53
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-list-columns.d.ts +3 -0
  17. package/src/lib/components/contact-list-columns.js +21 -5
  18. package/src/lib/components/contact-list-columns.js.map +1 -1
  19. package/src/lib/components/contact-merge-dialog.js +2 -1
  20. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  21. package/src/lib/components/contact-timeline-card.js +4 -3
  22. package/src/lib/components/contact-timeline-card.js.map +1 -1
  23. package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
  24. package/src/lib/components/contacts-bulk-bar.js +24 -51
  25. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  26. package/src/lib/components/contacts-section.js +9 -3
  27. package/src/lib/components/contacts-section.js.map +1 -1
  28. package/src/lib/components/crm-export-all-button.d.ts +1 -1
  29. package/src/lib/components/crm-export-all-button.js.map +1 -1
  30. package/src/lib/components/crm-send-email-dialog.js +9 -6
  31. package/src/lib/components/crm-send-email-dialog.js.map +1 -1
  32. package/src/lib/components/csv-import-drawer.js +2 -2
  33. package/src/lib/components/csv-import-drawer.js.map +1 -1
  34. package/src/lib/components/deal-detail-page.js +18 -2
  35. package/src/lib/components/deal-detail-page.js.map +1 -1
  36. package/src/lib/components/deal-edit-drawer.js +10 -0
  37. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  38. package/src/lib/components/deals-bulk-bar.js +13 -5
  39. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +6 -0
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-detail-page.js +25 -5
  43. package/src/lib/components/lead-detail-page.js.map +1 -1
  44. package/src/lib/components/lead-properties-card.js +6 -0
  45. package/src/lib/components/lead-properties-card.js.map +1 -1
  46. package/src/lib/components/lead-surfaces-note.js +4 -5
  47. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  48. package/src/lib/components/lead-unqualify-dialog.js +6 -0
  49. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  50. package/src/lib/components/leads-bulk-bar.js +28 -3
  51. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  52. package/src/lib/components/leads-section.js +36 -7
  53. package/src/lib/components/leads-section.js.map +1 -1
  54. package/src/lib/components/org-lead-surfaces-note.js +6 -2
  55. package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
  56. package/src/lib/components/recipes-card.js +9 -4
  57. package/src/lib/components/recipes-card.js.map +1 -1
  58. package/src/lib/components/record-sharing-card.d.ts +57 -0
  59. package/src/lib/components/record-sharing-card.js +244 -0
  60. package/src/lib/components/record-sharing-card.js.map +1 -0
  61. package/src/lib/components/settings-section.js +7 -0
  62. package/src/lib/components/settings-section.js.map +1 -1
  63. package/src/lib/components/share-records-dialog.d.ts +28 -0
  64. package/src/lib/components/share-records-dialog.js +184 -0
  65. package/src/lib/components/share-records-dialog.js.map +1 -0
  66. package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
  67. package/src/lib/components/sharing-rule-drawer.js +374 -0
  68. package/src/lib/components/sharing-rule-drawer.js.map +1 -0
  69. package/src/lib/components/sharing-rules-card.d.ts +24 -0
  70. package/src/lib/components/sharing-rules-card.js +287 -0
  71. package/src/lib/components/sharing-rules-card.js.map +1 -0
  72. package/src/lib/components/use-crm-api.d.ts +1 -1
  73. package/src/lib/components/use-crm-api.js.map +1 -1
  74. package/src/lib/components/use-site-route-context.d.ts +25 -0
  75. package/src/lib/components/use-site-route-context.js +58 -0
  76. package/src/lib/components/use-site-route-context.js.map +1 -0
  77. package/src/lib/constants/api-routes.d.ts +31 -0
  78. package/src/lib/constants/api-routes.js +27 -1
  79. package/src/lib/constants/api-routes.js.map +1 -1
  80. package/src/lib/declarations.console-server.d.ts +37 -0
  81. package/src/lib/declarations.console-server.js +103 -0
  82. package/src/lib/declarations.console-server.js.map +1 -0
  83. package/src/lib/declarations.server.d.ts +6 -0
  84. package/src/lib/declarations.server.js +33 -0
  85. package/src/lib/declarations.server.js.map +1 -1
  86. package/src/lib/hooks/use-contact-remove.d.ts +35 -0
  87. package/src/lib/hooks/use-contact-remove.js +77 -0
  88. package/src/lib/hooks/use-contact-remove.js.map +1 -0
  89. package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
  90. package/src/lib/hooks/use-crm-sharing.js +109 -0
  91. package/src/lib/hooks/use-crm-sharing.js.map +1 -0
  92. package/src/lib/model/companies-csv.d.ts +1 -1
  93. package/src/lib/model/companies-csv.js +2 -2
  94. package/src/lib/model/companies-csv.js.map +1 -1
  95. package/src/lib/model/contact-holder.d.ts +68 -0
  96. package/src/lib/model/contact-holder.js +129 -0
  97. package/src/lib/model/contact-holder.js.map +1 -0
  98. package/src/lib/model/contact-interaction-links.d.ts +46 -0
  99. package/src/lib/model/contact-interaction-links.js +65 -0
  100. package/src/lib/model/contact-interaction-links.js.map +1 -0
  101. package/src/lib/model/contact-merge.d.ts +136 -0
  102. package/src/lib/model/contact-merge.js +412 -0
  103. package/src/lib/model/contact-merge.js.map +1 -0
  104. package/src/lib/model/contact-record.d.ts +7 -1
  105. package/src/lib/model/contact-record.js +6 -5
  106. package/src/lib/model/contact-record.js.map +1 -1
  107. package/src/lib/model/contact-remove.d.ts +48 -0
  108. package/src/lib/model/contact-remove.js +22 -0
  109. package/src/lib/model/contact-remove.js.map +1 -0
  110. package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
  111. package/src/lib/model/contacts-bulk-writes.js +2 -82
  112. package/src/lib/model/contacts-bulk-writes.js.map +1 -1
  113. package/src/lib/model/contacts-csv.d.ts +2 -2
  114. package/src/lib/model/contacts-csv.js +2 -2
  115. package/src/lib/model/contacts-csv.js.map +1 -1
  116. package/src/lib/model/crm-csv.d.ts +239 -0
  117. package/src/lib/model/crm-csv.js +467 -0
  118. package/src/lib/model/crm-csv.js.map +1 -0
  119. package/src/lib/model/crm-digest.d.ts +185 -0
  120. package/src/lib/model/crm-digest.js +272 -0
  121. package/src/lib/model/crm-digest.js.map +1 -0
  122. package/src/lib/model/crm-import.d.ts +7 -1
  123. package/src/lib/model/crm-import.js +2 -1
  124. package/src/lib/model/crm-import.js.map +1 -1
  125. package/src/lib/model/crm-record-routes.d.ts +5 -0
  126. package/src/lib/model/crm-record-routes.js +7 -0
  127. package/src/lib/model/crm-record-routes.js.map +1 -1
  128. package/src/lib/model/crm-routes.d.ts +6 -5
  129. package/src/lib/model/crm-routes.js +7 -7
  130. package/src/lib/model/crm-routes.js.map +1 -1
  131. package/src/lib/model/crm-search-sources.d.ts +15 -0
  132. package/src/lib/model/crm-search-sources.js +162 -0
  133. package/src/lib/model/crm-search-sources.js.map +1 -0
  134. package/src/lib/model/crm-sharing-api.d.ts +32 -0
  135. package/src/lib/model/crm-sharing-api.js +59 -0
  136. package/src/lib/model/crm-sharing-api.js.map +1 -0
  137. package/src/lib/model/crm-sharing.d.ts +333 -0
  138. package/src/lib/model/crm-sharing.js +603 -0
  139. package/src/lib/model/crm-sharing.js.map +1 -0
  140. package/src/lib/model/deals-csv.d.ts +1 -1
  141. package/src/lib/model/deals-csv.js +4 -5
  142. package/src/lib/model/deals-csv.js.map +1 -1
  143. package/src/lib/model/lead-filters.d.ts +1 -1
  144. package/src/lib/model/leads-csv.d.ts +1 -1
  145. package/src/lib/model/leads-csv.js +1 -1
  146. package/src/lib/model/leads-csv.js.map +1 -1
  147. package/src/lib/model/tasks-csv.d.ts +1 -1
  148. package/src/lib/model/tasks-csv.js +1 -1
  149. package/src/lib/model/tasks-csv.js.map +1 -1
  150. package/src/lib/plan-entitlements.d.ts +75 -0
  151. package/src/lib/plan-entitlements.js +56 -0
  152. package/src/lib/plan-entitlements.js.map +1 -0
  153. package/src/lib/plugin.js +3 -0
  154. package/src/lib/plugin.js.map +1 -1
  155. package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
  156. package/src/lib/server/api-v1/contacts-merge.js +120 -0
  157. package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
  158. package/src/lib/server/api-v1/contacts.d.ts +18 -0
  159. package/src/lib/server/api-v1/contacts.js +1076 -0
  160. package/src/lib/server/api-v1/contacts.js.map +1 -0
  161. package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
  162. package/src/lib/server/api-v1/crm-activities.js +269 -0
  163. package/src/lib/server/api-v1/crm-activities.js.map +1 -0
  164. package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
  165. package/src/lib/server/api-v1/crm-companies.js +391 -0
  166. package/src/lib/server/api-v1/crm-companies.js.map +1 -0
  167. package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
  168. package/src/lib/server/api-v1/crm-deals.js +631 -0
  169. package/src/lib/server/api-v1/crm-deals.js.map +1 -0
  170. package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
  171. package/src/lib/server/api-v1/crm-email-templates.js +336 -0
  172. package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
  173. package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
  174. package/src/lib/server/api-v1/crm-leads.js +832 -0
  175. package/src/lib/server/api-v1/crm-leads.js.map +1 -0
  176. package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
  177. package/src/lib/server/api-v1/crm-pipelines.js +146 -0
  178. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
  179. package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
  180. package/src/lib/server/api-v1/crm-shared.js +436 -0
  181. package/src/lib/server/api-v1/crm-shared.js.map +1 -0
  182. package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
  183. package/src/lib/server/api-v1/crm-tasks.js +398 -0
  184. package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.d.ts +29 -0
  186. package/src/lib/server/api-v1/openapi.js +985 -0
  187. package/src/lib/server/api-v1/openapi.js.map +1 -0
  188. package/src/lib/server/api-v1/usage.d.ts +26 -0
  189. package/src/lib/server/api-v1/usage.js +73 -0
  190. package/src/lib/server/api-v1/usage.js.map +1 -0
  191. package/src/lib/server/consent-group-participant.js +1 -1
  192. package/src/lib/server/consent-group-participant.js.map +1 -1
  193. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  194. package/src/lib/server/contact-lifecycle-floor.js +91 -0
  195. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  196. package/src/lib/server/contact-remove.d.ts +75 -0
  197. package/src/lib/server/contact-remove.js +244 -0
  198. package/src/lib/server/contact-remove.js.map +1 -0
  199. package/src/lib/server/contact-update.js +2 -1
  200. package/src/lib/server/contact-update.js.map +1 -1
  201. package/src/lib/server/contacts-import.d.ts +47 -11
  202. package/src/lib/server/contacts-import.js +171 -71
  203. package/src/lib/server/contacts-import.js.map +1 -1
  204. package/src/lib/server/contacts-merge.d.ts +1 -1
  205. package/src/lib/server/contacts-merge.js +3 -2
  206. package/src/lib/server/contacts-merge.js.map +1 -1
  207. package/src/lib/server/convert-host-lead.d.ts +189 -0
  208. package/src/lib/server/convert-host-lead.js +518 -0
  209. package/src/lib/server/convert-host-lead.js.map +1 -0
  210. package/src/lib/server/crm-sharing.d.ts +108 -0
  211. package/src/lib/server/crm-sharing.js +790 -0
  212. package/src/lib/server/crm-sharing.js.map +1 -0
  213. package/src/lib/server/daily-digest-route.d.ts +107 -0
  214. package/src/lib/server/daily-digest-route.js +588 -0
  215. package/src/lib/server/daily-digest-route.js.map +1 -0
  216. package/src/lib/server/export-route.d.ts +60 -0
  217. package/src/lib/server/export-route.js +447 -0
  218. package/src/lib/server/export-route.js.map +1 -0
  219. package/src/lib/server/inbound-route.d.ts +74 -0
  220. package/src/lib/server/inbound-route.js +257 -0
  221. package/src/lib/server/inbound-route.js.map +1 -0
  222. package/src/lib/server/lead-convert.d.ts +1 -1
  223. package/src/lib/server/lead-convert.js +2 -2
  224. package/src/lib/server/lead-convert.js.map +1 -1
  225. package/src/lib/server/merge-contacts.d.ts +55 -0
  226. package/src/lib/server/merge-contacts.js +274 -0
  227. package/src/lib/server/merge-contacts.js.map +1 -0
  228. package/src/lib/server/pipeline-record-index.d.ts +36 -0
  229. package/src/lib/server/pipeline-record-index.js +87 -0
  230. package/src/lib/server/pipeline-record-index.js.map +1 -0
  231. package/src/lib/server/record-facts.js +2 -1
  232. package/src/lib/server/record-facts.js.map +1 -1
  233. package/src/lib/server/task-reminders-route.d.ts +82 -0
  234. package/src/lib/server/task-reminders-route.js +497 -0
  235. package/src/lib/server/task-reminders-route.js.map +1 -0
  236. package/src/lib/server-deal-stage.js +2 -1
  237. package/src/lib/server-deal-stage.js.map +1 -1
  238. package/src/lib/server.d.ts +5 -2
  239. package/src/lib/server.js +47 -2
  240. package/src/lib/server.js.map +1 -1
  241. package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
  242. package/src/lib/testing/stand-in-site-record-routes.js +55 -0
  243. package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
  244. package/src/lib/usage-axes.d.ts +35 -0
  245. package/src/lib/usage-axes.js +68 -0
  246. package/src/lib/usage-axes.js.map +1 -0
  247. package/src/lib/components/use-emails-hub-path.d.ts +0 -24
  248. package/src/lib/components/use-emails-hub-path.js +0 -59
  249. package/src/lib/components/use-emails-hub-path.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/leads-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 * as Aglyn from '@aglyn/aglyn'\nimport type {\n ConsolePluginPageProps,\n CrmLeadFields,\n CrmViewFilterClause,\n CrmLeadStatus,\n} from '@aglyn/aglyn'\nimport {\n mdiAccountArrowRight,\n mdiAccountCancelOutline,\n mdiAccountConvertOutline,\n mdiAccountTieOutline,\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 {\n type OrgMemberOptions,\n useOrgMemberOptions,\n} from '../hooks/use-org-member-options'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport RowActionsMenu from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Select,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n updateDoc,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_EMAIL_FILTER_OPTIONS,\n LEAD_FILTER_CODECS,\n LEAD_LIST_DECLARATION,\n LEAD_LIST_FILTER_FIELDS,\n LEAD_LIST_FILTER_HEADERS,\n LEAD_SOURCE_FILTER_NONE,\n LEAD_STATUS_FILTER_OPTIONS,\n leadClausesForGrid,\n LEAD_PREFIX_SEARCH,\n leadClauseImpliesScope,\n leadClausesToStore,\n leadQueryClause,\n} from '../model/lead-filters'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type LeadCsvOptions, leadsCsv } from '../model/leads-csv'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { LeadImportButton } from './lead-import-drawer'\nimport NewLeadDrawer, { type NewLeadValues } from './new-lead-drawer'\nimport { useCrmApi } from './use-crm-api'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { CONVERT_PENDING_ERASURE_REASON } from './lead-properties-card'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { LeadStatusChip } from './lead-status-chip'\nimport LeadSurfacesNote from './lead-surfaces-note'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport LeadsBulkBar from './leads-bulk-bar'\nimport OrgLeadSurfacesNote from './org-lead-surfaces-note'\n\n/**\n * One row of the list. `$id` keys the grid and `leadId` names the document,\n * and they are the same value at both levels (AGL-3275).\n *\n * `$id` used to be `{hostId}/{leadId}` at the organization level, because a\n * lead's id is a person key and the same person met by two sites was two\n * documents carrying it — so the id alone could not key a list that spanned\n * sites. One org collection ends that, and the row no longer carries a site\n * at all: which sites hold this person is `capturedByHostIds`, a fact about\n * the person rather than part of their address.\n */\ntype LeadRow = Record<string, unknown> &\n CrmLeadFields & { $id: string; leadId: string }\n\n/**\n * `/crm/leads` — the people a site has met but not yet qualified (AGL-2608).\n *\n * A section of its own, the way Salesforce keeps Leads apart from Contacts:\n * a lead is a capture — a form, a booking, a sign-up — that somebody has\n * still to work, and it converts into a contact, a company and a deal when\n * it is real. Reads `orgs/{orgId}/leads` narrowed by `visibleTo` to the sites\n * this viewer may see (AGL-3275) — the same collection and the same clause at\n * both levels, which is what lets ONE listener serve a section that used to\n * open one per site — and pages it by a query that carries every filter and\n * the search word (AGL-3321; see `leadQueryClause`).\n *\n * Under a site the clause names that site; at the ORGANIZATION level an\n * org-wide member reads without one, since the rules short-circuit on\n * `isOrgWideMember()` and a clause would only narrow what they may already\n * read. The per-site notes — which of a site's forms file a lead — belong to\n * a site's own hub and are not drawn here.\n */\nexport function CrmLeadsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, createHostId } = useCrmScope({ hostId, org })\n const mount = useCrmOrgMount()\n const roster = useOrgMemberOptions(orgId)\n // The org's lead fields, for the optional columns below (AGL-3272).\n const leadFields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298): the filter's menu and the\n // column's sort order.\n const leadSourceList = useLeadSourcePicklist(orgId)\n const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275) by `visibleTo array-contains-any` over the site's own tokens.\n * At the ORGANIZATION level an org-wide member reads with no scope clause,\n * which is what `null` asks for.\n */\n const scopeTokens = useMemo(\n () => (hostId ? scopeTokensForHost(hostId) : null),\n [hostId],\n )\n const dataRoot = useMemo(\n () => (orgId ? (['orgs', orgId] as const) : null),\n [orgId],\n )\n const foldsScope = useCrmFoldsScope(orgId, scopeTokens)\n\n /*\n * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the\n * grid's own Filters panel and quick search edit it (AGL-3313): Status,\n * Email, Owner, Lead source and Campaign are select columns of the grid.\n * The clauses keep the shape the old dropdowns stored — see\n * `leadClausesForGrid` — so no saved view of leads loses its filter.\n */\n const views = useCrmSavedView({\n section: 'leads',\n hostId,\n org: props.org,\n basePath: basePath ?? '',\n })\n const clauses = useMemo(\n () => leadClausesForGrid(views.state.filters),\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) => views.setFilters(leadClausesToStore(next)),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['status', 'emailState', 'ownerUid'],\n codecs: LEAD_FILTER_CODECS,\n })\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n const campaignName = useCallback(\n (id: string) =>\n campaigns.options.find((option) => option.value === id)?.label ?? id,\n [campaigns.options],\n )\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed — a retired campaign, a lead source since removed —\n * 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 clauses\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 campaignOptions = campaigns.options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n // Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked.\n const sourceOptions = [\n ...leadSourceList.picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: LEAD_SOURCE_FILTER_NONE, label: 'No lead source' },\n ]\n return {\n status: LEAD_STATUS_FILTER_OPTIONS,\n emailState: LEAD_EMAIL_FILTER_OPTIONS,\n ownerUid: roster.options.map((option) => ({\n value: option.uid,\n label: Aglyn.crmMemberPickerLabel(option),\n })),\n campaignIds: [...campaignOptions, ...stale('campaignIds', campaignOptions)],\n leadSource: [...sourceOptions, ...stale('leadSource', sourceOptions)],\n }\n }, [clauses, campaigns.options, leadSourceList.picklist, roster.options])\n /*\n * Every clause and the search word on ONE query (AGL-3321): each stored\n * clause asked through the field its writer keeps (`leadQueryClause`),\n * paged by that query, and what one query cannot hold refused by name\n * above the list — never matched over the rows a page happened to load.\n */\n const asked = useMemo(\n () =>\n crmAskClauses(clauses, (clause) =>\n leadQueryClause(clause, { scopeTokens, foldsScope }),\n ),\n [clauses, scopeTokens, foldsScope],\n )\n const searchKey = gridFilter.searchWords.join(' ')\n const paged = useCrmListQuery<Record<string, unknown> & CrmLeadFields & { $id: string }>({\n scope: dataRoot,\n collection: 'leads',\n visibleTo: scopeTokens,\n foldsScope,\n declaration: LEAD_LIST_DECLARATION,\n clauses: asked.clauses,\n search: gridFilter.searchWords,\n impliesScope: leadClauseImpliesScope,\n prefixSearch: LEAD_PREFIX_SEARCH,\n })\n const status = paged.status\n const rows = useMemo<LeadRow[]>(\n () => paged.rows.map((row) => ({ ...row, leadId: row.$id })),\n [paged.rows],\n )\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: LEAD_LIST_FILTER_FIELDS,\n headers: LEAD_LIST_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n /** The list as it opens — Open leads, nothing typed — whose emptiness is news. */\n const opening =\n !searchKey.trim() &&\n clauses.length === 1 &&\n clauses[0].field === 'status' &&\n clauses[0].value === 'open'\n\n /*\n * The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or\n * the search term changes: a selection made on the open leads is not a\n * selection of the unqualified ones, and the bar's count would be over\n * rows no longer listed.\n */\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n useEffect(\n () => setSelectedIds([]),\n [views.state.filters, searchKey, paged.page],\n )\n // How the file names the owner and, at the org level, the site.\n const csvOptions: LeadCsvOptions = useMemo(\n () => ({\n ownerEmail: roster.emailFor,\n ...(hostId ? {} : { siteName: (id: string) => mount?.siteName(id) }),\n }),\n [roster.emailFor, hostId, mount],\n )\n // The page on screen; Export all on the bulk bar takes the whole list.\n const handleExport = useCallback(() => {\n downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions))\n }, [rows, csvOptions])\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | null>(null)\n // The row whose conversion dialog is open (AGL-2641) — the same dialog\n // the lead's page opens, fed the row so the list is one click shorter.\n const [converting, setConverting] = useState<LeadRow | null>(null)\n\n /*==========================================\n * NEW LEAD (AGL-3231) — Salesforce's New Lead, in a drawer over the\n * list. The route files the lead through the one lead door under the\n * mounted site, or at the organization level under the site the drawer's\n * picker named; the drawer holds its submit until one is known. It makes\n * a lead and nothing else — the conversion is what makes the contact.\n *=========================================*/\n const crmApi = useCrmApi(createHostId)\n const [createOpen, setCreateOpen] = useState(false)\n const [createBusy, setCreateBusy] = useState(false)\n const [createError, setCreateError] = useState<string | null>(null)\n const handleCreate = useCallback(\n async (values: NewLeadValues) => {\n setCreateBusy(true)\n setCreateError(null)\n try {\n const { response, payload } = await crmApi('leads-create', {\n email: values.email,\n ...(values.name ? { name: values.name } : {}),\n ...(values.company ? { company: values.company } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n ...(values.website ? { website: values.website } : {}),\n ...(values.leadSource ? { leadSource: values.leadSource } : {}),\n ...(values.address ? { address: values.address } : {}),\n ...(values.tags.length ? { tags: values.tags } : {}),\n ...(values.campaignIds.length\n ? { campaignIds: values.campaignIds }\n : {}),\n ...(values.ownerUid ? { ownerUid: values.ownerUid } : {}),\n ...(values.notes ? { notes: values.notes } : {}),\n // The org's own lead fields (AGL-3272), sent only when one was\n // filled — the route reads the definitions to judge the map, and\n // a body without it pays for no read.\n ...(values.custom ? { custom: values.custom } : {}),\n status: values.status,\n })\n if (!response.ok) {\n // The route's own sentence, shown above the form unchanged.\n setCreateError(\n String(payload['error'] ?? 'The lead could not be added.'),\n )\n return\n }\n // The activity entry is the route's: it verified the caller and\n // performed the write.\n enqueueSnackbar(\n payload['created']\n ? 'Lead added'\n : 'This site already held a lead for that address — it was updated',\n { variant: 'success', persist: false },\n )\n setCreateOpen(false)\n } catch (error) {\n console.error(error)\n setCreateError('The lead could not be added.')\n } finally {\n setCreateBusy(false)\n }\n },\n [crmApi, enqueueSnackbar],\n )\n\n const writeLead = useCallback(\n async (lead: LeadRow, fields: Record<string, unknown>, done: string) => {\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId, 'leads', lead.leadId),\n {\n ...fields,\n updatedAt: serverTimestamp(),\n },\n )\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error\n ? error.message\n : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n },\n [firestore, enqueueSnackbar],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'name',\n headerName: 'Lead',\n flex: 1.4,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n String(row['name'] || row['email'] || ''),\n renderCell: ({ row }: { row: LeadRow }) => (\n <Stack\n spacing={0}\n sx={{ minWidth: 0, justifyContent: 'center', height: '100%' }}\n >\n <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\n {row['name'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {String(row['email'] ?? '')}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n // The lead's own profile (AGL-3231): the two facts a work queue is\n // scanned by, beside the person.\n {\n field: 'company',\n headerName: 'Company',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => String(row.company ?? ''),\n },\n {\n field: 'jobTitle',\n headerName: 'Title',\n flex: 0.9,\n minWidth: 130,\n valueGetter: (_value, row: LeadRow) => String(row.jobTitle ?? ''),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n onChange={(next) => {\n if (next === 'unqualified') {\n setUnqualifying(row)\n return\n }\n void writeLead(\n row,\n {\n status: next,\n ...(Aglyn.crmLeadStatus(row) === 'unqualified'\n ? { unqualifiedReason: deleteField() }\n : {}),\n },\n 'Status updated',\n )\n }}\n />\n ),\n },\n {\n /*\n * The verdict on the address (AGL-3245): the chip the lead's page\n * carries, so a bounced lead is told apart in the queue it is worked\n * from; its label for the sort and the export.\n */\n field: 'emailState',\n headerName: 'Email',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => {\n const state = Aglyn.readEmailState(row)\n return state ? Aglyn.EMAIL_STATE_LABELS[state.status] : ''\n },\n renderCell: ({ row }: { row: LeadRow }) => {\n const state = Aglyn.readEmailState(row)\n return state ? (\n <CrmEmailStateChip state={state} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n row.ownerUid ? roster.labelFor(row.ownerUid) : 'Unassigned',\n },\n // Only at the organization level, where a row can be any site's.\n ...(hostId\n ? []\n : [\n {\n field: 'hostId',\n headerName: 'Site',\n flex: 0.9,\n minWidth: 140,\n /*\n * KNOWN BY, not \"the site\" (AGL-3275). A lead is one org row\n * and several sites in a consent group can hold the same\n * person, so this names every site that captured them — the\n * answer the Contacts list has always given in its own column.\n */\n valueGetter: (_value: unknown, row: LeadRow) => {\n const held = Array.isArray(row['capturedByHostIds'])\n ? (row['capturedByHostIds'] as string[])\n : []\n return held.map((id) => mount?.siteName(id) ?? id).join(', ')\n },\n } satisfies GridColDef,\n ]),\n {\n field: 'sources',\n headerName: 'Source',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n leadSources(row).map(leadSourceLabel).join(', '),\n },\n /*\n * Salesforce's Lead Source (AGL-3298), beside the surfaces that\n * captured the person. Sorted in the order the org keeps its values,\n * as a picklist sorts, with a value the list does not hold after\n * every listed one and a lead with none last.\n */\n {\n field: 'leadSource',\n headerName: 'Lead source',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => String(row.leadSource ?? ''),\n sortComparator: (a: unknown, b: unknown) =>\n Aglyn.crmPicklistRank(leadSourceList.picklist, a) -\n Aglyn.crmPicklistRank(leadSourceList.picklist, b) ||\n String(a ?? '').localeCompare(String(b ?? '')),\n } satisfies GridColDef,\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => (row.tags ?? []).join(', '),\n },\n // The campaigns the lead is filed under (AGL-3254), by name — the\n // ids are the storage.\n {\n field: 'campaignIds',\n headerName: 'Campaign',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value: unknown, row: LeadRow) =>\n Aglyn.readCampaignIds(row).map(campaignName).join(', '),\n } satisfies GridColDef,\n {\n field: 'lastSeenAtMs',\n headerName: 'Last seen',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n leadTimeLabel(row['lastSeenAtMs'] ?? row['createdAt']),\n },\n // The org's lead fields as optional columns (AGL-3272), read off the\n // row's own `custom` map the way the contacts list reads its own.\n // Ahead of the row menu, so the overflow stays at the right edge\n // however many fields the org has defined.\n ...customFieldColumns(leadFields.active),\n {\n field: 'actions',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n disableColumnMenu: true,\n renderCell: ({ row }: { row: LeadRow }) => {\n /*\n * Why Convert is refused, in the order the lead's page refuses it:\n * a converted lead has its contact already, a closed one was\n * judged not real, and a person with an erasure pending must not\n * be captured again — a conversion is a capture (AGL-2623).\n */\n const converted = Boolean(row.convertedContactId)\n const erasurePending = Aglyn.readErasureRequestedAtMs(row) !== null\n const convertRefusal = converted\n ? 'This lead was converted'\n : !Aglyn.isCrmLeadOpen(row)\n ? 'This lead was unqualified'\n : erasurePending\n ? CONVERT_PENDING_ERASURE_REASON\n : null\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{ display: 'flex', alignItems: 'center', height: '100%' }}\n >\n <RowActionsMenu\n label={String(row['email'] ?? row.$id)}\n items={[\n {\n key: 'open',\n label: 'Open lead',\n icon: (\n <MdiIcon path={mdiAccountArrowRight.path} size={0.8} />\n ),\n href: routes.lead(row.leadId),\n },\n {\n key: 'convert',\n label: 'Convert…',\n icon: (\n <MdiIcon\n path={mdiAccountConvertOutline.path}\n size={0.8}\n />\n ),\n onClick: () => setConverting(row),\n disabled: convertRefusal !== null,\n disabledReason: convertRefusal ?? undefined,\n },\n {\n key: 'assign',\n label: 'Assign owner',\n icon: (\n <MdiIcon path={mdiAccountTieOutline.path} size={0.8} />\n ),\n onClick: () => setAssigning(row),\n },\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: (\n <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />\n ),\n onClick: () => setUnqualifying(row),\n disabled:\n !Aglyn.isCrmLeadOpen(row) ||\n Boolean(row.convertedContactId),\n disabledReason: row.convertedContactId\n ? 'This lead was converted'\n : 'This lead is already closed',\n },\n ]}\n />\n </Box>\n )\n },\n },\n ],\n [\n roster,\n routes,\n writeLead,\n hostId,\n mount,\n campaignName,\n leadFields.active,\n leadSourceList.picklist,\n ],\n )\n /*\n * The column and sort models are the view's (AGL-2617); the filterable\n * columns are the declared fields, as selects over their choices (AGL-3313).\n */\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns)\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('crmLeads', {\n anchor: '#filter-the-leads',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <LeadImportButton hostId={hostId} orgId={orgId} />\n <Button size=\"small\" onClick={handleExport} disabled={!rows.length}>\n {'Export CSV'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New lead'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n {/* Which surfaces file a lead, by name (AGL-2612) — under a site its own, at the org level every site's (AGL-2638). */}\n {hostId ? (\n <LeadSurfacesNote hostId={hostId} />\n ) : (\n <OrgLeadSurfacesNote />\n )}\n {/*\n The saved view, and every clause it is narrowed by as a chip\n (AGL-3313). The clauses are set in the grid's own Filters panel;\n the chips show the whole set, which that panel shows one of.\n */}\n <CrmListToolbar label=\"Lead filters\">\n <CrmViewsControl controller={views} allLabel=\"All leads\" />\n <ListFilterChips\n fields={LEAD_LIST_FILTER_FIELDS}\n headers={LEAD_LIST_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={paged.plan.notices} />\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n csv={csvOptions}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={rows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{\n selected: selectedIds,\n onChange: setSelectedIds,\n }}\n loading={status === 'loading'}\n onOpen={(_id, row: LeadRow) => router.push(routes.lead(row.leadId))}\n // Columns are the view's, controlled (AGL-2617). The query\n // orders the list — newest seen first — so the grid sorts\n // nothing itself: a sort over one page would reorder that page.\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortingMode=\"server\"\n disableColumnSorting\n // The panel and the search are the grid's; the query answers\n // both (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel={opening ? 'No open leads' : 'No leads match these filters'}\n noRowsDescription={\n opening\n ? 'Bookings and lead-routed forms on your site become leads on their own — or add one with New lead, or bring a list in with Import CSV.'\n : undefined\n }\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n </CardDisplay>\n <NewLeadDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n hostId={hostId ?? null}\n org={org as Record<string, unknown> | undefined}\n busy={createBusy}\n error={createError}\n roster={roster}\n orgId={orgId}\n onSubmit={(values) => void handleCreate(values)}\n />\n <AssignOwnerDialog\n lead={assigning}\n roster={roster}\n onClose={() => setAssigning(null)}\n onAssign={(uid) => {\n if (!assigning) return\n void writeLead(\n assigning,\n { ownerUid: uid || deleteField() },\n uid ? 'Owner assigned' : 'Owner cleared',\n )\n setAssigning(null)\n }}\n />\n <LeadUnqualifyDialog\n open={Boolean(unqualifying)}\n onClose={() => setUnqualifying(null)}\n hostId={hostId ?? null}\n leadId={unqualifying?.leadId ?? ''}\n leadLabel={String(\n unqualifying?.['name'] || unqualifying?.['email'] || '',\n )}\n />\n {/* The site the conversion is filed as (AGL-2641), which since\n AGL-3275 is the first site that captured this person rather than\n \"the site the row lives under\" — a lead shared by a consent group\n lives under none of them in particular. Under a site the mounted\n one is used, and the two agree for every single-brand org. */}\n <LeadConvertDialog\n open={Boolean(converting)}\n onClose={() => setConverting(null)}\n hostId={\n hostId ??\n (Aglyn.leadPrimaryGroup(converting, org as Record<string, unknown>).hostId || null)\n }\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={converting?.leadId ?? ''}\n lead={converting ?? {}}\n basePath={basePath ?? ''}\n roster={roster}\n />\n </>\n )\n}\nCrmLeadsSection.displayName = 'CrmLeadsSection'\n\n/**\n * The status, editable in place for a lead that is still open.\n *\n * A converted lead shows the chip alone: its status IS the conversion, and\n * the route stamped it. The select stops its click from reaching the row, or\n * every status change would also open the record.\n */\nfunction InlineStatus(props: {\n lead: LeadRow\n onChange: (next: CrmLeadStatus) => void\n}) {\n const { lead, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} />\n </Box>\n )\n }\n const status = Aglyn.crmLeadStatus(lead)\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n width: '100%',\n }}\n >\n <Select\n size=\"small\"\n variant=\"standard\"\n disableUnderline\n value={status}\n onChange={(event) => onChange(event.target.value as CrmLeadStatus)}\n renderValue={() => <LeadStatusChip lead={lead} />}\n sx={{ width: '100%' }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {Aglyn.CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n <MenuItem value=\"unqualified\">{`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}</MenuItem>\n </Select>\n </Box>\n )\n}\nInlineStatus.displayName = 'InlineStatus'\n\n/** Hand a lead to a team member. */\nfunction AssignOwnerDialog(props: {\n lead: LeadRow | null\n roster: OrgMemberOptions\n onClose: () => void\n onAssign: (uid: string) => void\n}) {\n const { lead, roster, onClose, onAssign } = props\n const [uid, setUid] = useState<string | null>(null)\n const current = uid ?? String(lead?.ownerUid ?? '')\n return (\n <Dialog\n open={Boolean(lead)}\n onClose={onClose}\n maxWidth=\"xs\"\n fullWidth\n slotProps={{ transition: { onExited: () => setUid(null) } }}\n >\n <DialogTitle>{`Assign ${String(lead?.['name'] || lead?.['email'] || 'lead')}`}</DialogTitle>\n <DialogContent>\n <Box sx={{ pt: 1 }}>\n <LeadOwnerSelect\n value={current}\n onChange={setUid}\n roster={roster}\n size=\"medium\"\n />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={() => onAssign(current)}>\n {'Assign'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nAssignOwnerDialog.displayName = 'AssignOwnerDialog'\n\nexport default CrmLeadsSection\n"],"names":["Aglyn","mdiAccountArrowRight","mdiAccountCancelOutline","mdiAccountConvertOutline","mdiAccountTieOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useOrgMemberOptions","useCrmOrgMount","useCrmSavedView","useCrmScope","useCrmCampaigns","scopeTokensForHost","useContactFieldDefinitions","customFieldColumns","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","useCrmFoldsScope","useCrmListQuery","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","ListQueryNotices","useSnackbar","useFirestore","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Select","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","downloadTextFile","crmRoutes","LEAD_EMAIL_FILTER_OPTIONS","LEAD_FILTER_CODECS","LEAD_LIST_DECLARATION","LEAD_LIST_FILTER_FIELDS","LEAD_LIST_FILTER_HEADERS","LEAD_SOURCE_FILTER_NONE","LEAD_STATUS_FILTER_OPTIONS","leadClausesForGrid","LEAD_PREFIX_SEARCH","leadClauseImpliesScope","leadClausesToStore","leadQueryClause","crmAskClauses","crmQueryRefusals","listFilterGridColumns","useListGridFilter","ListFilterChips","useLeadSourcePicklist","leadsCsv","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","LeadImportButton","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","mount","roster","leadFields","leadSourceList","routes","scopeTokens","dataRoot","foldsScope","views","section","clauses","state","filters","setClauses","next","setFilters","gridFilter","onChange","selectFields","codecs","campaigns","enabled","campaignName","id","options","find","option","value","label","filterOptions","stale","field","known","filter","clause","op","flatMap","split","map","trim","some","campaignOptions","sourceOptions","picklist","values","active","status","emailState","ownerUid","uid","crmMemberPickerLabel","campaignIds","leadSource","asked","searchKey","searchWords","join","paged","scope","collection","visibleTo","declaration","search","impliesScope","prefixSearch","rows","row","leadId","$id","refused","plan","fields","headers","opening","length","selectedIds","setSelectedIds","page","csvOptions","ownerEmail","emailFor","siteName","handleExport","assigning","setAssigning","unqualifying","setUnqualifying","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","response","payload","email","name","company","jobTitle","phone","website","address","tags","notes","custom","ok","String","variant","persist","error","console","writeLead","lead","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","noWrap","color","crmLeadStatus","InlineStatus","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","sortComparator","a","b","crmPicklistRank","localeCompare","readCampaignIds","width","sortable","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","alignItems","items","key","icon","path","size","href","disabled","disabledReason","undefined","filterColumns","grid","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","marksServed","notices","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","sortingMode","disableColumnSorting","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","noRowsDescription","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","leadLabel","leadPrimaryGroup","displayName","disableUnderline","target","renderValue","CRM_LEAD_STATUS_LABELS","new","working","unqualified","setUid","current","maxWidth","fullWidth","slotProps","transition","onExited","pt"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/leads-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 * as Aglyn from '@aglyn/aglyn'\nimport type {\n ConsolePluginPageProps,\n CrmLeadFields,\n CrmViewFilterClause,\n CrmLeadStatus,\n} from '@aglyn/aglyn'\nimport {\n mdiAccountArrowRight,\n mdiAccountCancelOutline,\n mdiAccountConvertOutline,\n mdiAccountTieOutline,\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 {\n type OrgMemberOptions,\n useOrgMemberOptions,\n} from '../hooks/use-org-member-options'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { customFieldColumns } from './contact-custom-columns'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport RowActionsMenu from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Select,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n updateDoc,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { leadPrimaryGroup } from '../model/contact-holder'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport {\n LEAD_EMAIL_FILTER_OPTIONS,\n LEAD_FILTER_CODECS,\n LEAD_LIST_DECLARATION,\n LEAD_LIST_FILTER_FIELDS,\n LEAD_LIST_FILTER_HEADERS,\n LEAD_SOURCE_FILTER_NONE,\n LEAD_STATUS_FILTER_OPTIONS,\n leadClausesForGrid,\n LEAD_PREFIX_SEARCH,\n leadClauseImpliesScope,\n leadClausesToStore,\n leadQueryClause,\n} from '../model/lead-filters'\nimport { crmAskClauses, crmQueryRefusals } from '../model/crm-list-query'\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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\nimport { type LeadCsvOptions, leadsCsv } from '../model/leads-csv'\nimport { LeadConvertDialog } from './lead-convert-dialog'\nimport {\n leadSourceLabel,\n leadSources,\n leadTimeLabel,\n} from './lead-history-card'\nimport { LeadImportButton } from './lead-import-drawer'\nimport NewLeadDrawer, { type NewLeadValues } from './new-lead-drawer'\nimport { useCrmApi } from './use-crm-api'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { CONVERT_PENDING_ERASURE_REASON } from './lead-properties-card'\nimport { CrmEmailStateChip } from './crm-email-state-chip'\nimport { LeadStatusChip } from './lead-status-chip'\nimport { CrmShareChipView } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\nimport LeadSurfacesNote from './lead-surfaces-note'\nimport { LeadUnqualifyDialog } from './lead-unqualify-dialog'\nimport LeadsBulkBar from './leads-bulk-bar'\nimport OrgLeadSurfacesNote from './org-lead-surfaces-note'\n\n/**\n * One row of the list. `$id` keys the grid and `leadId` names the document,\n * and they are the same value at both levels (AGL-3275).\n *\n * `$id` used to be `{hostId}/{leadId}` at the organization level, because a\n * lead's id is a person key and the same person met by two sites was two\n * documents carrying it — so the id alone could not key a list that spanned\n * sites. One org collection ends that, and the row no longer carries a site\n * at all: which sites hold this person is `capturedByHostIds`, a fact about\n * the person rather than part of their address.\n */\ntype LeadRow = Record<string, unknown> &\n CrmLeadFields & { $id: string; leadId: string }\n\n/**\n * `/crm/leads` — the people a site has met but not yet qualified (AGL-2608).\n *\n * A section of its own, the way Salesforce keeps Leads apart from Contacts:\n * a lead is a capture — a form, a booking, a sign-up — that somebody has\n * still to work, and it converts into a contact, a company and a deal when\n * it is real. Reads `orgs/{orgId}/leads` narrowed by `visibleTo` to the sites\n * this viewer may see (AGL-3275) — the same collection and the same clause at\n * both levels, which is what lets ONE listener serve a section that used to\n * open one per site — and pages it by a query that carries every filter and\n * the search word (AGL-3321; see `leadQueryClause`).\n *\n * Under a site the clause names that site; at the ORGANIZATION level an\n * org-wide member reads without one, since the rules short-circuit on\n * `isOrgWideMember()` and a clause would only narrow what they may already\n * read. The per-site notes — which of a site's forms file a lead — belong to\n * a site's own hub and are not drawn here.\n */\nexport function CrmLeadsSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, createHostId, consentGroup } = useCrmScope({ hostId, org })\n // The viewing site's group: a row it sees only through a share is chipped\n // with who shared it (AGL-3336). None at the organization level.\n const viewingHostIds = consentGroup?.hostIds\n const mount = useCrmOrgMount()\n const roster = useOrgMemberOptions(orgId)\n // The org's lead fields, for the optional columns below (AGL-3272).\n const leadFields = useContactFieldDefinitions(orgId, 'lead')\n // The org's lead source values (AGL-3298): the filter's menu and the\n // column's sort order.\n const leadSourceList = useLeadSourcePicklist(orgId)\n const routes = crmRoutes(basePath ?? '')\n\n /*\n * Under a site: the ORG collection, narrowed to what this site may see\n * (AGL-3275) by `visibleTo array-contains-any` over the site's own tokens.\n * At the ORGANIZATION level an org-wide member reads with no scope clause,\n * which is what `null` asks for.\n */\n const scopeTokens = useMemo(\n () => (hostId ? scopeTokensForHost(hostId) : null),\n [hostId],\n )\n const dataRoot = useMemo(\n () => (orgId ? (['orgs', orgId] as const) : null),\n [orgId],\n )\n const foldsScope = useCrmFoldsScope(orgId, scopeTokens)\n\n /*\n * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the\n * grid's own Filters panel and quick search edit it (AGL-3313): Status,\n * Email, Owner, Lead source and Campaign are select columns of the grid.\n * The clauses keep the shape the old dropdowns stored — see\n * `leadClausesForGrid` — so no saved view of leads loses its filter.\n */\n const views = useCrmSavedView({\n section: 'leads',\n hostId,\n org: props.org,\n basePath: basePath ?? '',\n })\n const clauses = useMemo(\n () => leadClausesForGrid(views.state.filters),\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) => views.setFilters(leadClausesToStore(next)),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['status', 'emailState', 'ownerUid'],\n codecs: LEAD_FILTER_CODECS,\n })\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: true })\n const campaignName = useCallback(\n (id: string) =>\n campaigns.options.find((option) => option.value === id)?.label ?? id,\n [campaigns.options],\n )\n /*\n * The choices each select column offers. A stored clause naming a value\n * no longer listed — a retired campaign, a lead source since removed —\n * 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 clauses\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 campaignOptions = campaigns.options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n // Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked.\n const sourceOptions = [\n ...leadSourceList.picklist.values.map((value) => ({\n value: value.label,\n label: value.active ? value.label : `${value.label} (inactive)`,\n })),\n { value: LEAD_SOURCE_FILTER_NONE, label: 'No lead source' },\n ]\n return {\n status: LEAD_STATUS_FILTER_OPTIONS,\n emailState: LEAD_EMAIL_FILTER_OPTIONS,\n ownerUid: roster.options.map((option) => ({\n value: option.uid,\n label: Aglyn.crmMemberPickerLabel(option),\n })),\n campaignIds: [...campaignOptions, ...stale('campaignIds', campaignOptions)],\n leadSource: [...sourceOptions, ...stale('leadSource', sourceOptions)],\n }\n }, [clauses, campaigns.options, leadSourceList.picklist, roster.options])\n /*\n * Every clause and the search word on ONE query (AGL-3321): each stored\n * clause asked through the field its writer keeps (`leadQueryClause`),\n * paged by that query, and what one query cannot hold refused by name\n * above the list — never matched over the rows a page happened to load.\n */\n const asked = useMemo(\n () =>\n crmAskClauses(clauses, (clause) =>\n leadQueryClause(clause, { scopeTokens, foldsScope }),\n ),\n [clauses, scopeTokens, foldsScope],\n )\n const searchKey = gridFilter.searchWords.join(' ')\n const paged = useCrmListQuery<Record<string, unknown> & CrmLeadFields & { $id: string }>({\n scope: dataRoot,\n collection: 'leads',\n visibleTo: scopeTokens,\n foldsScope,\n declaration: LEAD_LIST_DECLARATION,\n clauses: asked.clauses,\n search: gridFilter.searchWords,\n impliesScope: leadClauseImpliesScope,\n prefixSearch: LEAD_PREFIX_SEARCH,\n })\n const status = paged.status\n const rows = useMemo<LeadRow[]>(\n () => paged.rows.map((row) => ({ ...row, leadId: row.$id })),\n [paged.rows],\n )\n const refused = useMemo(\n () =>\n crmQueryRefusals(paged.plan, asked, {\n fields: LEAD_LIST_FILTER_FIELDS,\n headers: LEAD_LIST_FILTER_HEADERS,\n options: filterOptions,\n }),\n [paged.plan, asked, filterOptions],\n )\n /** The list as it opens — Open leads, nothing typed — whose emptiness is news. */\n const opening =\n !searchKey.trim() &&\n clauses.length === 1 &&\n clauses[0].field === 'status' &&\n clauses[0].value === 'open'\n\n /*\n * The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or\n * the search term changes: a selection made on the open leads is not a\n * selection of the unqualified ones, and the bar's count would be over\n * rows no longer listed.\n */\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n useEffect(\n () => setSelectedIds([]),\n [views.state.filters, searchKey, paged.page],\n )\n // How the file names the owner and, at the org level, the site.\n const csvOptions: LeadCsvOptions = useMemo(\n () => ({\n ownerEmail: roster.emailFor,\n ...(hostId ? {} : { siteName: (id: string) => mount?.siteName(id) }),\n }),\n [roster.emailFor, hostId, mount],\n )\n // The page on screen; Export all on the bulk bar takes the whole list.\n const handleExport = useCallback(() => {\n downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions))\n }, [rows, csvOptions])\n\n const [assigning, setAssigning] = useState<LeadRow | null>(null)\n const [unqualifying, setUnqualifying] = useState<LeadRow | null>(null)\n // The row whose conversion dialog is open (AGL-2641) — the same dialog\n // the lead's page opens, fed the row so the list is one click shorter.\n const [converting, setConverting] = useState<LeadRow | null>(null)\n\n /*==========================================\n * NEW LEAD (AGL-3231) — Salesforce's New Lead, in a drawer over the\n * list. The route files the lead through the one lead door under the\n * mounted site, or at the organization level under the site the drawer's\n * picker named; the drawer holds its submit until one is known. It makes\n * a lead and nothing else — the conversion is what makes the contact.\n *=========================================*/\n const crmApi = useCrmApi(createHostId)\n const [createOpen, setCreateOpen] = useState(false)\n const [createBusy, setCreateBusy] = useState(false)\n const [createError, setCreateError] = useState<string | null>(null)\n const handleCreate = useCallback(\n async (values: NewLeadValues) => {\n setCreateBusy(true)\n setCreateError(null)\n try {\n const { response, payload } = await crmApi('leads-create', {\n email: values.email,\n ...(values.name ? { name: values.name } : {}),\n ...(values.company ? { company: values.company } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n ...(values.website ? { website: values.website } : {}),\n ...(values.leadSource ? { leadSource: values.leadSource } : {}),\n ...(values.address ? { address: values.address } : {}),\n ...(values.tags.length ? { tags: values.tags } : {}),\n ...(values.campaignIds.length\n ? { campaignIds: values.campaignIds }\n : {}),\n ...(values.ownerUid ? { ownerUid: values.ownerUid } : {}),\n ...(values.notes ? { notes: values.notes } : {}),\n // The org's own lead fields (AGL-3272), sent only when one was\n // filled — the route reads the definitions to judge the map, and\n // a body without it pays for no read.\n ...(values.custom ? { custom: values.custom } : {}),\n status: values.status,\n })\n if (!response.ok) {\n // The route's own sentence, shown above the form unchanged.\n setCreateError(\n String(payload['error'] ?? 'The lead could not be added.'),\n )\n return\n }\n // The activity entry is the route's: it verified the caller and\n // performed the write.\n enqueueSnackbar(\n payload['created']\n ? 'Lead added'\n : 'This site already held a lead for that address — it was updated',\n { variant: 'success', persist: false },\n )\n setCreateOpen(false)\n } catch (error) {\n console.error(error)\n setCreateError('The lead could not be added.')\n } finally {\n setCreateBusy(false)\n }\n },\n [crmApi, enqueueSnackbar],\n )\n\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const writeLead = useCallback(\n async (lead: LeadRow, fields: Record<string, unknown>, done: string) => {\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId, 'leads', lead.leadId),\n {\n ...fields,\n updatedAt: serverTimestamp(),\n },\n )\n followUpSharing('leads', [lead.leadId])\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error\n ? error.message\n : 'The lead could not be updated.',\n { variant: 'error' },\n )\n }\n },\n [firestore, enqueueSnackbar, followUpSharing],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'name',\n headerName: 'Lead',\n flex: 1.4,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n String(row['name'] || row['email'] || ''),\n renderCell: ({ row }: { row: LeadRow }) => (\n <Stack\n spacing={0}\n sx={{ minWidth: 0, justifyContent: 'center', height: '100%' }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {String(row['name'] || row['email'] || row.$id)}\n </Typography>\n <CrmShareChipView chip={crmShareChipFor(row, viewingHostIds ?? [])} org={org} />\n </Stack>\n {row['name'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {String(row['email'] ?? '')}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n // The lead's own profile (AGL-3231): the two facts a work queue is\n // scanned by, beside the person.\n {\n field: 'company',\n headerName: 'Company',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => String(row.company ?? ''),\n },\n {\n field: 'jobTitle',\n headerName: 'Title',\n flex: 0.9,\n minWidth: 130,\n valueGetter: (_value, row: LeadRow) => String(row.jobTitle ?? ''),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => Aglyn.crmLeadStatus(row),\n renderCell: ({ row }: { row: LeadRow }) => (\n <InlineStatus\n lead={row}\n onChange={(next) => {\n if (next === 'unqualified') {\n setUnqualifying(row)\n return\n }\n void writeLead(\n row,\n {\n status: next,\n ...(Aglyn.crmLeadStatus(row) === 'unqualified'\n ? { unqualifiedReason: deleteField() }\n : {}),\n },\n 'Status updated',\n )\n }}\n />\n ),\n },\n {\n /*\n * The verdict on the address (AGL-3245): the chip the lead's page\n * carries, so a bounced lead is told apart in the queue it is worked\n * from; its label for the sort and the export.\n */\n field: 'emailState',\n headerName: 'Email',\n flex: 0.9,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => {\n const state = Aglyn.readEmailState(row)\n return state ? Aglyn.EMAIL_STATE_LABELS[state.status] : ''\n },\n renderCell: ({ row }: { row: LeadRow }) => {\n const state = Aglyn.readEmailState(row)\n return state ? (\n <CrmEmailStateChip state={state} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n },\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n row.ownerUid ? roster.labelFor(row.ownerUid) : 'Unassigned',\n },\n // Only at the organization level, where a row can be any site's.\n ...(hostId\n ? []\n : [\n {\n field: 'hostId',\n headerName: 'Site',\n flex: 0.9,\n minWidth: 140,\n /*\n * KNOWN BY, not \"the site\" (AGL-3275). A lead is one org row\n * and several sites in a consent group can hold the same\n * person, so this names every site that captured them — the\n * answer the Contacts list has always given in its own column.\n */\n valueGetter: (_value: unknown, row: LeadRow) => {\n const held = Array.isArray(row['capturedByHostIds'])\n ? (row['capturedByHostIds'] as string[])\n : []\n return held.map((id) => mount?.siteName(id) ?? id).join(', ')\n },\n } satisfies GridColDef,\n ]),\n {\n field: 'sources',\n headerName: 'Source',\n flex: 1,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) =>\n leadSources(row).map(leadSourceLabel).join(', '),\n },\n /*\n * Salesforce's Lead Source (AGL-3298), beside the surfaces that\n * captured the person. Sorted in the order the org keeps its values,\n * as a picklist sorts, with a value the list does not hold after\n * every listed one and a lead with none last.\n */\n {\n field: 'leadSource',\n headerName: 'Lead source',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value, row: LeadRow) => String(row.leadSource ?? ''),\n sortComparator: (a: unknown, b: unknown) =>\n Aglyn.crmPicklistRank(leadSourceList.picklist, a) -\n Aglyn.crmPicklistRank(leadSourceList.picklist, b) ||\n String(a ?? '').localeCompare(String(b ?? '')),\n } satisfies GridColDef,\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 0.9,\n minWidth: 140,\n valueGetter: (_value, row: LeadRow) => (row.tags ?? []).join(', '),\n },\n // The campaigns the lead is filed under (AGL-3254), by name — the\n // ids are the storage.\n {\n field: 'campaignIds',\n headerName: 'Campaign',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value: unknown, row: LeadRow) =>\n Aglyn.readCampaignIds(row).map(campaignName).join(', '),\n } satisfies GridColDef,\n {\n field: 'lastSeenAtMs',\n headerName: 'Last seen',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: LeadRow) =>\n leadTimeLabel(row['lastSeenAtMs'] ?? row['createdAt']),\n },\n // The org's lead fields as optional columns (AGL-3272), read off the\n // row's own `custom` map the way the contacts list reads its own.\n // Ahead of the row menu, so the overflow stays at the right edge\n // however many fields the org has defined.\n ...customFieldColumns(leadFields.active),\n {\n field: 'actions',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n disableColumnMenu: true,\n renderCell: ({ row }: { row: LeadRow }) => {\n /*\n * Why Convert is refused, in the order the lead's page refuses it:\n * a converted lead has its contact already, a closed one was\n * judged not real, and a person with an erasure pending must not\n * be captured again — a conversion is a capture (AGL-2623).\n */\n const converted = Boolean(row.convertedContactId)\n const erasurePending = Aglyn.readErasureRequestedAtMs(row) !== null\n const convertRefusal = converted\n ? 'This lead was converted'\n : !Aglyn.isCrmLeadOpen(row)\n ? 'This lead was unqualified'\n : erasurePending\n ? CONVERT_PENDING_ERASURE_REASON\n : null\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{ display: 'flex', alignItems: 'center', height: '100%' }}\n >\n <RowActionsMenu\n label={String(row['email'] ?? row.$id)}\n items={[\n {\n key: 'open',\n label: 'Open lead',\n icon: (\n <MdiIcon path={mdiAccountArrowRight.path} size={0.8} />\n ),\n href: routes.lead(row.leadId),\n },\n {\n key: 'convert',\n label: 'Convert…',\n icon: (\n <MdiIcon\n path={mdiAccountConvertOutline.path}\n size={0.8}\n />\n ),\n onClick: () => setConverting(row),\n disabled: convertRefusal !== null,\n disabledReason: convertRefusal ?? undefined,\n },\n {\n key: 'assign',\n label: 'Assign owner',\n icon: (\n <MdiIcon path={mdiAccountTieOutline.path} size={0.8} />\n ),\n onClick: () => setAssigning(row),\n },\n {\n key: 'unqualify',\n label: 'Unqualify',\n icon: (\n <MdiIcon path={mdiAccountCancelOutline.path} size={0.8} />\n ),\n onClick: () => setUnqualifying(row),\n disabled:\n !Aglyn.isCrmLeadOpen(row) ||\n Boolean(row.convertedContactId),\n disabledReason: row.convertedContactId\n ? 'This lead was converted'\n : 'This lead is already closed',\n },\n ]}\n />\n </Box>\n )\n },\n },\n ],\n [\n roster,\n routes,\n writeLead,\n hostId,\n viewingHostIds,\n org,\n mount,\n campaignName,\n leadFields.active,\n leadSourceList.picklist,\n ],\n )\n /*\n * The column and sort models are the view's (AGL-2617); the filterable\n * columns are the declared fields, as selects over their choices (AGL-3313).\n */\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns)\n\n return (\n <>\n <CardDisplay\n header={'Leads'}\n help={Aglyn.pluginDocsHelp('crmLeads', {\n anchor: '#filter-the-leads',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <LeadImportButton hostId={hostId} orgId={orgId} />\n <Button size=\"small\" onClick={handleExport} disabled={!rows.length}>\n {'Export CSV'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New lead'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n {/* Which surfaces file a lead, by name (AGL-2612) — under a site its own, at the org level every site's (AGL-2638). */}\n {hostId ? (\n <LeadSurfacesNote hostId={hostId} />\n ) : (\n <OrgLeadSurfacesNote />\n )}\n {/*\n The saved view, and every clause it is narrowed by as a chip\n (AGL-3313). The clauses are set in the grid's own Filters panel;\n the chips show the whole set, which that panel shows one of.\n */}\n <CrmListToolbar label=\"Lead filters\">\n <CrmViewsControl controller={views} allLabel=\"All leads\" />\n <ListFilterChips\n fields={LEAD_LIST_FILTER_FIELDS}\n headers={LEAD_LIST_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={paged.plan.notices} />\n <LeadsBulkBar\n rows={rows}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n roster={roster}\n csv={csvOptions}\n orgId={orgId}\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={rows}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{\n selected: selectedIds,\n onChange: setSelectedIds,\n }}\n loading={status === 'loading'}\n onOpen={(_id, row: LeadRow) => router.push(routes.lead(row.leadId))}\n // Columns are the view's, controlled (AGL-2617). The query\n // orders the list — newest seen first — so the grid sorts\n // nothing itself: a sort over one page would reorder that page.\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortingMode=\"server\"\n disableColumnSorting\n // The panel and the search are the grid's; the query answers\n // both (AGL-3321).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel={opening ? 'No open leads' : 'No leads match these filters'}\n noRowsDescription={\n opening\n ? 'Bookings and lead-routed forms on your site become leads on their own — or add one with New lead, or bring a list in with Import CSV.'\n : undefined\n }\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n </CardDisplay>\n <NewLeadDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n hostId={hostId ?? null}\n org={org as Record<string, unknown> | undefined}\n busy={createBusy}\n error={createError}\n roster={roster}\n orgId={orgId}\n onSubmit={(values) => void handleCreate(values)}\n />\n <AssignOwnerDialog\n lead={assigning}\n roster={roster}\n onClose={() => setAssigning(null)}\n onAssign={(uid) => {\n if (!assigning) return\n void writeLead(\n assigning,\n { ownerUid: uid || deleteField() },\n uid ? 'Owner assigned' : 'Owner cleared',\n )\n setAssigning(null)\n }}\n />\n <LeadUnqualifyDialog\n open={Boolean(unqualifying)}\n onClose={() => setUnqualifying(null)}\n hostId={hostId ?? null}\n leadId={unqualifying?.leadId ?? ''}\n leadLabel={String(\n unqualifying?.['name'] || unqualifying?.['email'] || '',\n )}\n />\n {/* The site the conversion is filed as (AGL-2641), which since\n AGL-3275 is the first site that captured this person rather than\n \"the site the row lives under\" — a lead shared by a consent group\n lives under none of them in particular. Under a site the mounted\n one is used, and the two agree for every single-brand org. */}\n <LeadConvertDialog\n open={Boolean(converting)}\n onClose={() => setConverting(null)}\n hostId={\n hostId ??\n (leadPrimaryGroup(converting, org as Record<string, unknown>).hostId || null)\n }\n orgId={orgId}\n org={org as Record<string, unknown> | undefined}\n leadId={converting?.leadId ?? ''}\n lead={converting ?? {}}\n basePath={basePath ?? ''}\n roster={roster}\n />\n </>\n )\n}\nCrmLeadsSection.displayName = 'CrmLeadsSection'\n\n/**\n * The status, editable in place for a lead that is still open.\n *\n * A converted lead shows the chip alone: its status IS the conversion, and\n * the route stamped it. The select stops its click from reaching the row, or\n * every status change would also open the record.\n */\nfunction InlineStatus(props: {\n lead: LeadRow\n onChange: (next: CrmLeadStatus) => void\n}) {\n const { lead, onChange } = props\n if (lead.convertedContactId) {\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', height: '100%' }}>\n <LeadStatusChip lead={lead} />\n </Box>\n )\n }\n const status = Aglyn.crmLeadStatus(lead)\n return (\n <Box\n onClick={(event) => event.stopPropagation()}\n sx={{\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n width: '100%',\n }}\n >\n <Select\n size=\"small\"\n variant=\"standard\"\n disableUnderline\n value={status}\n onChange={(event) => onChange(event.target.value as CrmLeadStatus)}\n renderValue={() => <LeadStatusChip lead={lead} />}\n sx={{ width: '100%' }}\n >\n <MenuItem value=\"new\">{Aglyn.CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {Aglyn.CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n <MenuItem value=\"unqualified\">{`${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`}</MenuItem>\n </Select>\n </Box>\n )\n}\nInlineStatus.displayName = 'InlineStatus'\n\n/** Hand a lead to a team member. */\nfunction AssignOwnerDialog(props: {\n lead: LeadRow | null\n roster: OrgMemberOptions\n onClose: () => void\n onAssign: (uid: string) => void\n}) {\n const { lead, roster, onClose, onAssign } = props\n const [uid, setUid] = useState<string | null>(null)\n const current = uid ?? String(lead?.ownerUid ?? '')\n return (\n <Dialog\n open={Boolean(lead)}\n onClose={onClose}\n maxWidth=\"xs\"\n fullWidth\n slotProps={{ transition: { onExited: () => setUid(null) } }}\n >\n <DialogTitle>{`Assign ${String(lead?.['name'] || lead?.['email'] || 'lead')}`}</DialogTitle>\n <DialogContent>\n <Box sx={{ pt: 1 }}>\n <LeadOwnerSelect\n value={current}\n onChange={setUid}\n roster={roster}\n size=\"medium\"\n />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={() => onAssign(current)}>\n {'Assign'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nAssignOwnerDialog.displayName = 'AssignOwnerDialog'\n\nexport default CrmLeadsSection\n"],"names":["Aglyn","mdiAccountArrowRight","mdiAccountCancelOutline","mdiAccountConvertOutline","mdiAccountTieOutline","CardDisplay","MdiIcon","ListPagination","ListTable","useOrgMemberOptions","useCrmOrgMount","useCrmSavedView","useCrmScope","useCrmCampaigns","scopeTokensForHost","useContactFieldDefinitions","customFieldColumns","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","useCrmFoldsScope","useCrmListQuery","CrmViewsControl","CrmListActions","CrmListToolbar","RowActionsMenu","ListQueryNotices","useSnackbar","useFirestore","Box","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Select","Stack","Typography","deleteField","doc","serverTimestamp","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","leadPrimaryGroup","downloadTextFile","crmRoutes","LEAD_EMAIL_FILTER_OPTIONS","LEAD_FILTER_CODECS","LEAD_LIST_DECLARATION","LEAD_LIST_FILTER_FIELDS","LEAD_LIST_FILTER_HEADERS","LEAD_SOURCE_FILTER_NONE","LEAD_STATUS_FILTER_OPTIONS","leadClausesForGrid","LEAD_PREFIX_SEARCH","leadClauseImpliesScope","leadClausesToStore","leadQueryClause","crmAskClauses","crmQueryRefusals","listFilterGridColumns","useListGridFilter","ListFilterChips","useLeadSourcePicklist","leadsCsv","LeadConvertDialog","leadSourceLabel","leadSources","leadTimeLabel","LeadImportButton","NewLeadDrawer","useCrmApi","LeadOwnerSelect","CONVERT_PENDING_ERASURE_REASON","CrmEmailStateChip","LeadStatusChip","CrmShareChipView","crmShareChipFor","useCrmSharingFollowUp","LeadSurfacesNote","LeadUnqualifyDialog","LeadsBulkBar","OrgLeadSurfacesNote","CrmLeadsSection","props","hostId","org","basePath","firestore","router","enqueueSnackbar","orgId","createHostId","consentGroup","viewingHostIds","hostIds","mount","roster","leadFields","leadSourceList","routes","scopeTokens","dataRoot","foldsScope","views","section","clauses","state","filters","setClauses","next","setFilters","gridFilter","onChange","selectFields","codecs","campaigns","enabled","campaignName","id","options","find","option","value","label","filterOptions","stale","field","known","filter","clause","op","flatMap","split","map","trim","some","campaignOptions","sourceOptions","picklist","values","active","status","emailState","ownerUid","uid","crmMemberPickerLabel","campaignIds","leadSource","asked","searchKey","searchWords","join","paged","scope","collection","visibleTo","declaration","search","impliesScope","prefixSearch","rows","row","leadId","$id","refused","plan","fields","headers","opening","length","selectedIds","setSelectedIds","page","csvOptions","ownerEmail","emailFor","siteName","handleExport","assigning","setAssigning","unqualifying","setUnqualifying","converting","setConverting","crmApi","createOpen","setCreateOpen","createBusy","setCreateBusy","createError","setCreateError","handleCreate","response","payload","email","name","company","jobTitle","phone","website","address","tags","notes","custom","ok","String","variant","persist","error","console","followUpSharing","writeLead","lead","done","updatedAt","Error","message","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","spacing","sx","justifyContent","height","direction","alignItems","noWrap","chip","color","crmLeadStatus","InlineStatus","unqualifiedReason","readEmailState","EMAIL_STATE_LABELS","labelFor","held","Array","isArray","sortComparator","a","b","crmPicklistRank","localeCompare","readCampaignIds","width","sortable","filterable","disableColumnMenu","converted","Boolean","convertedContactId","erasurePending","readErasureRequestedAtMs","convertRefusal","isCrmLeadOpen","onClick","event","stopPropagation","display","items","key","icon","path","size","href","disabled","disabledReason","undefined","filterColumns","grid","header","help","pluginDocsHelp","anchor","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","marksServed","notices","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","onOpen","_id","push","columnVisibilityModel","onColumnVisibilityModelChange","sortingMode","disableColumnSorting","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","noRowsDescription","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","busy","onSubmit","AssignOwnerDialog","onAssign","leadLabel","displayName","disableUnderline","target","renderValue","CRM_LEAD_STATUS_LABELS","new","working","unqualified","setUid","current","maxWidth","fullWidth","slotProps","transition","onExited","pt"],"mappings":"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"}
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { pluginRecordHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
18
19
  import { AppLink } from "@aglyn/shared-ui-jsx";
19
20
  import { Button, Stack, Typography } from "@mui/material";
20
21
  import { useCallback, useState } from "react";
@@ -124,8 +125,11 @@ OrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote';
124
125
  const name = mount.siteName(host.id);
125
126
  const hubHref = mount.siteHubHref(host.id);
126
127
  const subdomain = mount.siteSubdomain(host.id);
127
- const formHref = useCallback((formId)=>subdomain ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/forms/${encodeURIComponent(formId)}` : null, [
128
- mount.hostsPath,
128
+ const formHref = useCallback((formId)=>subdomain ? pluginRecordHref('form', {
129
+ orgSlug: mount.orgSlug,
130
+ host: subdomain
131
+ }, formId) : null, [
132
+ mount.orgSlug,
129
133
  subdomain
130
134
  ]);
131
135
  return /*#__PURE__*/ _jsxs(Stack, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/org-lead-surfaces-note.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\n'use client'\n\nimport type { ConsolePluginOrgHost } from '@aglyn/aglyn'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { LeadSurfaceForm } from '../model/lead-surfaces'\nimport {\n LEAD_SURFACES_CONTACTS_TOO,\n LEAD_SURFACES_INTRO,\n type LeadRoutingTarget,\n LeadSurfaceFormList,\n UnroutedLeadSurfaces,\n useLeadSurfaceForms,\n useTurnOnLeadRouting,\n} from './lead-surfaces-note'\n\n/**\n * How many sites the note opens with their forms showing. The rest sit\n * behind a count until asked for — and, since a site's forms are read by\n * the group that shows them, are not read until then either.\n */\nexport const ORG_LEAD_SURFACES_OPEN_SITES = 3\n\n/**\n * WHAT CREATES A LEAD, ACROSS THE ORGANIZATION (AGL-2638).\n *\n * The org-level Leads section lists every site's leads in one table, and\n * for a while said nothing about why a form's people were in it: the\n * per-site note was drawn only under a site. This is the same note grouped\n * by site — each site's routed forms, the forms that could route with the\n * switch beside them, and the forms that cannot with the reason as the\n * tooltip — over the sites the mount already bounds. The always-on\n * surfaces are named once at the top, because a member sign-up files a\n * lead on every site and saying so three times would suggest otherwise.\n *\n * ## Bounded two ways\n *\n * The sites come from the mount, which the shell already capped; and only\n * the first {@link ORG_LEAD_SURFACES_OPEN_SITES} groups mount their reader\n * until the reader opens the rest, so an org with twenty sites opens three\n * listeners on arrival and not twenty.\n *\n * ## Links\n *\n * A site's name opens its own Leads section, and a form its own page, both\n * built from the mount's `hostsPath` and the site's subdomain the way the\n * mount builds a site's hub; a site whose subdomain never resolved is named\n * and not linked. No host-index read per site: the mount already answered.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the note, which keeps the section's one-line choice\n * between the two notes honest.\n */\nexport function OrgLeadSurfacesNote() {\n const mount = useCrmOrgMount()\n const { turningOn, turnOn } = useTurnOnLeadRouting()\n const [opened, setOpened] = useState(false)\n if (!mount) return null\n const { hosts, hostsReady } = mount\n const shown = opened ? hosts : hosts.slice(0, ORG_LEAD_SURFACES_OPEN_SITES)\n const folded = hosts.length - shown.length\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${LEAD_SURFACES_INTRO}, on every site. ${LEAD_SURFACES_CONTACTS_TOO}`}\n </Typography>\n {!hostsReady ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'…'}\n </Typography>\n ) : !hosts.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet.'}\n </Typography>\n ) : (\n <>\n {shown.map((host) => (\n <SiteLeadSurfaces\n key={host.id}\n host={host}\n mount={mount}\n turningOn={turningOn}\n onTurnOn={(form) =>\n void turnOn({ hostId: host.id, form, siteName: mount.siteName(host.id) })\n }\n />\n ))}\n {folded > 0 ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(true)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show ${folded} more ${folded === 1 ? 'site' : 'sites'}`}\n </Button>\n ) : hosts.length > ORG_LEAD_SURFACES_OPEN_SITES ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(false)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show the first ${ORG_LEAD_SURFACES_OPEN_SITES} sites only`}\n </Button>\n ) : null}\n </>\n )}\n </Stack>\n )\n}\nOrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote'\n\n/**\n * One site's group: its name, then the same two rows the site note draws,\n * read by the same reader. A site with no forms says so in one line rather\n * than drawing an empty \"no form routes leads yet\" that reads like a\n * verdict on forms that do not exist.\n */\nfunction SiteLeadSurfaces(props: {\n host: ConsolePluginOrgHost\n mount: CrmOrgMount\n turningOn: LeadRoutingTarget | null\n onTurnOn: (form: LeadSurfaceForm) => void\n}) {\n const { host, mount, turningOn, onTurnOn } = props\n const { routed, unrouted, truncated, status } = useLeadSurfaceForms(host.id)\n const name = mount.siteName(host.id)\n const hubHref = mount.siteHubHref(host.id)\n const subdomain = mount.siteSubdomain(host.id)\n const formHref = useCallback(\n (formId: string) =>\n subdomain\n ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/forms/${encodeURIComponent(formId)}`\n : null,\n [mount.hostsPath, subdomain],\n )\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle2\" component=\"div\">\n {hubHref ? (\n <AppLink href={`${hubHref}/leads`} underline=\"hover\">\n {name}\n </AppLink>\n ) : (\n name\n )}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"div\">\n {status === 'loading' ? (\n '…'\n ) : status === 'error' ? (\n 'The forms on this site could not be read.'\n ) : !routed.length && !unrouted.length ? (\n 'No forms on this site yet.'\n ) : routed.length ? (\n <>\n {'Routing leads: '}\n <LeadSurfaceFormList forms={routed} truncated={truncated} formHref={formHref} />\n {'.'}\n </>\n ) : (\n 'No form routes leads yet.'\n )}\n </Typography>\n {unrouted.length ? (\n <UnroutedLeadSurfaces\n hostId={host.id}\n forms={unrouted}\n formHref={formHref}\n turningOn={turningOn}\n onTurnOn={onTurnOn}\n />\n ) : null}\n </Stack>\n )\n}\nSiteLeadSurfaces.displayName = 'SiteLeadSurfaces'\n\nexport default OrgLeadSurfacesNote\n"],"names":["AppLink","Button","Stack","Typography","useCallback","useState","useCrmOrgMount","LEAD_SURFACES_CONTACTS_TOO","LEAD_SURFACES_INTRO","LeadSurfaceFormList","UnroutedLeadSurfaces","useLeadSurfaceForms","useTurnOnLeadRouting","ORG_LEAD_SURFACES_OPEN_SITES","OrgLeadSurfacesNote","mount","turningOn","turnOn","opened","setOpened","hosts","hostsReady","shown","slice","folded","length","spacing","variant","color","map","host","SiteLeadSurfaces","onTurnOn","form","hostId","id","siteName","size","onClick","sx","alignSelf","displayName","props","routed","unrouted","truncated","status","name","hubHref","siteHubHref","subdomain","siteSubdomain","formHref","formId","hostsPath","encodeURIComponent","component","href","underline","forms"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/org-lead-surfaces-note.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\n'use client'\n\nimport type { ConsolePluginOrgHost } from '@aglyn/aglyn'\nimport { pluginRecordHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { LeadSurfaceForm } from '../model/lead-surfaces'\nimport {\n LEAD_SURFACES_CONTACTS_TOO,\n LEAD_SURFACES_INTRO,\n type LeadRoutingTarget,\n LeadSurfaceFormList,\n UnroutedLeadSurfaces,\n useLeadSurfaceForms,\n useTurnOnLeadRouting,\n} from './lead-surfaces-note'\n\n/**\n * How many sites the note opens with their forms showing. The rest sit\n * behind a count until asked for — and, since a site's forms are read by\n * the group that shows them, are not read until then either.\n */\nexport const ORG_LEAD_SURFACES_OPEN_SITES = 3\n\n/**\n * WHAT CREATES A LEAD, ACROSS THE ORGANIZATION (AGL-2638).\n *\n * The org-level Leads section lists every site's leads in one table, and\n * for a while said nothing about why a form's people were in it: the\n * per-site note was drawn only under a site. This is the same note grouped\n * by site — each site's routed forms, the forms that could route with the\n * switch beside them, and the forms that cannot with the reason as the\n * tooltip — over the sites the mount already bounds. The always-on\n * surfaces are named once at the top, because a member sign-up files a\n * lead on every site and saying so three times would suggest otherwise.\n *\n * ## Bounded two ways\n *\n * The sites come from the mount, which the shell already capped; and only\n * the first {@link ORG_LEAD_SURFACES_OPEN_SITES} groups mount their reader\n * until the reader opens the rest, so an org with twenty sites opens three\n * listeners on arrival and not twenty.\n *\n * ## Links\n *\n * A site's name opens its own Leads section, and a form its own page, both\n * built from the mount's `hostsPath` and the site's subdomain the way the\n * mount builds a site's hub; a site whose subdomain never resolved is named\n * and not linked. No host-index read per site: the mount already answered.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the note, which keeps the section's one-line choice\n * between the two notes honest.\n */\nexport function OrgLeadSurfacesNote() {\n const mount = useCrmOrgMount()\n const { turningOn, turnOn } = useTurnOnLeadRouting()\n const [opened, setOpened] = useState(false)\n if (!mount) return null\n const { hosts, hostsReady } = mount\n const shown = opened ? hosts : hosts.slice(0, ORG_LEAD_SURFACES_OPEN_SITES)\n const folded = hosts.length - shown.length\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${LEAD_SURFACES_INTRO}, on every site. ${LEAD_SURFACES_CONTACTS_TOO}`}\n </Typography>\n {!hostsReady ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'…'}\n </Typography>\n ) : !hosts.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet.'}\n </Typography>\n ) : (\n <>\n {shown.map((host) => (\n <SiteLeadSurfaces\n key={host.id}\n host={host}\n mount={mount}\n turningOn={turningOn}\n onTurnOn={(form) =>\n void turnOn({ hostId: host.id, form, siteName: mount.siteName(host.id) })\n }\n />\n ))}\n {folded > 0 ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(true)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show ${folded} more ${folded === 1 ? 'site' : 'sites'}`}\n </Button>\n ) : hosts.length > ORG_LEAD_SURFACES_OPEN_SITES ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(false)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show the first ${ORG_LEAD_SURFACES_OPEN_SITES} sites only`}\n </Button>\n ) : null}\n </>\n )}\n </Stack>\n )\n}\nOrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote'\n\n/**\n * One site's group: its name, then the same two rows the site note draws,\n * read by the same reader. A site with no forms says so in one line rather\n * than drawing an empty \"no form routes leads yet\" that reads like a\n * verdict on forms that do not exist.\n */\nfunction SiteLeadSurfaces(props: {\n host: ConsolePluginOrgHost\n mount: CrmOrgMount\n turningOn: LeadRoutingTarget | null\n onTurnOn: (form: LeadSurfaceForm) => void\n}) {\n const { host, mount, turningOn, onTurnOn } = props\n const { routed, unrouted, truncated, status } = useLeadSurfaceForms(host.id)\n const name = mount.siteName(host.id)\n const hubHref = mount.siteHubHref(host.id)\n const subdomain = mount.siteSubdomain(host.id)\n const formHref = useCallback(\n (formId: string) =>\n subdomain ? pluginRecordHref('form', { orgSlug: mount.orgSlug, host: subdomain }, formId) : null,\n [mount.orgSlug, subdomain],\n )\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle2\" component=\"div\">\n {hubHref ? (\n <AppLink href={`${hubHref}/leads`} underline=\"hover\">\n {name}\n </AppLink>\n ) : (\n name\n )}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"div\">\n {status === 'loading' ? (\n '…'\n ) : status === 'error' ? (\n 'The forms on this site could not be read.'\n ) : !routed.length && !unrouted.length ? (\n 'No forms on this site yet.'\n ) : routed.length ? (\n <>\n {'Routing leads: '}\n <LeadSurfaceFormList forms={routed} truncated={truncated} formHref={formHref} />\n {'.'}\n </>\n ) : (\n 'No form routes leads yet.'\n )}\n </Typography>\n {unrouted.length ? (\n <UnroutedLeadSurfaces\n hostId={host.id}\n forms={unrouted}\n formHref={formHref}\n turningOn={turningOn}\n onTurnOn={onTurnOn}\n />\n ) : null}\n </Stack>\n )\n}\nSiteLeadSurfaces.displayName = 'SiteLeadSurfaces'\n\nexport default OrgLeadSurfacesNote\n"],"names":["pluginRecordHref","AppLink","Button","Stack","Typography","useCallback","useState","useCrmOrgMount","LEAD_SURFACES_CONTACTS_TOO","LEAD_SURFACES_INTRO","LeadSurfaceFormList","UnroutedLeadSurfaces","useLeadSurfaceForms","useTurnOnLeadRouting","ORG_LEAD_SURFACES_OPEN_SITES","OrgLeadSurfacesNote","mount","turningOn","turnOn","opened","setOpened","hosts","hostsReady","shown","slice","folded","length","spacing","variant","color","map","host","SiteLeadSurfaces","onTurnOn","form","hostId","id","siteName","size","onClick","sx","alignSelf","displayName","props","routed","unrouted","truncated","status","name","hubHref","siteHubHref","subdomain","siteSubdomain","formHref","formId","orgSlug","component","href","underline","forms"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAGA,SAASA,gBAAgB,QAAQ,mDAAkD;AACnF,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzD,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7C,SAA2BC,cAAc,QAAQ,gCAA4B;AAE7E,SACEC,0BAA0B,EAC1BC,mBAAmB,EAEnBC,mBAAmB,EACnBC,oBAAoB,EACpBC,mBAAmB,EACnBC,oBAAoB,QACf,0BAAsB;AAE7B;;;;CAIC,GACD,OAAO,MAAMC,+BAA+B,EAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC;IACd,MAAMC,QAAQT;IACd,MAAM,EAAEU,SAAS,EAAEC,MAAM,EAAE,GAAGL;IAC9B,MAAM,CAACM,QAAQC,UAAU,GAAGd,SAAS;IACrC,IAAI,CAACU,OAAO,OAAO;IACnB,MAAM,EAAEK,KAAK,EAAEC,UAAU,EAAE,GAAGN;IAC9B,MAAMO,QAAQJ,SAASE,QAAQA,MAAMG,KAAK,CAAC,GAAGV;IAC9C,MAAMW,SAASJ,MAAMK,MAAM,GAAGH,MAAMG,MAAM;IAC1C,qBACE,MAACvB;QAAMwB,SAAS;;0BACd,KAACvB;gBAAWwB,SAAQ;gBAAQC,OAAM;0BAC/B,GAAGpB,oBAAoB,iBAAiB,EAAED,4BAA4B;;YAExE,CAACc,2BACA,KAAClB;gBAAWwB,SAAQ;gBAAUC,OAAM;0BACjC;iBAED,CAACR,MAAMK,MAAM,iBACf,KAACtB;gBAAWwB,SAAQ;gBAAQC,OAAM;0BAC/B;+BAGH;;oBACGN,MAAMO,GAAG,CAAC,CAACC,qBACV,KAACC;4BAECD,MAAMA;4BACNf,OAAOA;4BACPC,WAAWA;4BACXgB,UAAU,CAACC,OACT,KAAKhB,OAAO;oCAAEiB,QAAQJ,KAAKK,EAAE;oCAAEF;oCAAMG,UAAUrB,MAAMqB,QAAQ,CAACN,KAAKK,EAAE;gCAAE;2BALpEL,KAAKK,EAAE;oBASfX,SAAS,kBACR,KAACvB;wBACCoC,MAAK;wBACLV,SAAQ;wBACRW,SAAS,IAAMnB,UAAU;wBACzBoB,IAAI;4BAAEC,WAAW;wBAAa;kCAE7B,CAAC,KAAK,EAAEhB,OAAO,MAAM,EAAEA,WAAW,IAAI,SAAS,SAAS;yBAEzDJ,MAAMK,MAAM,GAAGZ,6CACjB,KAACZ;wBACCoC,MAAK;wBACLV,SAAQ;wBACRW,SAAS,IAAMnB,UAAU;wBACzBoB,IAAI;4BAAEC,WAAW;wBAAa;kCAE7B,CAAC,eAAe,EAAE3B,6BAA6B,WAAW,CAAC;yBAE5D;;;;;AAKd;AACAC,oBAAoB2B,WAAW,GAAG;AAElC;;;;;CAKC,GACD,SAASV,iBAAiBW,KAKzB;IACC,MAAM,EAAEZ,IAAI,EAAEf,KAAK,EAAEC,SAAS,EAAEgB,QAAQ,EAAE,GAAGU;IAC7C,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGnC,oBAAoBmB,KAAKK,EAAE;IAC3E,MAAMY,OAAOhC,MAAMqB,QAAQ,CAACN,KAAKK,EAAE;IACnC,MAAMa,UAAUjC,MAAMkC,WAAW,CAACnB,KAAKK,EAAE;IACzC,MAAMe,YAAYnC,MAAMoC,aAAa,CAACrB,KAAKK,EAAE;IAC7C,MAAMiB,WAAWhD,YACf,CAACiD,SACCH,YAAYnD,iBAAiB,QAAQ;YAAEuD,SAASvC,MAAMuC,OAAO;YAAExB,MAAMoB;QAAU,GAAGG,UAAU,MAC9F;QAACtC,MAAMuC,OAAO;QAAEJ;KAAU;IAE5B,qBACE,MAAChD;QAAMwB,SAAS;;0BACd,KAACvB;gBAAWwB,SAAQ;gBAAY4B,WAAU;0BACvCP,wBACC,KAAChD;oBAAQwD,MAAM,GAAGR,QAAQ,MAAM,CAAC;oBAAES,WAAU;8BAC1CV;qBAGHA;;0BAGJ,KAAC5C;gBAAWwB,SAAQ;gBAAQC,OAAM;gBAAiB2B,WAAU;0BAC1DT,WAAW,YACV,MACEA,WAAW,UACb,8CACE,CAACH,OAAOlB,MAAM,IAAI,CAACmB,SAASnB,MAAM,GACpC,+BACEkB,OAAOlB,MAAM,iBACf;;wBACG;sCACD,KAAChB;4BAAoBiD,OAAOf;4BAAQE,WAAWA;4BAAWO,UAAUA;;wBACnE;;qBAGH;;YAGHR,SAASnB,MAAM,iBACd,KAACf;gBACCwB,QAAQJ,KAAKK,EAAE;gBACfuB,OAAOd;gBACPQ,UAAUA;gBACVpC,WAAWA;gBACXgB,UAAUA;iBAEV;;;AAGV;AACAD,iBAAiBU,WAAW,GAAG;AAE/B,eAAe3B,oBAAmB"}
@@ -17,6 +17,7 @@
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
19
  import { checkEntitlement, CRM_ACTION_RECIPES, planLabelGrantingFeature, pluginDocsHelp } from "@aglyn/aglyn";
20
+ import { pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
20
21
  import { AppLink, CardDisplay, SrOnly } from "@aglyn/shared-ui-jsx";
21
22
  import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
22
23
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
@@ -28,12 +29,16 @@ import { useCrmOrgMount } from "../hooks/use-crm-org-mount.js";
28
29
  import { useCrmApi } from "./use-crm-api.js";
29
30
  /** How many of a site's forms the picker offers before it says it stopped. */ const FORM_OPTION_CEILING = 100;
30
31
  /**
31
- * The site's Automation → Actions page — where an installed action lives
32
- * and is edited — or `null` for a site whose subdomain the mount could not
33
- * answer, which is named and not linked.
32
+ * The site's list of actions — where an installed action lives and is
33
+ * edited — at the address the plugin that keeps actions publishes, or `null`
34
+ * for a site whose subdomain the mount could not answer or a workspace where
35
+ * no plugin keeps actions, which is named and not linked.
34
36
  */ function siteActionsHref(mount, hostId) {
35
37
  const subdomain = mount.siteSubdomain(hostId);
36
- return subdomain ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/automation/actions` : null;
38
+ return subdomain ? pluginRecordListHref('action', {
39
+ orgSlug: mount.orgSlug,
40
+ host: subdomain
41
+ }) : null;
37
42
  }
38
43
  function standingOf(status, hostId, recipeId) {
39
44
  const site = status == null ? void 0 : status.get(hostId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's Automation → Actions page — where an installed action lives\n * and is edited — or `null` for a site whose subdomain the mount could not\n * answer, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/automation/actions`\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.needs === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","CRM_ACTION_RECIPES","planLabelGrantingFeature","pluginDocsHelp","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","hostsPath","encodeURIComponent","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","host","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","needs","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { pluginRecordListHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's list of actions — where an installed action lives and is\n * edited — at the address the plugin that keeps actions publishes, or `null`\n * for a site whose subdomain the mount could not answer or a workspace where\n * no plugin keeps actions, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? pluginRecordListHref('action', { orgSlug: mount.orgSlug, host: subdomain })\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.needs === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","CRM_ACTION_RECIPES","planLabelGrantingFeature","pluginDocsHelp","pluginRecordListHref","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","orgSlug","host","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","needs","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"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"}