@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.222

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 (514) hide show
  1. package/README.md +2 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/activity-list.js +12 -5
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.d.ts +1 -4
  6. package/src/lib/components/companies-bulk-bar.js +7 -20
  7. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  8. package/src/lib/components/companies-section.d.ts +3 -3
  9. package/src/lib/components/companies-section.js +149 -46
  10. package/src/lib/components/companies-section.js.map +1 -1
  11. package/src/lib/components/company-edit-drawer.js +281 -20
  12. package/src/lib/components/company-edit-drawer.js.map +1 -1
  13. package/src/lib/components/company-properties-card.js +99 -8
  14. package/src/lib/components/company-properties-card.js.map +1 -1
  15. package/src/lib/components/contact-detail-page.js +3 -1
  16. package/src/lib/components/contact-detail-page.js.map +1 -1
  17. package/src/lib/components/contact-merge-dialog.js +33 -10
  18. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  19. package/src/lib/components/contact-properties-card.d.ts +0 -69
  20. package/src/lib/components/contact-properties-card.js +350 -60
  21. package/src/lib/components/contact-properties-card.js.map +1 -1
  22. package/src/lib/components/contact-reports-to-field.d.ts +46 -0
  23. package/src/lib/components/contact-reports-to-field.js +67 -0
  24. package/src/lib/components/contact-reports-to-field.js.map +1 -0
  25. package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
  26. package/src/lib/components/contacts-bulk-bar.js +7 -19
  27. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  28. package/src/lib/components/contacts-section.js +46 -33
  29. package/src/lib/components/contacts-section.js.map +1 -1
  30. package/src/lib/components/crm-call-actions.d.ts +10 -0
  31. package/src/lib/components/crm-call-actions.js +26 -9
  32. package/src/lib/components/crm-call-actions.js.map +1 -1
  33. package/src/lib/components/crm-record-picker.d.ts +15 -0
  34. package/src/lib/components/crm-record-picker.js +16 -10
  35. package/src/lib/components/crm-record-picker.js.map +1 -1
  36. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  37. package/src/lib/components/crm-transfer-buttons.js +167 -0
  38. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  39. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  40. package/src/lib/components/deal-contact-roles-card.js +368 -0
  41. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  42. package/src/lib/components/deal-detail-page.d.ts +3 -2
  43. package/src/lib/components/deal-detail-page.js +25 -6
  44. package/src/lib/components/deal-detail-page.js.map +1 -1
  45. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  46. package/src/lib/components/deal-edit-drawer.js +219 -10
  47. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  48. package/src/lib/components/deal-properties-card.d.ts +7 -0
  49. package/src/lib/components/deal-properties-card.js +34 -3
  50. package/src/lib/components/deal-properties-card.js.map +1 -1
  51. package/src/lib/components/deal-stage-card.d.ts +4 -0
  52. package/src/lib/components/deal-stage-card.js +10 -2
  53. package/src/lib/components/deal-stage-card.js.map +1 -1
  54. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  55. package/src/lib/components/deals-bulk-bar.js +7 -20
  56. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  57. package/src/lib/components/deals-section.d.ts +3 -3
  58. package/src/lib/components/deals-section.js +123 -63
  59. package/src/lib/components/deals-section.js.map +1 -1
  60. package/src/lib/components/fields-section.js +31 -17
  61. package/src/lib/components/fields-section.js.map +1 -1
  62. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  63. package/src/lib/components/lead-convert-dialog.js +23 -1
  64. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  65. package/src/lib/components/lead-detail-page.js +8 -2
  66. package/src/lib/components/lead-detail-page.js.map +1 -1
  67. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  68. package/src/lib/components/lead-profile-fields.js +386 -0
  69. package/src/lib/components/lead-profile-fields.js.map +1 -0
  70. package/src/lib/components/lead-properties-card.d.ts +2 -1
  71. package/src/lib/components/lead-properties-card.js +85 -176
  72. package/src/lib/components/lead-properties-card.js.map +1 -1
  73. package/src/lib/components/lead-source-select.d.ts +4 -23
  74. package/src/lib/components/lead-source-select.js +9 -73
  75. package/src/lib/components/lead-source-select.js.map +1 -1
  76. package/src/lib/components/lead-status-chip.d.ts +9 -3
  77. package/src/lib/components/lead-status-chip.js +8 -4
  78. package/src/lib/components/lead-status-chip.js.map +1 -1
  79. package/src/lib/components/lead-status-options.d.ts +20 -0
  80. package/src/lib/components/lead-status-options.js +51 -0
  81. package/src/lib/components/lead-status-options.js.map +1 -0
  82. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  83. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  84. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  85. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  86. package/src/lib/components/leads-bulk-bar.js +29 -29
  87. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  88. package/src/lib/components/leads-section.js +120 -68
  89. package/src/lib/components/leads-section.js.map +1 -1
  90. package/src/lib/components/linked-deals-card.d.ts +4 -0
  91. package/src/lib/components/linked-deals-card.js +47 -3
  92. package/src/lib/components/linked-deals-card.js.map +1 -1
  93. package/src/lib/components/log-activity-dialog.js +43 -3
  94. package/src/lib/components/log-activity-dialog.js.map +1 -1
  95. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  96. package/src/lib/components/new-contact-drawer.js +108 -6
  97. package/src/lib/components/new-contact-drawer.js.map +1 -1
  98. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  99. package/src/lib/components/new-lead-drawer.js +88 -121
  100. package/src/lib/components/new-lead-drawer.js.map +1 -1
  101. package/src/lib/components/picklist-select.d.ts +36 -0
  102. package/src/lib/components/picklist-select.js +114 -0
  103. package/src/lib/components/picklist-select.js.map +1 -0
  104. package/src/lib/components/picklist-values-card.d.ts +34 -0
  105. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  106. package/src/lib/components/picklist-values-card.js.map +1 -0
  107. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  108. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  109. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  110. package/src/lib/components/pipelines-dialog.js +15 -6
  111. package/src/lib/components/pipelines-dialog.js.map +1 -1
  112. package/src/lib/components/recent-activity-feed.js +20 -5
  113. package/src/lib/components/recent-activity-feed.js.map +1 -1
  114. package/src/lib/components/recipes-card.js +4 -2
  115. package/src/lib/components/recipes-card.js.map +1 -1
  116. package/src/lib/components/record-tasks-card.js +15 -8
  117. package/src/lib/components/record-tasks-card.js.map +1 -1
  118. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  119. package/src/lib/components/reports/forecast-card.js +101 -3
  120. package/src/lib/components/reports/forecast-card.js.map +1 -1
  121. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  122. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  123. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  124. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  125. package/src/lib/components/reports/lead-source-card.js +318 -0
  126. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  127. package/src/lib/components/reports/report-export.js +1 -1
  128. package/src/lib/components/reports/report-export.js.map +1 -1
  129. package/src/lib/components/reports-section.js +8 -0
  130. package/src/lib/components/reports-section.js.map +1 -1
  131. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  132. package/src/lib/components/sharing-rule-drawer.js +108 -19
  133. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  134. package/src/lib/components/sharing-rules-card.js +11 -3
  135. package/src/lib/components/sharing-rules-card.js.map +1 -1
  136. package/src/lib/components/task-cells.d.ts +9 -1
  137. package/src/lib/components/task-cells.js +9 -4
  138. package/src/lib/components/task-cells.js.map +1 -1
  139. package/src/lib/components/task-edit-drawer.js +111 -43
  140. package/src/lib/components/task-edit-drawer.js.map +1 -1
  141. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  142. package/src/lib/components/tasks-bulk-bar.js +7 -20
  143. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  144. package/src/lib/components/tasks-calendar.d.ts +3 -0
  145. package/src/lib/components/tasks-calendar.js +8 -1
  146. package/src/lib/components/tasks-calendar.js.map +1 -1
  147. package/src/lib/components/tasks-section.d.ts +3 -3
  148. package/src/lib/components/tasks-section.js +58 -43
  149. package/src/lib/components/tasks-section.js.map +1 -1
  150. package/src/lib/constants/api-routes.d.ts +2 -9
  151. package/src/lib/constants/api-routes.js +1 -7
  152. package/src/lib/constants/api-routes.js.map +1 -1
  153. package/src/lib/constants/bundle-common.d.ts +6 -0
  154. package/src/lib/constants/bundle-common.js +11 -0
  155. package/src/lib/constants/bundle-common.js.map +1 -1
  156. package/src/lib/constants/company-filters.d.ts +14 -0
  157. package/src/lib/constants/company-filters.js +51 -1
  158. package/src/lib/constants/company-filters.js.map +1 -1
  159. package/src/lib/constants/contact-filters.d.ts +2 -0
  160. package/src/lib/constants/contact-filters.js +18 -1
  161. package/src/lib/constants/contact-filters.js.map +1 -1
  162. package/src/lib/constants/deal-filters.d.ts +22 -1
  163. package/src/lib/constants/deal-filters.js +67 -4
  164. package/src/lib/constants/deal-filters.js.map +1 -1
  165. package/src/lib/declarations.console-server.d.ts +3 -0
  166. package/src/lib/declarations.console-server.js +35 -0
  167. package/src/lib/declarations.console-server.js.map +1 -1
  168. package/src/lib/declarations.d.ts +26 -0
  169. package/src/lib/declarations.js +35 -0
  170. package/src/lib/declarations.js.map +1 -0
  171. package/src/lib/declarations.server.d.ts +11 -0
  172. package/src/lib/declarations.server.js +92 -1
  173. package/src/lib/declarations.server.js.map +1 -1
  174. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  175. package/src/lib/hooks/use-company-picklists.js +55 -0
  176. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  177. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  178. package/src/lib/hooks/use-crm-picklist.js +53 -0
  179. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  180. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  181. package/src/lib/hooks/use-crm-record-names.js +45 -19
  182. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  183. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  184. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  185. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  186. package/src/lib/hooks/use-deals.d.ts +14 -0
  187. package/src/lib/hooks/use-deals.js +29 -0
  188. package/src/lib/hooks/use-deals.js.map +1 -1
  189. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  190. package/src/lib/hooks/use-lead-picklists.js +46 -0
  191. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  192. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  193. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  194. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  195. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  196. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  197. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  198. package/src/lib/model/companies.d.ts +32 -3
  199. package/src/lib/model/companies.js +133 -16
  200. package/src/lib/model/companies.js.map +1 -1
  201. package/src/lib/model/contact-merge.d.ts +27 -3
  202. package/src/lib/model/contact-merge.js +81 -7
  203. package/src/lib/model/contact-merge.js.map +1 -1
  204. package/src/lib/model/contact-record.d.ts +17 -0
  205. package/src/lib/model/contact-record.js +15 -1
  206. package/src/lib/model/contact-record.js.map +1 -1
  207. package/src/lib/model/contact-update.d.ts +31 -2
  208. package/src/lib/model/contact-update.js +14 -0
  209. package/src/lib/model/contact-update.js.map +1 -1
  210. package/src/lib/model/crm-list-query.d.ts +5 -0
  211. package/src/lib/model/crm-list-query.js +8 -0
  212. package/src/lib/model/crm-list-query.js.map +1 -1
  213. package/src/lib/model/crm-recipes.d.ts +40 -0
  214. package/src/lib/model/crm-recipes.js +244 -0
  215. package/src/lib/model/crm-recipes.js.map +1 -0
  216. package/src/lib/model/crm-record-counts.d.ts +36 -0
  217. package/src/lib/model/crm-record-counts.js +54 -0
  218. package/src/lib/model/crm-record-counts.js.map +1 -0
  219. package/src/lib/model/crm-record-lists.d.ts +23 -0
  220. package/src/lib/model/crm-record-lists.js +161 -0
  221. package/src/lib/model/crm-record-lists.js.map +1 -0
  222. package/src/lib/model/crm-reports.d.ts +73 -3
  223. package/src/lib/model/crm-reports.js +120 -1
  224. package/src/lib/model/crm-reports.js.map +1 -1
  225. package/src/lib/model/crm-sharing.d.ts +21 -3
  226. package/src/lib/model/crm-sharing.js +27 -6
  227. package/src/lib/model/crm-sharing.js.map +1 -1
  228. package/src/lib/model/deal-board-model.d.ts +8 -2
  229. package/src/lib/model/deal-board-model.js +15 -2
  230. package/src/lib/model/deal-board-model.js.map +1 -1
  231. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  232. package/src/lib/model/deal-contact-roles.js +73 -0
  233. package/src/lib/model/deal-contact-roles.js.map +1 -0
  234. package/src/lib/model/deal-form-model.d.ts +56 -0
  235. package/src/lib/model/deal-form-model.js +79 -7
  236. package/src/lib/model/deal-form-model.js.map +1 -1
  237. package/src/lib/model/download-text-file.d.ts +23 -0
  238. package/src/lib/model/download-text-file.js +34 -0
  239. package/src/lib/model/download-text-file.js.map +1 -0
  240. package/src/lib/model/lead-filters.d.ts +36 -2
  241. package/src/lib/model/lead-filters.js +123 -2
  242. package/src/lib/model/lead-filters.js.map +1 -1
  243. package/src/lib/model/lead-source-direction.d.ts +38 -0
  244. package/src/lib/model/lead-source-direction.js +52 -0
  245. package/src/lib/model/lead-source-direction.js.map +1 -0
  246. package/src/lib/model/picklist-values.d.ts +119 -0
  247. package/src/lib/model/picklist-values.js +256 -0
  248. package/src/lib/model/picklist-values.js.map +1 -0
  249. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  250. package/src/lib/model/record-facts-disclosed.js +281 -0
  251. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  252. package/src/lib/model/record-facts.d.ts +215 -34
  253. package/src/lib/model/record-facts.js +307 -128
  254. package/src/lib/model/record-facts.js.map +1 -1
  255. package/src/lib/model/task-picklists.d.ts +52 -0
  256. package/src/lib/model/task-picklists.js +83 -0
  257. package/src/lib/model/task-picklists.js.map +1 -0
  258. package/src/lib/model/task-routes.d.ts +13 -0
  259. package/src/lib/model/task-routes.js +23 -4
  260. package/src/lib/model/task-routes.js.map +1 -1
  261. package/src/lib/plan-entitlements.js +6 -4
  262. package/src/lib/plan-entitlements.js.map +1 -1
  263. package/src/lib/plugin.js +17 -0
  264. package/src/lib/plugin.js.map +1 -1
  265. package/src/lib/server/api-v1/contacts.js +220 -14
  266. package/src/lib/server/api-v1/contacts.js.map +1 -1
  267. package/src/lib/server/api-v1/crm-activities.js +14 -1
  268. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  269. package/src/lib/server/api-v1/crm-companies.js +123 -14
  270. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  271. package/src/lib/server/api-v1/crm-deals.js +291 -26
  272. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  273. package/src/lib/server/api-v1/crm-leads.js +144 -38
  274. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  275. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  276. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  277. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  278. package/src/lib/server/api-v1/crm-tasks.js +75 -33
  279. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  280. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  281. package/src/lib/server/api-v1/openapi.js +171 -16
  282. package/src/lib/server/api-v1/openapi.js.map +1 -1
  283. package/src/lib/server/assign-contact-owner.d.ts +162 -0
  284. package/src/lib/server/assign-contact-owner.js +340 -0
  285. package/src/lib/server/assign-contact-owner.js.map +1 -0
  286. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  287. package/src/lib/server/associate-company-by-domain.js +153 -0
  288. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  289. package/src/lib/server/automation-steps.d.ts +139 -0
  290. package/src/lib/server/automation-steps.js +472 -0
  291. package/src/lib/server/automation-steps.js.map +1 -0
  292. package/src/lib/server/capture-contact.js +65 -21
  293. package/src/lib/server/capture-contact.js.map +1 -1
  294. package/src/lib/server/capture-host-contact.d.ts +104 -0
  295. package/src/lib/server/capture-host-contact.js +151 -0
  296. package/src/lib/server/capture-host-contact.js.map +1 -0
  297. package/src/lib/server/company-children.d.ts +25 -0
  298. package/src/lib/server/company-children.js +42 -0
  299. package/src/lib/server/company-children.js.map +1 -0
  300. package/src/lib/server/company-delete.d.ts +18 -0
  301. package/src/lib/server/company-delete.js +44 -21
  302. package/src/lib/server/company-delete.js.map +1 -1
  303. package/src/lib/server/contact-email-history.js +21 -19
  304. package/src/lib/server/contact-email-history.js.map +1 -1
  305. package/src/lib/server/contact-remove.js +27 -2
  306. package/src/lib/server/contact-remove.js.map +1 -1
  307. package/src/lib/server/contact-reports-to.d.ts +61 -0
  308. package/src/lib/server/contact-reports-to.js +114 -0
  309. package/src/lib/server/contact-reports-to.js.map +1 -0
  310. package/src/lib/server/contact-update.d.ts +42 -1
  311. package/src/lib/server/contact-update.js +204 -23
  312. package/src/lib/server/contact-update.js.map +1 -1
  313. package/src/lib/server/convert-host-lead.d.ts +46 -1
  314. package/src/lib/server/convert-host-lead.js +173 -15
  315. package/src/lib/server/convert-host-lead.js.map +1 -1
  316. package/src/lib/server/convert-open-lead.js +12 -5
  317. package/src/lib/server/convert-open-lead.js.map +1 -1
  318. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  319. package/src/lib/server/crm-record-indexes.js +78 -0
  320. package/src/lib/server/crm-record-indexes.js.map +1 -0
  321. package/src/lib/server/crm-sharing.d.ts +9 -1
  322. package/src/lib/server/crm-sharing.js +23 -3
  323. package/src/lib/server/crm-sharing.js.map +1 -1
  324. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  325. package/src/lib/server/deal-contact-roles.js +89 -0
  326. package/src/lib/server/deal-contact-roles.js.map +1 -0
  327. package/src/lib/server/lead-convert.d.ts +5 -0
  328. package/src/lib/server/lead-convert.js +19 -3
  329. package/src/lib/server/lead-convert.js.map +1 -1
  330. package/src/lib/server/lead-create.d.ts +17 -2
  331. package/src/lib/server/lead-create.js +46 -14
  332. package/src/lib/server/lead-create.js.map +1 -1
  333. package/src/lib/server/lead-nurturing.d.ts +6 -0
  334. package/src/lib/server/lead-nurturing.js +57 -20
  335. package/src/lib/server/lead-nurturing.js.map +1 -1
  336. package/src/lib/server/lead-picklists.d.ts +35 -0
  337. package/src/lib/server/lead-picklists.js +52 -0
  338. package/src/lib/server/lead-picklists.js.map +1 -0
  339. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  340. package/src/lib/server/lead-source-picklist.js +6 -33
  341. package/src/lib/server/lead-source-picklist.js.map +1 -1
  342. package/src/lib/server/merge-contacts.js +22 -4
  343. package/src/lib/server/merge-contacts.js.map +1 -1
  344. package/src/lib/server/person-eraser.d.ts +53 -0
  345. package/src/lib/server/person-eraser.js +149 -0
  346. package/src/lib/server/person-eraser.js.map +1 -0
  347. package/src/lib/server/person-records.d.ts +69 -0
  348. package/src/lib/server/person-records.js +317 -0
  349. package/src/lib/server/person-records.js.map +1 -0
  350. package/src/lib/server/person-refund.d.ts +97 -0
  351. package/src/lib/server/person-refund.js +221 -0
  352. package/src/lib/server/person-refund.js.map +1 -0
  353. package/src/lib/server/picklist-values.d.ts +21 -0
  354. package/src/lib/server/picklist-values.js +308 -0
  355. package/src/lib/server/picklist-values.js.map +1 -0
  356. package/src/lib/server/read-picklist.d.ts +44 -0
  357. package/src/lib/server/read-picklist.js +65 -0
  358. package/src/lib/server/read-picklist.js.map +1 -0
  359. package/src/lib/server/recipe-routes.js +15 -7
  360. package/src/lib/server/recipe-routes.js.map +1 -1
  361. package/src/lib/server/record-email-state.js +7 -1
  362. package/src/lib/server/record-email-state.js.map +1 -1
  363. package/src/lib/server/record-facts.d.ts +3 -0
  364. package/src/lib/server/record-facts.js +233 -31
  365. package/src/lib/server/record-facts.js.map +1 -1
  366. package/src/lib/server/record-origin.d.ts +27 -0
  367. package/src/lib/server/record-origin.js +104 -0
  368. package/src/lib/server/record-origin.js.map +1 -0
  369. package/src/lib/server/record-timeline.js +15 -2
  370. package/src/lib/server/record-timeline.js.map +1 -1
  371. package/src/lib/server/suite-gate.js +4 -3
  372. package/src/lib/server/suite-gate.js.map +1 -1
  373. package/src/lib/server/task-routes.js +30 -16
  374. package/src/lib/server/task-routes.js.map +1 -1
  375. package/src/lib/server-deal-stage.d.ts +5 -1
  376. package/src/lib/server-deal-stage.js +10 -5
  377. package/src/lib/server-deal-stage.js.map +1 -1
  378. package/src/lib/server.d.ts +8 -3
  379. package/src/lib/server.js +47 -43
  380. package/src/lib/server.js.map +1 -1
  381. package/src/lib/testing/memory-firestore.d.ts +55 -0
  382. package/src/lib/testing/memory-firestore.js +335 -0
  383. package/src/lib/testing/memory-firestore.js.map +1 -0
  384. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  385. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  386. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  387. package/src/lib/transfer/activities.d.ts +30 -0
  388. package/src/lib/transfer/activities.js +132 -0
  389. package/src/lib/transfer/activities.js.map +1 -0
  390. package/src/lib/transfer/aliases.d.ts +22 -0
  391. package/src/lib/transfer/aliases.js +1342 -0
  392. package/src/lib/transfer/aliases.js.map +1 -0
  393. package/src/lib/transfer/common.d.ts +222 -0
  394. package/src/lib/transfer/common.js +832 -0
  395. package/src/lib/transfer/common.js.map +1 -0
  396. package/src/lib/transfer/companies.d.ts +43 -0
  397. package/src/lib/transfer/companies.js +549 -0
  398. package/src/lib/transfer/companies.js.map +1 -0
  399. package/src/lib/transfer/contacts.d.ts +31 -0
  400. package/src/lib/transfer/contacts.js +727 -0
  401. package/src/lib/transfer/contacts.js.map +1 -0
  402. package/src/lib/transfer/deals.d.ts +66 -0
  403. package/src/lib/transfer/deals.js +698 -0
  404. package/src/lib/transfer/deals.js.map +1 -0
  405. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  406. package/src/lib/transfer/email-templates-package.js +76 -0
  407. package/src/lib/transfer/email-templates-package.js.map +1 -0
  408. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  409. package/src/lib/transfer/email-templates-package.server.js +176 -0
  410. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  411. package/src/lib/transfer/fields.d.ts +102 -0
  412. package/src/lib/transfer/fields.js +1755 -0
  413. package/src/lib/transfer/fields.js.map +1 -0
  414. package/src/lib/transfer/leads.d.ts +30 -0
  415. package/src/lib/transfer/leads.js +582 -0
  416. package/src/lib/transfer/leads.js.map +1 -0
  417. package/src/lib/transfer/pipelines.d.ts +21 -0
  418. package/src/lib/transfer/pipelines.js +172 -0
  419. package/src/lib/transfer/pipelines.js.map +1 -0
  420. package/src/lib/transfer/register.d.ts +47 -0
  421. package/src/lib/transfer/register.js +277 -0
  422. package/src/lib/transfer/register.js.map +1 -0
  423. package/src/lib/transfer/tasks.d.ts +30 -0
  424. package/src/lib/transfer/tasks.js +418 -0
  425. package/src/lib/transfer/tasks.js.map +1 -0
  426. package/src/lib/transfer/ui.d.ts +20 -0
  427. package/src/lib/transfer/ui.js +42 -0
  428. package/src/lib/transfer/ui.js.map +1 -0
  429. package/src/lib/components/company-import-drawer.d.ts +0 -42
  430. package/src/lib/components/company-import-drawer.js +0 -99
  431. package/src/lib/components/company-import-drawer.js.map +0 -1
  432. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  433. package/src/lib/components/contact-import-drawer.js +0 -149
  434. package/src/lib/components/contact-import-drawer.js.map +0 -1
  435. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  436. package/src/lib/components/crm-export-all-button.js +0 -98
  437. package/src/lib/components/crm-export-all-button.js.map +0 -1
  438. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  439. package/src/lib/components/csv-import-drawer.js +0 -757
  440. package/src/lib/components/csv-import-drawer.js.map +0 -1
  441. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  442. package/src/lib/components/deal-import-drawer.js +0 -83
  443. package/src/lib/components/deal-import-drawer.js.map +0 -1
  444. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  445. package/src/lib/components/lead-import-drawer.js +0 -118
  446. package/src/lib/components/lead-import-drawer.js.map +0 -1
  447. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  448. package/src/lib/components/lead-source-values-card.js.map +0 -1
  449. package/src/lib/components/task-import-drawer.d.ts +0 -24
  450. package/src/lib/components/task-import-drawer.js +0 -82
  451. package/src/lib/components/task-import-drawer.js.map +0 -1
  452. package/src/lib/model/companies-csv.d.ts +0 -36
  453. package/src/lib/model/companies-csv.js +0 -38
  454. package/src/lib/model/companies-csv.js.map +0 -1
  455. package/src/lib/model/contacts-csv.d.ts +0 -54
  456. package/src/lib/model/contacts-csv.js +0 -67
  457. package/src/lib/model/contacts-csv.js.map +0 -1
  458. package/src/lib/model/crm-company-import.d.ts +0 -154
  459. package/src/lib/model/crm-company-import.js +0 -331
  460. package/src/lib/model/crm-company-import.js.map +0 -1
  461. package/src/lib/model/crm-csv.d.ts +0 -239
  462. package/src/lib/model/crm-csv.js +0 -467
  463. package/src/lib/model/crm-csv.js.map +0 -1
  464. package/src/lib/model/crm-deal-import.d.ts +0 -112
  465. package/src/lib/model/crm-deal-import.js +0 -263
  466. package/src/lib/model/crm-deal-import.js.map +0 -1
  467. package/src/lib/model/crm-import.d.ts +0 -250
  468. package/src/lib/model/crm-import.js +0 -467
  469. package/src/lib/model/crm-import.js.map +0 -1
  470. package/src/lib/model/crm-lead-import.d.ts +0 -138
  471. package/src/lib/model/crm-lead-import.js +0 -446
  472. package/src/lib/model/crm-lead-import.js.map +0 -1
  473. package/src/lib/model/crm-task-import.d.ts +0 -94
  474. package/src/lib/model/crm-task-import.js +0 -275
  475. package/src/lib/model/crm-task-import.js.map +0 -1
  476. package/src/lib/model/deals-csv.d.ts +0 -32
  477. package/src/lib/model/deals-csv.js +0 -36
  478. package/src/lib/model/deals-csv.js.map +0 -1
  479. package/src/lib/model/import-column-shapes.d.ts +0 -36
  480. package/src/lib/model/import-column-shapes.js +0 -76
  481. package/src/lib/model/import-column-shapes.js.map +0 -1
  482. package/src/lib/model/lead-source-values.d.ts +0 -82
  483. package/src/lib/model/lead-source-values.js +0 -149
  484. package/src/lib/model/lead-source-values.js.map +0 -1
  485. package/src/lib/model/leads-csv.d.ts +0 -40
  486. package/src/lib/model/leads-csv.js +0 -40
  487. package/src/lib/model/leads-csv.js.map +0 -1
  488. package/src/lib/model/tasks-csv.d.ts +0 -28
  489. package/src/lib/model/tasks-csv.js +0 -28
  490. package/src/lib/model/tasks-csv.js.map +0 -1
  491. package/src/lib/server/companies-import.d.ts +0 -57
  492. package/src/lib/server/companies-import.js +0 -242
  493. package/src/lib/server/companies-import.js.map +0 -1
  494. package/src/lib/server/contacts-import.d.ts +0 -109
  495. package/src/lib/server/contacts-import.js +0 -393
  496. package/src/lib/server/contacts-import.js.map +0 -1
  497. package/src/lib/server/deals-import.d.ts +0 -76
  498. package/src/lib/server/deals-import.js +0 -248
  499. package/src/lib/server/deals-import.js.map +0 -1
  500. package/src/lib/server/export-route.d.ts +0 -60
  501. package/src/lib/server/export-route.js +0 -447
  502. package/src/lib/server/export-route.js.map +0 -1
  503. package/src/lib/server/import-context.d.ts +0 -87
  504. package/src/lib/server/import-context.js +0 -182
  505. package/src/lib/server/import-context.js.map +0 -1
  506. package/src/lib/server/lead-source-values.d.ts +0 -50
  507. package/src/lib/server/lead-source-values.js +0 -191
  508. package/src/lib/server/lead-source-values.js.map +0 -1
  509. package/src/lib/server/leads-import.d.ts +0 -84
  510. package/src/lib/server/leads-import.js +0 -367
  511. package/src/lib/server/leads-import.js.map +0 -1
  512. package/src/lib/server/tasks-import.d.ts +0 -54
  513. package/src/lib/server/tasks-import.js +0 -160
  514. package/src/lib/server/tasks-import.js.map +0 -1
package/README.md CHANGED
@@ -34,10 +34,11 @@ The CRM is console-only. Its records live in Firestore and it has no canvas comp
34
34
  - A "Saves to contact fields" widget in the `formContactFields` slot, a zone the forms plugin hosts on a form's page.
35
35
  - Two zones the CRM hosts for other plugins to draw in: one for booking a meeting from a record, and one that says which campaign or link a record came from.
36
36
  - The CRM's record routes, so other surfaces can link to a record.
37
+ - Record count sources for `lead` (the leads a site captured) and `contact` (the organization's, a count that crosses sites) (`plugin-record-counts`), which a campaign's conversion report divides by.
37
38
 
38
39
  ### On the server
39
40
 
40
- `registerCrmConsoleApi()` from `@aglyn/plugins-crm/server` (the `consoleApi` surface) registers routes under the `crm` prefix for contact stage and field updates, contact creation and merge, lead conversion, deal stages, tasks, CSV imports of contacts, companies, deals, tasks and leads, one-to-one email and a contact's email history, erasing a person, organization-level activity, recipes, and the workspace's inbound email capture address. It also registers the CRM on two core seams, so another plugin can read a record's facts or file an item on a record's timeline without importing this package.
41
+ `registerCrmConsoleApi()` from `@aglyn/plugins-crm/server` (the `consoleApi` surface) registers routes under the `crm` prefix for contact stage and field updates, contact creation and merge, lead conversion, deal stages, tasks, one-to-one email and a contact's email history, erasing a person, organization-level activity, recipes, and the workspace's inbound email capture address. Contacts, companies, leads, deals and tasks are imported and exported through the platform's transfer framework, and activities, pipelines and custom fields exported: `registerCrmConsoleServerDeclarations()` registers each resource's server half (`src/lib/transfer/`) and the console registrar its name in the import wizard and export dialog. It also registers the CRM on two core seams, so another plugin can read a record's facts or file an item on a record's timeline without importing this package.
41
42
 
42
43
  At the console's boot (`registerCrmConsoleServerDeclarations`) it registers a membership detacher (`plugin-membership-detach`): when the plugin that keeps a container — a campaign — removes it, the container comes off every lead and every contact facet that names it, and the records stay.
43
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-crm",
3
- "version": "1.0.0-beta.221",
3
+ "version": "1.0.0-beta.222",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.221",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.221",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.221",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.221",
32
- "@aglyn/shared-ui-next": "1.0.0-beta.221",
33
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.221",
34
- "@aglyn/shared-util-email": "1.0.0-beta.221",
35
- "@aglyn/shared-util-http": "1.0.0-beta.221",
36
- "@aglyn/shared-util-timestamp": "1.0.0-beta.221",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.221",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.221",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.221",
28
+ "@aglyn/aglyn": "1.0.0-beta.222",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.222",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.222",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.222",
32
+ "@aglyn/shared-ui-next": "1.0.0-beta.222",
33
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.222",
34
+ "@aglyn/shared-util-email": "1.0.0-beta.222",
35
+ "@aglyn/shared-util-http": "1.0.0-beta.222",
36
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.222",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.222",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.222",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.222",
40
40
  "@dnd-kit/core": "^6.3.1",
41
41
  "@dnd-kit/utilities": "^3.2.2",
42
42
  "@swc/helpers": "0.5.23",
@@ -165,7 +165,11 @@ ActivityKindIcon.displayName = 'ActivityKindIcon';
165
165
  * the kind, red once the message did not land. What was sent is a record
166
166
  * of a fact and offers no edit — a rewritten body would misstate what
167
167
  * left — though whoever may delete a row may still delete this one.
168
- */ const sent = activity.direction === 'outbound';
168
+ */ const captured = typeof activity.messageId === 'string' && activity.messageId !== '';
169
+ const deliveryState = Aglyn.isCrmEmailDeliveryState(activity.deliveryState) ? activity.deliveryState : null;
170
+ // A hand-logged email or call carries a direction too (AGL-3517); only a
171
+ // message the platform carried or captured is "sent" or "received" here.
172
+ const sent = activity.direction === 'outbound' && (deliveryState !== null || captured);
169
173
  /*
170
174
  * A message CAPTURED from a mailbox (AGL-2657) carries the provider's
171
175
  * Message-ID and, when a correspondent wrote it, the `inbound` direction.
@@ -174,9 +178,12 @@ ActivityKindIcon.displayName = 'ActivityKindIcon';
174
178
  * team logged it — and a member's copied send reads "Sent" without a
175
179
  * delivery chip, since the platform did not carry it. Neither takes the
176
180
  * caller's "Logged" chip: nobody logged them.
177
- */ const received = activity.direction === 'inbound';
178
- const captured = typeof activity.messageId === 'string' && activity.messageId !== '';
179
- const deliveryState = Aglyn.isCrmEmailDeliveryState(activity.deliveryState) ? activity.deliveryState : null;
181
+ */ const received = activity.direction === 'inbound' && captured;
182
+ /*
183
+ * A hand-logged call or email names its direction in the kind's own chip
184
+ * — "Inbound call", "Outbound email" (AGL-3517); a sent or captured
185
+ * message says it with the chips below instead.
186
+ */ const kindChip = sent || received || captured ? label : Aglyn.crmActivityKindTitle(Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other', activity.direction);
180
187
  const when = new Date(activity.atMs);
181
188
  const detail = [
182
189
  activity.outcome ? activity.outcome : null,
@@ -196,7 +203,7 @@ ActivityKindIcon.displayName = 'ActivityKindIcon';
196
203
  chips: /*#__PURE__*/ _jsxs(_Fragment, {
197
204
  children: [
198
205
  /*#__PURE__*/ _jsx(Chip, {
199
- label: label,
206
+ label: kindChip,
200
207
  size: "small"
201
208
  }),
202
209
  received ? /*#__PURE__*/ _jsx(Chip, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/activity-list.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 { CrmActivityKind, CrmActivityRow } from '@aglyn/aglyn'\nimport {\n mdiAccountGroupOutline,\n mdiDeleteOutline,\n mdiDotsHorizontalCircleOutline,\n mdiEmailOutline,\n mdiEmailReceiveOutline,\n mdiNoteTextOutline,\n mdiPencilOutline,\n mdiPhoneOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser, useUserName } from '@aglyn/tenant-feature-instance'\nimport { Chip, IconButton, Stack, Tooltip, Typography } from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { type ReactNode, useCallback, useState } from 'react'\nimport { type ActivityScope, useCanEditActivity } from './activity-queries'\nimport {\n TimelineEntry,\n type TimelineExpansion,\n useTimelineExpansion,\n useTimelinePages,\n} from './activity-timeline'\n\n/**\n * One glyph per kind. Typed against the union so a kind added to the list\n * cannot render with no icon — `MdiIcon` would fall back to its default\n * glyph silently, and a log where every note looks like a placeholder is\n * worse than a build error.\n */\nconst KIND_ICONS: Record<CrmActivityKind, { path: string }> = {\n call: mdiPhoneOutline,\n email: mdiEmailOutline,\n meeting: mdiAccountGroupOutline,\n note: mdiNoteTextOutline,\n other: mdiDotsHorizontalCircleOutline,\n}\n\n/** The icon for a kind — a stored kind outside the union draws as `other`. */\nexport function ActivityKindIcon(props: { kind: string; size?: number }) {\n const { kind, size = 1 } = props\n const icon = Aglyn.isCrmActivityKind(kind) ? KIND_ICONS[kind] : KIND_ICONS.other\n return <MdiIcon path={icon.path} size={size} />\n}\nActivityKindIcon.displayName = 'ActivityKindIcon'\n\n/**\n * The author's name for a row (AGL-2600).\n *\n * The denormalized `byName` leads: it is the only name a scoped colleague\n * can read, since a member document is readable by its subject and by\n * org-wide members alone. The signed-in user's OWN rows fall back to their\n * live name — the one case where a lookup is possible — and a row that\n * carries neither (written by a door that did not stamp one) reads as a\n * team member rather than as a uid.\n */\nexport function useActivityAuthorName(): (\n activity: Pick<CrmActivityRow, 'byUid' | 'byName'>,\n) => string {\n const { data: user } = useUser()\n const ownName = useUserName()\n const uid = user?.uid\n return useCallback(\n (activity) => {\n if (activity.byName) return activity.byName\n if (uid && activity.byUid === uid && ownName) return ownName\n return 'A team member'\n },\n [uid, ownName],\n )\n}\n\n/**\n * How long a note's own words may run on a collapsed row before it opens to\n * the rest; a single line under this reads whole, with nothing to open.\n */\nconst ACTIVITY_HEADING_MAX = 80\n\n/**\n * What a collapsed activity reads as, and whether opening it shows more.\n *\n * A subject is the heading and the body is what the row opens to. With no\n * subject the body's first line is the heading, and a body that runs past\n * that line, or past what one line holds, opens to the whole text.\n */\nexport function activityHeading(activity: Pick<CrmActivityRow, 'subject' | 'body'>): {\n heading: string\n opens: boolean\n} {\n const body = typeof activity.body === 'string' ? activity.body.trim() : ''\n const subject = typeof activity.subject === 'string' ? activity.subject.trim() : ''\n if (subject) return { heading: subject, opens: body !== '' }\n const firstLine = body.split('\\n').find((line) => line.trim() !== '')?.trim() ?? ''\n return {\n heading: firstLine,\n opens: body !== firstLine || firstLine.length > ACTIVITY_HEADING_MAX,\n }\n}\n\n/** \"45 min\" or \"2 h 05 min\" — a duration a manager reads at a glance. */\nfunction durationLabel(minutes: number): string {\n if (minutes < 60) return `${minutes} min`\n const hours = Math.floor(minutes / 60)\n const rest = minutes % 60\n return rest ? `${hours} h ${String(rest).padStart(2, '0')} min` : `${hours} h`\n}\n\nexport interface ActivityRowProps {\n activity: CrmActivityRow\n /** Where the delete is written, from `useActivityScope`. */\n scope: ActivityScope\n /** Opens the dialog on this activity. Absent, the row offers no edit. */\n onEdit?: (activity: CrmActivityRow) => void\n /**\n * What sits after the kind, before the body — a feed puts the record the\n * activity is about here, a record page needs nothing.\n */\n subject?: ReactNode\n /** The clock the relative time is read against; `Date.now()` when absent. */\n nowMs?: number\n /**\n * Whether THIS reader may edit and delete the row — the author, or an\n * org-wide member. Decided by whoever draws the rows, through\n * `useCanEditActivity`, once for the whole list: a row that asked for\n * itself would read the member document once per row.\n */\n editable?: boolean\n /**\n * Whether the row is open to its body. The list holds it so the card's\n * \"Expand all\" reaches every row; a row drawn alone holds its own.\n */\n expanded?: boolean\n onToggle?: () => void\n}\n\n/**\n * One logged activity: the kind's icon and label, what was said, how it\n * went, who logged it and how long ago — with edit and delete for whoever\n * may (AGL-2600).\n *\n * Collapsed, the row is its chips, its heading and its caption: the kind,\n * the direction and delivery state, how a call went, the subject or the\n * note's first line, and who, to or from whom, and when. Opened, it shows\n * the body — an email's whole text, a note's every line.\n *\n * Edit and delete appear only when `editable` says this reader may — the\n * author or an org-wide member. That is the console's verdict and not the\n * rules' — see `useCanEditActivity` for why the rules admit more — so the\n * controls are hidden rather than disabled: a disabled button asks \"why\n * not?\", and the honest answer would be \"you could, through the API\".\n */\nexport function ActivityRow(props: ActivityRowProps) {\n const { activity, scope, onEdit, subject, nowMs, editable } = props\n const [ownExpanded, setOwnExpanded] = useState(false)\n const expanded = props.expanded ?? ownExpanded\n const onToggle = props.onToggle ?? (() => setOwnExpanded((open) => !open))\n const { firestore, dataScope } = scope\n const authorName = useActivityAuthorName()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n\n const handleDelete = useCallback(async () => {\n if (!dataScope) return\n const confirmed = await confirm({\n title: 'Delete this activity?',\n description:\n `This ${Aglyn.CRM_ACTIVITY_KIND_LABELS[\n Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n ].toLowerCase()} is removed from the record it was logged against. ` +\n 'Nothing the platform captured — a form, an order, a booking — is ' +\n 'affected.',\n confirmationText: 'Delete activity',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n await deleteDoc(\n doc(\n firestore,\n dataScope[0],\n dataScope[1],\n Aglyn.CRM_COLLECTIONS.activities,\n activity.$id,\n ),\n )\n enqueueSnackbar('Activity deleted', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [dataScope, confirm, activity, firestore, enqueueSnackbar])\n\n const label =\n Aglyn.CRM_ACTIVITY_KIND_LABELS[\n Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n ]\n /*\n * A message the platform SENT (AGL-2615) carries a subject, an address\n * and a delivery state the webhook advances; the state is the chip beside\n * the kind, red once the message did not land. What was sent is a record\n * of a fact and offers no edit — a rewritten body would misstate what\n * left — though whoever may delete a row may still delete this one.\n */\n const sent = activity.direction === 'outbound'\n /*\n * A message CAPTURED from a mailbox (AGL-2657) carries the provider's\n * Message-ID and, when a correspondent wrote it, the `inbound` direction.\n * It is drawn as its own thing — \"Received\" beside the kind, the sender\n * where a sent row shows the recipient, no author because nobody on the\n * team logged it — and a member's copied send reads \"Sent\" without a\n * delivery chip, since the platform did not carry it. Neither takes the\n * caller's \"Logged\" chip: nobody logged them.\n */\n const received = activity.direction === 'inbound'\n const captured = typeof activity.messageId === 'string' && activity.messageId !== ''\n const deliveryState = Aglyn.isCrmEmailDeliveryState(activity.deliveryState)\n ? activity.deliveryState\n : null\n const when = new Date(activity.atMs)\n const detail = [\n activity.outcome ? activity.outcome : null,\n typeof activity.durationMinutes === 'number' && activity.durationMinutes > 0\n ? durationLabel(activity.durationMinutes)\n : null,\n ].filter(Boolean)\n\n const { heading, opens } = activityHeading(activity)\n const hasSubject = typeof activity.subject === 'string' && activity.subject.trim() !== ''\n\n return (\n <TimelineEntry\n icon={\n received ? (\n <MdiIcon path={mdiEmailReceiveOutline.path} />\n ) : (\n <ActivityKindIcon kind={activity.kind} />\n )\n }\n label={heading || label.toLowerCase()}\n expanded={expanded}\n onToggle={onToggle}\n chips={\n <>\n <Chip label={label} size=\"small\" />\n {received ? (\n <Chip\n label=\"Received\"\n size=\"small\"\n variant=\"outlined\"\n color=\"info\"\n data-testid=\"activity-direction\"\n />\n ) : captured ? (\n <Chip\n label=\"Sent\"\n size=\"small\"\n variant=\"outlined\"\n data-testid=\"activity-direction\"\n />\n ) : null}\n {deliveryState ? (\n <Tooltip\n title={\n typeof activity.deliveryAtMs === 'number'\n ? new Date(activity.deliveryAtMs).toLocaleString()\n : ''\n }\n >\n <Chip\n label={Aglyn.CRM_EMAIL_DELIVERY_STATE_LABELS[deliveryState]}\n size=\"small\"\n variant=\"outlined\"\n color={Aglyn.isCrmEmailDeliveryFailure(deliveryState) ? 'error' : 'default'}\n data-testid=\"activity-delivery-state\"\n />\n </Tooltip>\n ) : null}\n {captured ? null : subject}\n {detail.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {detail.join(' · ')}\n </Typography>\n ) : null}\n </>\n }\n // With no subject the heading IS the body's first line, so an open\n // row shows the body alone rather than its first line twice.\n title={hasSubject || !opens || !expanded ? heading : null}\n titleVariant={hasSubject ? 'subtitle2' : 'body2'}\n meta={\n <Tooltip title={when.toLocaleString()}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {[\n received ? null : authorName(activity),\n received && activity.from ? `from ${activity.from}` : null,\n sent && activity.to ? `to ${activity.to}` : null,\n Aglyn.activityTimeLabel(activity.atMs, nowMs ?? Date.now()),\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n </Tooltip>\n }\n body={\n opens ? (\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>\n {activity.body}\n </Typography>\n ) : undefined\n }\n actions={\n editable ? (\n <>\n {onEdit && !sent && !received ? (\n <IconButton\n size=\"small\"\n aria-label=\"Edit activity\"\n onClick={() => onEdit(activity)}\n >\n <MdiIcon path={mdiPencilOutline.path} size={0.8} />\n </IconButton>\n ) : null}\n <IconButton\n size=\"small\"\n aria-label=\"Delete activity\"\n onClick={handleDelete}\n >\n <MdiIcon path={mdiDeleteOutline.path} size={0.8} />\n </IconButton>\n </>\n ) : null\n }\n />\n )\n}\nActivityRow.displayName = 'ActivityRow'\n\nexport interface ActivityListProps {\n /** Newest-first, as the listener hands them back. */\n rows: readonly CrmActivityRow[]\n scope: ActivityScope\n onEdit?: (activity: CrmActivityRow) => void\n /** What the list says when there is nothing in it. */\n emptyText?: string\n /** The way out of an empty list — the record's \"Log activity\" button. */\n emptyAction?: ReactNode\n /** The listener holds less than exists; `onShowMore` widens it. */\n hasMore?: boolean\n onShowMore?: () => void\n /** The listener is reading, so an empty window is not yet an empty log. */\n loading?: boolean\n /** The record each row is about, for a list that spans records. */\n subjectFor?: (activity: CrmActivityRow) => ReactNode\n /**\n * No controls on any row, whoever is reading — and no member read to\n * decide them. A feed that spans records is a place to see what happened,\n * not to rewrite it; the record's own page is where an activity is\n * corrected, beside everything else about it.\n */\n readOnly?: boolean\n /**\n * Which rows are open, held by the card so its header's \"Expand all\"\n * reaches them. A list with no header control holds its own.\n */\n expansion?: TimelineExpansion\n}\n\n/**\n * A newest-first list of logged activities, collapsed to one entry each and\n * paged by the console's shared footer (AGL-2600).\n *\n * The rows arrive ordered — the query is `orderBy('atMs', 'desc')` — and the\n * list does not sort them again; a second sort here is a second place for\n * the order to be defined. The footer pages the window the listener holds,\n * and a page turned past it widens the listener while the probe row says\n * more exists (`useTimelinePages`).\n *\n * Who may edit is decided here, once, and handed to every row: the verdict\n * reads the member document, and the list is the one place that can ask\n * for it a single time however many rows it draws.\n */\nexport function ActivityList(props: ActivityListProps) {\n const {\n rows,\n scope,\n onEdit,\n emptyText = 'Nothing logged yet.',\n emptyAction,\n hasMore,\n onShowMore,\n loading,\n subjectFor,\n readOnly,\n } = props\n const canEdit = useCanEditActivity(scope.orgId, !readOnly)\n const ownExpansion = useTimelineExpansion()\n const expansion = props.expansion ?? ownExpansion\n const pages = useTimelinePages(rows.length, { hasMore, showMore: onShowMore, loading })\n // One clock for every row of one paint.\n const nowMs = Date.now()\n if (!rows.length) {\n return loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : (\n <EmptyStateComponent\n compact\n label={'Nothing logged yet'}\n description={emptyText}\n action={emptyAction}\n />\n )\n }\n return (\n <Stack spacing={2}>\n {pages.slice(rows).map((activity) => (\n <ActivityRow\n key={activity.$id}\n activity={activity}\n scope={scope}\n onEdit={onEdit}\n subject={subjectFor?.(activity)}\n nowMs={nowMs}\n editable={canEdit(activity)}\n expanded={expansion.isExpanded(activity.$id)}\n onToggle={() => expansion.toggle(activity.$id)}\n />\n ))}\n <ListPagination {...pages.footer} />\n </Stack>\n )\n}\nActivityList.displayName = 'ActivityList'\n\nexport default ActivityList\n"],"names":["Aglyn","mdiAccountGroupOutline","mdiDeleteOutline","mdiDotsHorizontalCircleOutline","mdiEmailOutline","mdiEmailReceiveOutline","mdiNoteTextOutline","mdiPencilOutline","mdiPhoneOutline","MdiIcon","useConfirmationContext","EmptyStateComponent","ListPagination","useSnackbar","useUser","useUserName","Chip","IconButton","Stack","Tooltip","Typography","deleteDoc","doc","useCallback","useState","useCanEditActivity","TimelineEntry","useTimelineExpansion","useTimelinePages","KIND_ICONS","call","email","meeting","note","other","ActivityKindIcon","props","kind","size","icon","isCrmActivityKind","path","displayName","useActivityAuthorName","data","user","ownName","uid","activity","byName","byUid","ACTIVITY_HEADING_MAX","activityHeading","body","trim","subject","heading","opens","firstLine","split","find","line","length","durationLabel","minutes","hours","Math","floor","rest","String","padStart","ActivityRow","scope","onEdit","nowMs","editable","ownExpanded","setOwnExpanded","expanded","onToggle","open","firestore","dataScope","authorName","confirm","enqueueSnackbar","handleDelete","confirmed","title","description","CRM_ACTIVITY_KIND_LABELS","toLowerCase","confirmationText","confirmationButtonProps","color","then","catch","CRM_COLLECTIONS","activities","$id","variant","persist","error","console","allowDuplicate","label","sent","direction","received","captured","messageId","deliveryState","isCrmEmailDeliveryState","when","Date","atMs","detail","outcome","durationMinutes","filter","Boolean","hasSubject","chips","data-testid","deliveryAtMs","toLocaleString","CRM_EMAIL_DELIVERY_STATE_LABELS","isCrmEmailDeliveryFailure","join","titleVariant","meta","sx","alignSelf","from","to","activityTimeLabel","now","whiteSpace","overflowWrap","undefined","actions","aria-label","onClick","ActivityList","rows","emptyText","emptyAction","hasMore","onShowMore","loading","subjectFor","readOnly","canEdit","orgId","ownExpansion","expansion","pages","showMore","compact","action","spacing","slice","map","isExpanded","toggle","footer"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/activity-list.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 { CrmActivityKind, CrmActivityRow } from '@aglyn/aglyn'\nimport {\n mdiAccountGroupOutline,\n mdiDeleteOutline,\n mdiDotsHorizontalCircleOutline,\n mdiEmailOutline,\n mdiEmailReceiveOutline,\n mdiNoteTextOutline,\n mdiPencilOutline,\n mdiPhoneOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser, useUserName } from '@aglyn/tenant-feature-instance'\nimport { Chip, IconButton, Stack, Tooltip, Typography } from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { type ReactNode, useCallback, useState } from 'react'\nimport { type ActivityScope, useCanEditActivity } from './activity-queries'\nimport {\n TimelineEntry,\n type TimelineExpansion,\n useTimelineExpansion,\n useTimelinePages,\n} from './activity-timeline'\n\n/**\n * One glyph per kind. Typed against the union so a kind added to the list\n * cannot render with no icon — `MdiIcon` would fall back to its default\n * glyph silently, and a log where every note looks like a placeholder is\n * worse than a build error.\n */\nconst KIND_ICONS: Record<CrmActivityKind, { path: string }> = {\n call: mdiPhoneOutline,\n email: mdiEmailOutline,\n meeting: mdiAccountGroupOutline,\n note: mdiNoteTextOutline,\n other: mdiDotsHorizontalCircleOutline,\n}\n\n/** The icon for a kind — a stored kind outside the union draws as `other`. */\nexport function ActivityKindIcon(props: { kind: string; size?: number }) {\n const { kind, size = 1 } = props\n const icon = Aglyn.isCrmActivityKind(kind) ? KIND_ICONS[kind] : KIND_ICONS.other\n return <MdiIcon path={icon.path} size={size} />\n}\nActivityKindIcon.displayName = 'ActivityKindIcon'\n\n/**\n * The author's name for a row (AGL-2600).\n *\n * The denormalized `byName` leads: it is the only name a scoped colleague\n * can read, since a member document is readable by its subject and by\n * org-wide members alone. The signed-in user's OWN rows fall back to their\n * live name — the one case where a lookup is possible — and a row that\n * carries neither (written by a door that did not stamp one) reads as a\n * team member rather than as a uid.\n */\nexport function useActivityAuthorName(): (\n activity: Pick<CrmActivityRow, 'byUid' | 'byName'>,\n) => string {\n const { data: user } = useUser()\n const ownName = useUserName()\n const uid = user?.uid\n return useCallback(\n (activity) => {\n if (activity.byName) return activity.byName\n if (uid && activity.byUid === uid && ownName) return ownName\n return 'A team member'\n },\n [uid, ownName],\n )\n}\n\n/**\n * How long a note's own words may run on a collapsed row before it opens to\n * the rest; a single line under this reads whole, with nothing to open.\n */\nconst ACTIVITY_HEADING_MAX = 80\n\n/**\n * What a collapsed activity reads as, and whether opening it shows more.\n *\n * A subject is the heading and the body is what the row opens to. With no\n * subject the body's first line is the heading, and a body that runs past\n * that line, or past what one line holds, opens to the whole text.\n */\nexport function activityHeading(activity: Pick<CrmActivityRow, 'subject' | 'body'>): {\n heading: string\n opens: boolean\n} {\n const body = typeof activity.body === 'string' ? activity.body.trim() : ''\n const subject = typeof activity.subject === 'string' ? activity.subject.trim() : ''\n if (subject) return { heading: subject, opens: body !== '' }\n const firstLine = body.split('\\n').find((line) => line.trim() !== '')?.trim() ?? ''\n return {\n heading: firstLine,\n opens: body !== firstLine || firstLine.length > ACTIVITY_HEADING_MAX,\n }\n}\n\n/** \"45 min\" or \"2 h 05 min\" — a duration a manager reads at a glance. */\nfunction durationLabel(minutes: number): string {\n if (minutes < 60) return `${minutes} min`\n const hours = Math.floor(minutes / 60)\n const rest = minutes % 60\n return rest ? `${hours} h ${String(rest).padStart(2, '0')} min` : `${hours} h`\n}\n\nexport interface ActivityRowProps {\n activity: CrmActivityRow\n /** Where the delete is written, from `useActivityScope`. */\n scope: ActivityScope\n /** Opens the dialog on this activity. Absent, the row offers no edit. */\n onEdit?: (activity: CrmActivityRow) => void\n /**\n * What sits after the kind, before the body — a feed puts the record the\n * activity is about here, a record page needs nothing.\n */\n subject?: ReactNode\n /** The clock the relative time is read against; `Date.now()` when absent. */\n nowMs?: number\n /**\n * Whether THIS reader may edit and delete the row — the author, or an\n * org-wide member. Decided by whoever draws the rows, through\n * `useCanEditActivity`, once for the whole list: a row that asked for\n * itself would read the member document once per row.\n */\n editable?: boolean\n /**\n * Whether the row is open to its body. The list holds it so the card's\n * \"Expand all\" reaches every row; a row drawn alone holds its own.\n */\n expanded?: boolean\n onToggle?: () => void\n}\n\n/**\n * One logged activity: the kind's icon and label, what was said, how it\n * went, who logged it and how long ago — with edit and delete for whoever\n * may (AGL-2600).\n *\n * Collapsed, the row is its chips, its heading and its caption: the kind,\n * the direction and delivery state, how a call went, the subject or the\n * note's first line, and who, to or from whom, and when. Opened, it shows\n * the body — an email's whole text, a note's every line.\n *\n * Edit and delete appear only when `editable` says this reader may — the\n * author or an org-wide member. That is the console's verdict and not the\n * rules' — see `useCanEditActivity` for why the rules admit more — so the\n * controls are hidden rather than disabled: a disabled button asks \"why\n * not?\", and the honest answer would be \"you could, through the API\".\n */\nexport function ActivityRow(props: ActivityRowProps) {\n const { activity, scope, onEdit, subject, nowMs, editable } = props\n const [ownExpanded, setOwnExpanded] = useState(false)\n const expanded = props.expanded ?? ownExpanded\n const onToggle = props.onToggle ?? (() => setOwnExpanded((open) => !open))\n const { firestore, dataScope } = scope\n const authorName = useActivityAuthorName()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n\n const handleDelete = useCallback(async () => {\n if (!dataScope) return\n const confirmed = await confirm({\n title: 'Delete this activity?',\n description:\n `This ${Aglyn.CRM_ACTIVITY_KIND_LABELS[\n Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n ].toLowerCase()} is removed from the record it was logged against. ` +\n 'Nothing the platform captured — a form, an order, a booking — is ' +\n 'affected.',\n confirmationText: 'Delete activity',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n await deleteDoc(\n doc(\n firestore,\n dataScope[0],\n dataScope[1],\n Aglyn.CRM_COLLECTIONS.activities,\n activity.$id,\n ),\n )\n enqueueSnackbar('Activity deleted', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [dataScope, confirm, activity, firestore, enqueueSnackbar])\n\n const label =\n Aglyn.CRM_ACTIVITY_KIND_LABELS[\n Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n ]\n /*\n * A message the platform SENT (AGL-2615) carries a subject, an address\n * and a delivery state the webhook advances; the state is the chip beside\n * the kind, red once the message did not land. What was sent is a record\n * of a fact and offers no edit — a rewritten body would misstate what\n * left — though whoever may delete a row may still delete this one.\n */\n const captured = typeof activity.messageId === 'string' && activity.messageId !== ''\n const deliveryState = Aglyn.isCrmEmailDeliveryState(activity.deliveryState)\n ? activity.deliveryState\n : null\n // A hand-logged email or call carries a direction too (AGL-3517); only a\n // message the platform carried or captured is \"sent\" or \"received\" here.\n const sent = activity.direction === 'outbound' && (deliveryState !== null || captured)\n /*\n * A message CAPTURED from a mailbox (AGL-2657) carries the provider's\n * Message-ID and, when a correspondent wrote it, the `inbound` direction.\n * It is drawn as its own thing — \"Received\" beside the kind, the sender\n * where a sent row shows the recipient, no author because nobody on the\n * team logged it — and a member's copied send reads \"Sent\" without a\n * delivery chip, since the platform did not carry it. Neither takes the\n * caller's \"Logged\" chip: nobody logged them.\n */\n const received = activity.direction === 'inbound' && captured\n /*\n * A hand-logged call or email names its direction in the kind's own chip\n * — \"Inbound call\", \"Outbound email\" (AGL-3517); a sent or captured\n * message says it with the chips below instead.\n */\n const kindChip =\n sent || received || captured\n ? label\n : Aglyn.crmActivityKindTitle(\n Aglyn.isCrmActivityKind(activity.kind) ? activity.kind : 'other',\n activity.direction,\n )\n const when = new Date(activity.atMs)\n const detail = [\n activity.outcome ? activity.outcome : null,\n typeof activity.durationMinutes === 'number' && activity.durationMinutes > 0\n ? durationLabel(activity.durationMinutes)\n : null,\n ].filter(Boolean)\n\n const { heading, opens } = activityHeading(activity)\n const hasSubject = typeof activity.subject === 'string' && activity.subject.trim() !== ''\n\n return (\n <TimelineEntry\n icon={\n received ? (\n <MdiIcon path={mdiEmailReceiveOutline.path} />\n ) : (\n <ActivityKindIcon kind={activity.kind} />\n )\n }\n label={heading || label.toLowerCase()}\n expanded={expanded}\n onToggle={onToggle}\n chips={\n <>\n <Chip label={kindChip} size=\"small\" />\n {received ? (\n <Chip\n label=\"Received\"\n size=\"small\"\n variant=\"outlined\"\n color=\"info\"\n data-testid=\"activity-direction\"\n />\n ) : captured ? (\n <Chip\n label=\"Sent\"\n size=\"small\"\n variant=\"outlined\"\n data-testid=\"activity-direction\"\n />\n ) : null}\n {deliveryState ? (\n <Tooltip\n title={\n typeof activity.deliveryAtMs === 'number'\n ? new Date(activity.deliveryAtMs).toLocaleString()\n : ''\n }\n >\n <Chip\n label={Aglyn.CRM_EMAIL_DELIVERY_STATE_LABELS[deliveryState]}\n size=\"small\"\n variant=\"outlined\"\n color={Aglyn.isCrmEmailDeliveryFailure(deliveryState) ? 'error' : 'default'}\n data-testid=\"activity-delivery-state\"\n />\n </Tooltip>\n ) : null}\n {captured ? null : subject}\n {detail.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {detail.join(' · ')}\n </Typography>\n ) : null}\n </>\n }\n // With no subject the heading IS the body's first line, so an open\n // row shows the body alone rather than its first line twice.\n title={hasSubject || !opens || !expanded ? heading : null}\n titleVariant={hasSubject ? 'subtitle2' : 'body2'}\n meta={\n <Tooltip title={when.toLocaleString()}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {[\n received ? null : authorName(activity),\n received && activity.from ? `from ${activity.from}` : null,\n sent && activity.to ? `to ${activity.to}` : null,\n Aglyn.activityTimeLabel(activity.atMs, nowMs ?? Date.now()),\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n </Tooltip>\n }\n body={\n opens ? (\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>\n {activity.body}\n </Typography>\n ) : undefined\n }\n actions={\n editable ? (\n <>\n {onEdit && !sent && !received ? (\n <IconButton\n size=\"small\"\n aria-label=\"Edit activity\"\n onClick={() => onEdit(activity)}\n >\n <MdiIcon path={mdiPencilOutline.path} size={0.8} />\n </IconButton>\n ) : null}\n <IconButton\n size=\"small\"\n aria-label=\"Delete activity\"\n onClick={handleDelete}\n >\n <MdiIcon path={mdiDeleteOutline.path} size={0.8} />\n </IconButton>\n </>\n ) : null\n }\n />\n )\n}\nActivityRow.displayName = 'ActivityRow'\n\nexport interface ActivityListProps {\n /** Newest-first, as the listener hands them back. */\n rows: readonly CrmActivityRow[]\n scope: ActivityScope\n onEdit?: (activity: CrmActivityRow) => void\n /** What the list says when there is nothing in it. */\n emptyText?: string\n /** The way out of an empty list — the record's \"Log activity\" button. */\n emptyAction?: ReactNode\n /** The listener holds less than exists; `onShowMore` widens it. */\n hasMore?: boolean\n onShowMore?: () => void\n /** The listener is reading, so an empty window is not yet an empty log. */\n loading?: boolean\n /** The record each row is about, for a list that spans records. */\n subjectFor?: (activity: CrmActivityRow) => ReactNode\n /**\n * No controls on any row, whoever is reading — and no member read to\n * decide them. A feed that spans records is a place to see what happened,\n * not to rewrite it; the record's own page is where an activity is\n * corrected, beside everything else about it.\n */\n readOnly?: boolean\n /**\n * Which rows are open, held by the card so its header's \"Expand all\"\n * reaches them. A list with no header control holds its own.\n */\n expansion?: TimelineExpansion\n}\n\n/**\n * A newest-first list of logged activities, collapsed to one entry each and\n * paged by the console's shared footer (AGL-2600).\n *\n * The rows arrive ordered — the query is `orderBy('atMs', 'desc')` — and the\n * list does not sort them again; a second sort here is a second place for\n * the order to be defined. The footer pages the window the listener holds,\n * and a page turned past it widens the listener while the probe row says\n * more exists (`useTimelinePages`).\n *\n * Who may edit is decided here, once, and handed to every row: the verdict\n * reads the member document, and the list is the one place that can ask\n * for it a single time however many rows it draws.\n */\nexport function ActivityList(props: ActivityListProps) {\n const {\n rows,\n scope,\n onEdit,\n emptyText = 'Nothing logged yet.',\n emptyAction,\n hasMore,\n onShowMore,\n loading,\n subjectFor,\n readOnly,\n } = props\n const canEdit = useCanEditActivity(scope.orgId, !readOnly)\n const ownExpansion = useTimelineExpansion()\n const expansion = props.expansion ?? ownExpansion\n const pages = useTimelinePages(rows.length, { hasMore, showMore: onShowMore, loading })\n // One clock for every row of one paint.\n const nowMs = Date.now()\n if (!rows.length) {\n return loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : (\n <EmptyStateComponent\n compact\n label={'Nothing logged yet'}\n description={emptyText}\n action={emptyAction}\n />\n )\n }\n return (\n <Stack spacing={2}>\n {pages.slice(rows).map((activity) => (\n <ActivityRow\n key={activity.$id}\n activity={activity}\n scope={scope}\n onEdit={onEdit}\n subject={subjectFor?.(activity)}\n nowMs={nowMs}\n editable={canEdit(activity)}\n expanded={expansion.isExpanded(activity.$id)}\n onToggle={() => expansion.toggle(activity.$id)}\n />\n ))}\n <ListPagination {...pages.footer} />\n </Stack>\n )\n}\nActivityList.displayName = 'ActivityList'\n\nexport default ActivityList\n"],"names":["Aglyn","mdiAccountGroupOutline","mdiDeleteOutline","mdiDotsHorizontalCircleOutline","mdiEmailOutline","mdiEmailReceiveOutline","mdiNoteTextOutline","mdiPencilOutline","mdiPhoneOutline","MdiIcon","useConfirmationContext","EmptyStateComponent","ListPagination","useSnackbar","useUser","useUserName","Chip","IconButton","Stack","Tooltip","Typography","deleteDoc","doc","useCallback","useState","useCanEditActivity","TimelineEntry","useTimelineExpansion","useTimelinePages","KIND_ICONS","call","email","meeting","note","other","ActivityKindIcon","props","kind","size","icon","isCrmActivityKind","path","displayName","useActivityAuthorName","data","user","ownName","uid","activity","byName","byUid","ACTIVITY_HEADING_MAX","activityHeading","body","trim","subject","heading","opens","firstLine","split","find","line","length","durationLabel","minutes","hours","Math","floor","rest","String","padStart","ActivityRow","scope","onEdit","nowMs","editable","ownExpanded","setOwnExpanded","expanded","onToggle","open","firestore","dataScope","authorName","confirm","enqueueSnackbar","handleDelete","confirmed","title","description","CRM_ACTIVITY_KIND_LABELS","toLowerCase","confirmationText","confirmationButtonProps","color","then","catch","CRM_COLLECTIONS","activities","$id","variant","persist","error","console","allowDuplicate","label","captured","messageId","deliveryState","isCrmEmailDeliveryState","sent","direction","received","kindChip","crmActivityKindTitle","when","Date","atMs","detail","outcome","durationMinutes","filter","Boolean","hasSubject","chips","data-testid","deliveryAtMs","toLocaleString","CRM_EMAIL_DELIVERY_STATE_LABELS","isCrmEmailDeliveryFailure","join","titleVariant","meta","sx","alignSelf","from","to","activityTimeLabel","now","whiteSpace","overflowWrap","undefined","actions","aria-label","onClick","ActivityList","rows","emptyText","emptyAction","hasMore","onShowMore","loading","subjectFor","readOnly","canEdit","orgId","ownExpansion","expansion","pages","showMore","compact","action","spacing","slice","map","isExpanded","toggle","footer"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import type { OrgMemberOptions } from '../hooks/use-org-member-options';
2
2
  import { type CompanyBulkRow } from '../model/companies-bulk-writes';
3
- import { type CompanyCsvOptions, type CompanyCsvRow } from '../model/companies-csv';
4
3
  export interface CompaniesBulkBarProps {
5
4
  /**
6
5
  * The site whose activity feed the bar's acts are logged in, or `null`
@@ -10,13 +9,11 @@ export interface CompaniesBulkBarProps {
10
9
  hostId: string | null;
11
10
  /** `['orgs', orgId]`, or `null` while the org is unresolved. */
12
11
  scope: readonly ['orgs', string] | null;
13
- rows: readonly (CompanyBulkRow & CompanyCsvRow)[];
12
+ rows: readonly CompanyBulkRow[];
14
13
  selected: readonly string[];
15
14
  onSelectedChange: (ids: string[]) => void;
16
15
  /** The section's roster — already read for the Owner column. */
17
16
  members: OrgMemberOptions;
18
- /** How the export names an owner — the table's own options. */
19
- csv?: CompanyCsvOptions;
20
17
  }
21
18
  /**
22
19
  * The bar, or nothing — a separate boundary so the body's hooks never run
@@ -42,14 +42,13 @@ import { doc } from "firebase/firestore";
42
42
  import { useCallback, useMemo, useState } from "react";
43
43
  import { useCrmBulkApply } from "../hooks/use-crm-bulk-apply.js";
44
44
  import { planCompanyAddTag, planCompanyRemoveTag, planCompanySetOwner } from "../model/companies-bulk-writes.js";
45
- import { companiesCsv } from "../model/companies-csv.js";
46
45
  import { COMPANY_DETACH_LIMIT } from "../model/companies.js";
47
46
  import { deleteCompanyThroughRoute } from "../model/company-delete.js";
48
47
  import { normalizeBulkTag } from "../model/contacts-bulk-writes.js";
49
- import { downloadTextFile } from "../model/contacts-csv.js";
50
48
  import { crmBulkWriters, runCrmBulkCalls, runCrmBulkWrites } from "../model/crm-bulk-writes.js";
51
49
  import { CrmBulkBarFrame, CrmBulkValueDialog, countNoun } from "./crm-bulk-bar-frame.js";
52
- import CrmExportAllButton from "./crm-export-all-button.js";
50
+ import { CrmExportButton } from "./crm-transfer-buttons.js";
51
+ import { CRM_COMPANIES_RESOURCE } from "../transfer/fields.js";
53
52
  import { useCrmApi } from "./use-crm-api.js";
54
53
  import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
55
54
  const NOUN = {
@@ -81,8 +80,8 @@ const doneSentence = (action, done)=>{
81
80
  }
82
81
  CompaniesBulkBar.displayName = 'CompaniesBulkBar';
83
82
  function CompaniesBulkBarBody(props) {
84
- var _ref, _members_error, _ref1;
85
- const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props;
83
+ var _ref, _members_error;
84
+ const { hostId, scope, rows, selected, onSelectedChange, members } = props;
86
85
  const firestore = useFirestore();
87
86
  const { confirm } = useConfirmationContext();
88
87
  const logActivity = useHostActivityLogger(hostId != null ? hostId : undefined);
@@ -142,12 +141,6 @@ function CompaniesBulkBarBody(props) {
142
141
  selectedRows,
143
142
  runPlan
144
143
  ]);
145
- const handleExport = useCallback(()=>{
146
- downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv));
147
- }, [
148
- selectedRows,
149
- csv
150
- ]);
151
144
  const handleDelete = useCallback(async ()=>{
152
145
  if (!scope || !selectedRows.length) return;
153
146
  const count = selectedRows.length;
@@ -254,16 +247,10 @@ function CompaniesBulkBarBody(props) {
254
247
  onClick: ()=>openAction('owner'),
255
248
  children: 'Set owner'
256
249
  }),
257
- /*#__PURE__*/ _jsx(Button, {
258
- size: "small",
259
- disabled: busy,
260
- onClick: handleExport,
261
- children: 'Export CSV'
262
- }),
263
- /*#__PURE__*/ _jsx(CrmExportAllButton, {
264
- resource: "companies",
265
- orgId: (_ref1 = scope == null ? void 0 : scope[1]) != null ? _ref1 : null,
250
+ /*#__PURE__*/ _jsx(CrmExportButton, {
251
+ resource: CRM_COMPANIES_RESOURCE,
266
252
  hostId: hostId,
253
+ selection: selectedRows.map((row)=>row.$id),
267
254
  disabled: busy
268
255
  }),
269
256
  /*#__PURE__*/ _jsx(Button, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.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\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport {\n type CompanyCsvOptions,\n type CompanyCsvRow,\n companiesCsv,\n} from '../model/companies-csv'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\nimport { useCrmApi } from './use-crm-api'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly (CompanyBulkRow & CompanyCsvRow)[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n /** How the export names an owner — the table's own options. */\n csv?: CompanyCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n const followUpSharing = useCrmSharingFollowUp(hostId ?? null, scope?.[1] ?? null)\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: async () => {\n const outcome = await runCrmBulkWrites(writers, plan.writes, (write) => write.label)\n // A client-direct write owes the sharing rules a re-evaluation (AGL-3336).\n followUpSharing(\n 'companies',\n plan.writes.filter((write) => write.kind === 'update').map((write) => write.id),\n )\n return outcome\n },\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers, followUpSharing],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"companies\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","companiesCsv","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","downloadTextFile","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","useCrmApi","useCrmSharingFollowUp","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","csv","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","followUpSharing","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","outcome","write","label","kind","map","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","String","name","result","deleted","Error","toLocaleString","type","refused","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","orgId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.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\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport { CrmExportButton } from './crm-transfer-buttons'\nimport { CRM_COMPANIES_RESOURCE } from '../transfer/fields'\nimport { useCrmApi } from './use-crm-api'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly CompanyBulkRow[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n const followUpSharing = useCrmSharingFollowUp(hostId ?? null, scope?.[1] ?? null)\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: async () => {\n const outcome = await runCrmBulkWrites(writers, plan.writes, (write) => write.label)\n // A client-direct write owes the sharing rules a re-evaluation (AGL-3336).\n followUpSharing(\n 'companies',\n plan.writes.filter((write) => write.kind === 'update').map((write) => write.id),\n )\n return outcome\n },\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers, followUpSharing],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n {/* The selection through the export dialog: every field, chosen (AGL-3527). */}\n <CrmExportButton\n resource={CRM_COMPANIES_RESOURCE}\n hostId={hostId}\n selection={selectedRows.map((row) => row.$id)}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportButton","CRM_COMPANIES_RESOURCE","useCrmApi","useCrmSharingFollowUp","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","followUpSharing","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","outcome","write","label","kind","map","count","handleApply","nowMs","Date","now","tag","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","String","name","result","deleted","Error","toLocaleString","type","refused","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","selection"],"mappings":"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"}
@@ -43,9 +43,9 @@ export interface CompaniesSectionProps {
43
43
  * ## Selection, the bar and the file (AGL-2621)
44
44
  *
45
45
  * The rows are selectable, and a selection raises `CompaniesBulkBar` over
46
- * the table. Export CSV writes the loaded page through `companiesCsv()` —
47
- * the same file the bar writes over the selection, headed as the import
48
- * reads it — and Import CSV opens the companies drawer beside it.
46
+ * the table. Import and Export open the console's import wizard and
47
+ * export dialog on `crm.companies` (AGL-3527): the export over the
48
+ * selection, the list's own query, or every company.
49
49
  */
50
50
  export declare function CompaniesSection(props: CompaniesSectionProps): import("react").JSX.Element;
51
51
  export declare namespace CompaniesSection {