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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/package.json +14 -13
  2. package/src/index.d.ts +1 -0
  3. package/src/index.js.map +1 -1
  4. package/src/lib/components/companies-bulk-bar.js +12 -4
  5. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  6. package/src/lib/components/company-detail-page.js +11 -0
  7. package/src/lib/components/company-detail-page.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +12 -2
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/company-import-drawer.d.ts +1 -1
  11. package/src/lib/components/company-picker.js +10 -3
  12. package/src/lib/components/company-picker.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -4
  14. package/src/lib/components/contact-detail-page.js +50 -53
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-list-columns.d.ts +3 -0
  17. package/src/lib/components/contact-list-columns.js +21 -5
  18. package/src/lib/components/contact-list-columns.js.map +1 -1
  19. package/src/lib/components/contact-merge-dialog.js +2 -1
  20. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  21. package/src/lib/components/contact-timeline-card.js +4 -3
  22. package/src/lib/components/contact-timeline-card.js.map +1 -1
  23. package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
  24. package/src/lib/components/contacts-bulk-bar.js +24 -51
  25. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  26. package/src/lib/components/contacts-section.js +9 -3
  27. package/src/lib/components/contacts-section.js.map +1 -1
  28. package/src/lib/components/crm-export-all-button.d.ts +1 -1
  29. package/src/lib/components/crm-export-all-button.js.map +1 -1
  30. package/src/lib/components/crm-send-email-dialog.js +9 -6
  31. package/src/lib/components/crm-send-email-dialog.js.map +1 -1
  32. package/src/lib/components/csv-import-drawer.js +2 -2
  33. package/src/lib/components/csv-import-drawer.js.map +1 -1
  34. package/src/lib/components/deal-detail-page.js +18 -2
  35. package/src/lib/components/deal-detail-page.js.map +1 -1
  36. package/src/lib/components/deal-edit-drawer.js +10 -0
  37. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  38. package/src/lib/components/deals-bulk-bar.js +13 -5
  39. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +6 -0
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-detail-page.js +25 -5
  43. package/src/lib/components/lead-detail-page.js.map +1 -1
  44. package/src/lib/components/lead-properties-card.js +6 -0
  45. package/src/lib/components/lead-properties-card.js.map +1 -1
  46. package/src/lib/components/lead-surfaces-note.js +4 -5
  47. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  48. package/src/lib/components/lead-unqualify-dialog.js +6 -0
  49. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  50. package/src/lib/components/leads-bulk-bar.js +28 -3
  51. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  52. package/src/lib/components/leads-section.js +36 -7
  53. package/src/lib/components/leads-section.js.map +1 -1
  54. package/src/lib/components/org-lead-surfaces-note.js +6 -2
  55. package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
  56. package/src/lib/components/recipes-card.js +9 -4
  57. package/src/lib/components/recipes-card.js.map +1 -1
  58. package/src/lib/components/record-sharing-card.d.ts +57 -0
  59. package/src/lib/components/record-sharing-card.js +244 -0
  60. package/src/lib/components/record-sharing-card.js.map +1 -0
  61. package/src/lib/components/settings-section.js +7 -0
  62. package/src/lib/components/settings-section.js.map +1 -1
  63. package/src/lib/components/share-records-dialog.d.ts +28 -0
  64. package/src/lib/components/share-records-dialog.js +184 -0
  65. package/src/lib/components/share-records-dialog.js.map +1 -0
  66. package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
  67. package/src/lib/components/sharing-rule-drawer.js +374 -0
  68. package/src/lib/components/sharing-rule-drawer.js.map +1 -0
  69. package/src/lib/components/sharing-rules-card.d.ts +24 -0
  70. package/src/lib/components/sharing-rules-card.js +287 -0
  71. package/src/lib/components/sharing-rules-card.js.map +1 -0
  72. package/src/lib/components/use-crm-api.d.ts +1 -1
  73. package/src/lib/components/use-crm-api.js.map +1 -1
  74. package/src/lib/components/use-site-route-context.d.ts +25 -0
  75. package/src/lib/components/use-site-route-context.js +58 -0
  76. package/src/lib/components/use-site-route-context.js.map +1 -0
  77. package/src/lib/constants/api-routes.d.ts +31 -0
  78. package/src/lib/constants/api-routes.js +27 -1
  79. package/src/lib/constants/api-routes.js.map +1 -1
  80. package/src/lib/declarations.console-server.d.ts +37 -0
  81. package/src/lib/declarations.console-server.js +103 -0
  82. package/src/lib/declarations.console-server.js.map +1 -0
  83. package/src/lib/declarations.server.d.ts +6 -0
  84. package/src/lib/declarations.server.js +33 -0
  85. package/src/lib/declarations.server.js.map +1 -1
  86. package/src/lib/hooks/use-contact-remove.d.ts +35 -0
  87. package/src/lib/hooks/use-contact-remove.js +77 -0
  88. package/src/lib/hooks/use-contact-remove.js.map +1 -0
  89. package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
  90. package/src/lib/hooks/use-crm-sharing.js +109 -0
  91. package/src/lib/hooks/use-crm-sharing.js.map +1 -0
  92. package/src/lib/model/companies-csv.d.ts +1 -1
  93. package/src/lib/model/companies-csv.js +2 -2
  94. package/src/lib/model/companies-csv.js.map +1 -1
  95. package/src/lib/model/contact-holder.d.ts +68 -0
  96. package/src/lib/model/contact-holder.js +129 -0
  97. package/src/lib/model/contact-holder.js.map +1 -0
  98. package/src/lib/model/contact-interaction-links.d.ts +46 -0
  99. package/src/lib/model/contact-interaction-links.js +65 -0
  100. package/src/lib/model/contact-interaction-links.js.map +1 -0
  101. package/src/lib/model/contact-merge.d.ts +136 -0
  102. package/src/lib/model/contact-merge.js +412 -0
  103. package/src/lib/model/contact-merge.js.map +1 -0
  104. package/src/lib/model/contact-record.d.ts +7 -1
  105. package/src/lib/model/contact-record.js +6 -5
  106. package/src/lib/model/contact-record.js.map +1 -1
  107. package/src/lib/model/contact-remove.d.ts +48 -0
  108. package/src/lib/model/contact-remove.js +22 -0
  109. package/src/lib/model/contact-remove.js.map +1 -0
  110. package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
  111. package/src/lib/model/contacts-bulk-writes.js +2 -82
  112. package/src/lib/model/contacts-bulk-writes.js.map +1 -1
  113. package/src/lib/model/contacts-csv.d.ts +2 -2
  114. package/src/lib/model/contacts-csv.js +2 -2
  115. package/src/lib/model/contacts-csv.js.map +1 -1
  116. package/src/lib/model/crm-csv.d.ts +239 -0
  117. package/src/lib/model/crm-csv.js +467 -0
  118. package/src/lib/model/crm-csv.js.map +1 -0
  119. package/src/lib/model/crm-digest.d.ts +185 -0
  120. package/src/lib/model/crm-digest.js +272 -0
  121. package/src/lib/model/crm-digest.js.map +1 -0
  122. package/src/lib/model/crm-import.d.ts +7 -1
  123. package/src/lib/model/crm-import.js +2 -1
  124. package/src/lib/model/crm-import.js.map +1 -1
  125. package/src/lib/model/crm-record-routes.d.ts +5 -0
  126. package/src/lib/model/crm-record-routes.js +7 -0
  127. package/src/lib/model/crm-record-routes.js.map +1 -1
  128. package/src/lib/model/crm-routes.d.ts +6 -5
  129. package/src/lib/model/crm-routes.js +7 -7
  130. package/src/lib/model/crm-routes.js.map +1 -1
  131. package/src/lib/model/crm-search-sources.d.ts +15 -0
  132. package/src/lib/model/crm-search-sources.js +162 -0
  133. package/src/lib/model/crm-search-sources.js.map +1 -0
  134. package/src/lib/model/crm-sharing-api.d.ts +32 -0
  135. package/src/lib/model/crm-sharing-api.js +59 -0
  136. package/src/lib/model/crm-sharing-api.js.map +1 -0
  137. package/src/lib/model/crm-sharing.d.ts +333 -0
  138. package/src/lib/model/crm-sharing.js +603 -0
  139. package/src/lib/model/crm-sharing.js.map +1 -0
  140. package/src/lib/model/deals-csv.d.ts +1 -1
  141. package/src/lib/model/deals-csv.js +4 -5
  142. package/src/lib/model/deals-csv.js.map +1 -1
  143. package/src/lib/model/lead-filters.d.ts +1 -1
  144. package/src/lib/model/leads-csv.d.ts +1 -1
  145. package/src/lib/model/leads-csv.js +1 -1
  146. package/src/lib/model/leads-csv.js.map +1 -1
  147. package/src/lib/model/tasks-csv.d.ts +1 -1
  148. package/src/lib/model/tasks-csv.js +1 -1
  149. package/src/lib/model/tasks-csv.js.map +1 -1
  150. package/src/lib/plan-entitlements.d.ts +75 -0
  151. package/src/lib/plan-entitlements.js +56 -0
  152. package/src/lib/plan-entitlements.js.map +1 -0
  153. package/src/lib/plugin.js +3 -0
  154. package/src/lib/plugin.js.map +1 -1
  155. package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
  156. package/src/lib/server/api-v1/contacts-merge.js +120 -0
  157. package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
  158. package/src/lib/server/api-v1/contacts.d.ts +18 -0
  159. package/src/lib/server/api-v1/contacts.js +1076 -0
  160. package/src/lib/server/api-v1/contacts.js.map +1 -0
  161. package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
  162. package/src/lib/server/api-v1/crm-activities.js +269 -0
  163. package/src/lib/server/api-v1/crm-activities.js.map +1 -0
  164. package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
  165. package/src/lib/server/api-v1/crm-companies.js +391 -0
  166. package/src/lib/server/api-v1/crm-companies.js.map +1 -0
  167. package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
  168. package/src/lib/server/api-v1/crm-deals.js +631 -0
  169. package/src/lib/server/api-v1/crm-deals.js.map +1 -0
  170. package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
  171. package/src/lib/server/api-v1/crm-email-templates.js +336 -0
  172. package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
  173. package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
  174. package/src/lib/server/api-v1/crm-leads.js +832 -0
  175. package/src/lib/server/api-v1/crm-leads.js.map +1 -0
  176. package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
  177. package/src/lib/server/api-v1/crm-pipelines.js +146 -0
  178. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
  179. package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
  180. package/src/lib/server/api-v1/crm-shared.js +436 -0
  181. package/src/lib/server/api-v1/crm-shared.js.map +1 -0
  182. package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
  183. package/src/lib/server/api-v1/crm-tasks.js +398 -0
  184. package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.d.ts +29 -0
  186. package/src/lib/server/api-v1/openapi.js +985 -0
  187. package/src/lib/server/api-v1/openapi.js.map +1 -0
  188. package/src/lib/server/api-v1/usage.d.ts +26 -0
  189. package/src/lib/server/api-v1/usage.js +73 -0
  190. package/src/lib/server/api-v1/usage.js.map +1 -0
  191. package/src/lib/server/consent-group-participant.js +1 -1
  192. package/src/lib/server/consent-group-participant.js.map +1 -1
  193. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  194. package/src/lib/server/contact-lifecycle-floor.js +91 -0
  195. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  196. package/src/lib/server/contact-remove.d.ts +75 -0
  197. package/src/lib/server/contact-remove.js +244 -0
  198. package/src/lib/server/contact-remove.js.map +1 -0
  199. package/src/lib/server/contact-update.js +2 -1
  200. package/src/lib/server/contact-update.js.map +1 -1
  201. package/src/lib/server/contacts-import.d.ts +47 -11
  202. package/src/lib/server/contacts-import.js +171 -71
  203. package/src/lib/server/contacts-import.js.map +1 -1
  204. package/src/lib/server/contacts-merge.d.ts +1 -1
  205. package/src/lib/server/contacts-merge.js +3 -2
  206. package/src/lib/server/contacts-merge.js.map +1 -1
  207. package/src/lib/server/convert-host-lead.d.ts +189 -0
  208. package/src/lib/server/convert-host-lead.js +518 -0
  209. package/src/lib/server/convert-host-lead.js.map +1 -0
  210. package/src/lib/server/crm-sharing.d.ts +108 -0
  211. package/src/lib/server/crm-sharing.js +790 -0
  212. package/src/lib/server/crm-sharing.js.map +1 -0
  213. package/src/lib/server/daily-digest-route.d.ts +107 -0
  214. package/src/lib/server/daily-digest-route.js +588 -0
  215. package/src/lib/server/daily-digest-route.js.map +1 -0
  216. package/src/lib/server/export-route.d.ts +60 -0
  217. package/src/lib/server/export-route.js +447 -0
  218. package/src/lib/server/export-route.js.map +1 -0
  219. package/src/lib/server/inbound-route.d.ts +74 -0
  220. package/src/lib/server/inbound-route.js +257 -0
  221. package/src/lib/server/inbound-route.js.map +1 -0
  222. package/src/lib/server/lead-convert.d.ts +1 -1
  223. package/src/lib/server/lead-convert.js +2 -2
  224. package/src/lib/server/lead-convert.js.map +1 -1
  225. package/src/lib/server/merge-contacts.d.ts +55 -0
  226. package/src/lib/server/merge-contacts.js +274 -0
  227. package/src/lib/server/merge-contacts.js.map +1 -0
  228. package/src/lib/server/pipeline-record-index.d.ts +36 -0
  229. package/src/lib/server/pipeline-record-index.js +87 -0
  230. package/src/lib/server/pipeline-record-index.js.map +1 -0
  231. package/src/lib/server/record-facts.js +2 -1
  232. package/src/lib/server/record-facts.js.map +1 -1
  233. package/src/lib/server/task-reminders-route.d.ts +82 -0
  234. package/src/lib/server/task-reminders-route.js +497 -0
  235. package/src/lib/server/task-reminders-route.js.map +1 -0
  236. package/src/lib/server-deal-stage.js +2 -1
  237. package/src/lib/server-deal-stage.js.map +1 -1
  238. package/src/lib/server.d.ts +5 -2
  239. package/src/lib/server.js +47 -2
  240. package/src/lib/server.js.map +1 -1
  241. package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
  242. package/src/lib/testing/stand-in-site-record-routes.js +55 -0
  243. package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
  244. package/src/lib/usage-axes.d.ts +35 -0
  245. package/src/lib/usage-axes.js +68 -0
  246. package/src/lib/usage-axes.js.map +1 -0
  247. package/src/lib/components/use-emails-hub-path.d.ts +0 -24
  248. package/src/lib/components/use-emails-hub-path.js +0 -59
  249. package/src/lib/components/use-emails-hub-path.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-crm",
3
- "version": "1.0.0-beta.217",
3
+ "version": "1.0.0-beta.219",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.217",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.217",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.217",
31
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.217",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.217",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.217",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.217",
35
- "@aglyn/shared-util-email": "1.0.0-beta.217",
36
- "@aglyn/shared-util-http": "1.0.0-beta.217",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.217",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.217",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.217",
28
+ "@aglyn/aglyn": "1.0.0-beta.219",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.219",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.219",
31
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.219",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.219",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.219",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.219",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.219",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.219",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.219",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.219",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.219",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.219",
40
41
  "@dnd-kit/core": "^6.3.1",
41
42
  "@dnd-kit/utilities": "^3.2.2",
42
43
  "@swc/helpers": "0.5.23",
package/src/index.d.ts CHANGED
@@ -19,3 +19,4 @@ export * from './lib/plugin';
19
19
  export * from './lib/components/crm-console-sections';
20
20
  export * from './lib/model/crm-routes';
21
21
  export * from './lib/hooks/use-contact-field-definitions';
22
+ export type { crmPlanEntitlements } from './lib/plan-entitlements';
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../libs/plugins/crm/src/index.ts"],"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\nexport * from './lib/constants/bundle-common'\nexport * from './lib/plugin'\n// The hub's section ids and the one way to link into it (AGL-2595), so a\n// surface outside this plugin — a campaign naming a contact, an order\n// naming a company — builds the same address the hub resolves.\nexport * from './lib/components/crm-console-sections'\nexport * from './lib/model/crm-routes'\n// The org's custom contact field definitions, read once and shared\n// (AGL-2601): the contact list's columns, the profile card, the form editor's\n// mapping picker, an import's target list and an audience filter all ask the\n// same question of the same small collection.\nexport * from './lib/hooks/use-contact-field-definitions'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,mCAA+B;AAC7C,cAAc,kBAAc;AAC5B,yEAAyE;AACzE,sEAAsE;AACtE,+DAA+D;AAC/D,cAAc,2CAAuC;AACrD,cAAc,4BAAwB;AACtC,mEAAmE;AACnE,8EAA8E;AAC9E,6EAA6E;AAC7E,8CAA8C;AAC9C,cAAc,+CAA2C"}
1
+ {"version":3,"sources":["../../../../../libs/plugins/crm/src/index.ts"],"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\nexport * from './lib/constants/bundle-common'\nexport * from './lib/plugin'\n// The hub's section ids and the one way to link into it (AGL-2595), so a\n// surface outside this plugin — a campaign naming a contact, an order\n// naming a company — builds the same address the hub resolves.\nexport * from './lib/components/crm-console-sections'\nexport * from './lib/model/crm-routes'\n// The org's custom contact field definitions, read once and shared\n// (AGL-2601): the contact list's columns, the profile card, the form editor's\n// mapping picker, an import's target list and an audience filter all ask the\n// same question of the same small collection.\nexport * from './lib/hooks/use-contact-field-definitions'\n// Type-only (AGL-3080): the plugin's entitlement keys are declared by module\n// augmentation in this module, so every program that loads the package\n// type-checks them. The figures reach core through the manifest generator.\nexport type { crmPlanEntitlements } from './lib/plan-entitlements'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,mCAA+B;AAC7C,cAAc,kBAAc;AAC5B,yEAAyE;AACzE,sEAAsE;AACtE,+DAA+D;AAC/D,cAAc,2CAAuC;AACrD,cAAc,4BAAwB;AACtC,mEAAmE;AACnE,8EAA8E;AAC9E,6EAA6E;AAC7E,8CAA8C;AAC9C,cAAc,+CAA2C"}
@@ -51,6 +51,7 @@ import { crmBulkWriters, runCrmBulkCalls, runCrmBulkWrites } from "../model/crm-
51
51
  import { CrmBulkBarFrame, CrmBulkValueDialog, countNoun } from "./crm-bulk-bar-frame.js";
52
52
  import CrmExportAllButton from "./crm-export-all-button.js";
53
53
  import { useCrmApi } from "./use-crm-api.js";
54
+ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
54
55
  const NOUN = {
55
56
  singular: 'company',
56
57
  plural: 'companies'
@@ -80,7 +81,7 @@ const doneSentence = (action, done)=>{
80
81
  }
81
82
  CompaniesBulkBar.displayName = 'CompaniesBulkBar';
82
83
  function CompaniesBulkBarBody(props) {
83
- var _members_error, _ref;
84
+ var _ref, _members_error, _ref1;
84
85
  const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props;
85
86
  const firestore = useFirestore();
86
87
  const { confirm } = useConfirmationContext();
@@ -89,6 +90,7 @@ function CompaniesBulkBarBody(props) {
89
90
  const { busy, report, apply, dismissReport } = useCrmBulkApply({
90
91
  recordKind: 'company'
91
92
  });
93
+ const followUpSharing = useCrmSharingFollowUp(hostId != null ? hostId : null, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
92
94
  const selectedRows = useMemo(()=>{
93
95
  const chosen = new Set(selected);
94
96
  return rows.filter((row)=>chosen.has(row.$id));
@@ -112,11 +114,17 @@ function CompaniesBulkBarBody(props) {
112
114
  const runPlan = useCallback((action, plan)=>apply({
113
115
  attempted: plan.writes.length,
114
116
  skipped: plan.skipped,
115
- job: ()=>runCrmBulkWrites(writers, plan.writes, (write)=>write.label),
117
+ job: async ()=>{
118
+ const outcome = await runCrmBulkWrites(writers, plan.writes, (write)=>write.label);
119
+ // A client-direct write owes the sharing rules a re-evaluation (AGL-3336).
120
+ followUpSharing('companies', plan.writes.filter((write)=>write.kind === 'update').map((write)=>write.id));
121
+ return outcome;
122
+ },
116
123
  done: (count)=>doneSentence(action, count)
117
124
  }), [
118
125
  apply,
119
- writers
126
+ writers,
127
+ followUpSharing
120
128
  ]);
121
129
  const handleApply = useCallback(async ()=>{
122
130
  if (!pending || !scope) return;
@@ -254,7 +262,7 @@ function CompaniesBulkBarBody(props) {
254
262
  }),
255
263
  /*#__PURE__*/ _jsx(CrmExportAllButton, {
256
264
  resource: "companies",
257
- orgId: (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null,
265
+ orgId: (_ref1 = scope == null ? void 0 : scope[1]) != null ? _ref1 : null,
258
266
  hostId: hostId,
259
267
  disabled: busy
260
268
  }),
@@ -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'\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\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: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers],\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","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","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","write","label","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","String","name","result","deleted","Error","toLocaleString","type","refused","map","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 {\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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"}
@@ -33,6 +33,7 @@ import { RecordActivityCard } from "./record-activity-card.js";
33
33
  import RecordFilesCard from "./record-files-card.js";
34
34
  import { RecordTasksCard } from "./record-tasks-card.js";
35
35
  import CompanyPropertiesCard from "./company-properties-card.js";
36
+ import { RecordSharingCard } from "./record-sharing-card.js";
36
37
  import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
37
38
  /**
38
39
  * `/crm/companies/{companyId}` — one organization (AGL-2597).
@@ -49,6 +50,7 @@ import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
49
50
  * not a link anybody can paste.
50
51
  */ export function CompanyDetailPage(props) {
51
52
  var _company_hostId, _company_name, _company_name1, _company_name2;
53
+ var _crmScope_consentGroup;
52
54
  const { id, basePath, hostId, org } = props;
53
55
  const routes = crmRoutes(basePath);
54
56
  const router = useRouter();
@@ -150,6 +152,15 @@ import { CrmRecordInsightsZone } from "./crm-record-insights-zone.js";
150
152
  mediaIds: company.mediaIds,
151
153
  topic: "companies"
152
154
  }),
155
+ /*#__PURE__*/ _jsx(RecordSharingCard, {
156
+ object: "companies",
157
+ id: id,
158
+ record: company,
159
+ hostId: hostId != null ? hostId : null,
160
+ orgId: orgId,
161
+ org: org,
162
+ viewingHostIds: (_crmScope_consentGroup = crmScope.consentGroup) == null ? void 0 : _crmScope_consentGroup.hostIds
163
+ }),
153
164
  /*#__PURE__*/ _jsx(RecordActivityCard, {
154
165
  hostId: hostId,
155
166
  org: org,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.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 { CRM_COLLECTIONS, type CrmCompany, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard from './company-properties-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","id","basePath","hostId","org","routes","router","firestore","crmScope","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","anchor","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-detail-page.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 { CRM_COLLECTIONS, type CrmCompany, pluginDocsHelp } from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback } from 'react'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport CompanyContactsCard from './company-contacts-card'\nimport CompanyDealsCard from './company-deals-card'\nimport { CrmRecordHeader } from './crm-record-header'\nimport { RecordActivityCard } from './record-activity-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\nimport CompanyPropertiesCard from './company-properties-card'\nimport { RecordSharingCard } from './record-sharing-card'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\n\n/**\n * `/crm/companies/{companyId}` — one organization (AGL-2597).\n *\n * The record behind a row of the Companies section: its properties, its\n * people, and — as the rest of the CRM lands — its deals, its open tasks\n * and the activity logged against it. Each of those is a card in a file of\n * its own, so the agent shipping deals replaces one file and touches nothing\n * here; this page reads the ONE document every card is about and hands it\n * down.\n *\n * A route rather than a drawer because a company is what a deal, a task and\n * a contact all point at, and a link that opens a drawer on top of a list is\n * not a link anybody can paste.\n */\nexport function CompanyDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const crmScope = useCrmScope({ hostId, org })\n const { scope, orgId } = crmScope\n const members = useOrgMemberOptions(orgId)\n\n const {\n data: company,\n status,\n fromCache,\n } = useFirestoreDoc<CrmCompany>(\n () =>\n scope\n ? doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id)\n : null,\n [firestore, scope, id],\n )\n\n const onDeleted = useCallback(\n () => router.push(routes.section('companies')),\n [router, routes],\n )\n\n if (!company) {\n /*\n * Loading and MISSING are different answers. A company that cannot be\n * read is a different situation from an empty one, and reading `status`\n * rather than the absence of data is what stops the refusal being\n * flashed on every arrival, before the org scope has resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CrmRecordHeader\n kind=\"Company\"\n title={undefined}\n help={pluginDocsHelp('companies', { anchor: '#a-companys-page' })}\n backHref={routes.section('companies')}\n backLabel=\"Back to companies\"\n loading={!settled}\n >\n {settled ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This company could not be loaded. It may have been deleted.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n )\n }\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the company's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={company.hostId ?? null}>\n {/* The properties card is the record's lead card: it publishes the\n page heading and the trail, so the cards under it say what they\n hold rather than repeating the name. */}\n <Stack spacing={2}>\n <CompanyPropertiesCard\n company={{ ...company, $id: id }}\n seed={{ fromCache, unreadable: status === 'error' }}\n hostId={hostId}\n org={org}\n crmScope={crmScope}\n members={members}\n routes={routes}\n onDeleted={onDeleted}\n />\n {/* What an assistant proposes about the company (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"company\"\n recordId={id}\n name={String(company.name ?? '')}\n taskLink={{ companyId: id }}\n />\n <CompanyContactsCard\n companyId={id}\n companyName={String(company.name ?? '')}\n crmScope={crmScope}\n org={org as Record<string, unknown> | undefined}\n routes={routes}\n />\n <CompanyDealsCard\n hostId={hostId}\n org={org}\n basePath={basePath}\n companyId={id}\n companyName={String(company.name ?? '')}\n />\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} companyId={id} />\n <RecordFilesCard\n scope={scope}\n collection={CRM_COLLECTIONS.companies}\n recordId={id}\n mediaIds={company.mediaIds}\n topic=\"companies\"\n />\n {/* Where the company is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"companies\"\n id={id}\n record={company as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={orgId}\n org={org}\n viewingHostIds={crmScope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} companyId={id} />\n </Stack>\n </CrmCreateSiteDefault>\n )\n}\nCompanyDetailPage.displayName = 'CompanyDetailPage'\n\nexport default CompanyDetailPage\n"],"names":["CRM_COLLECTIONS","pluginDocsHelp","useFirestore","useFirestoreDoc","Stack","Typography","doc","useRouter","useCallback","CrmCreateSiteDefault","useCrmScope","useOrgMemberOptions","crmRoutes","CompanyContactsCard","CompanyDealsCard","CrmRecordHeader","RecordActivityCard","RecordFilesCard","RecordTasksCard","CompanyPropertiesCard","RecordSharingCard","CrmRecordInsightsZone","CompanyDetailPage","props","company","crmScope","id","basePath","hostId","org","routes","router","firestore","scope","orgId","members","data","status","fromCache","companies","onDeleted","push","section","settled","Boolean","kind","title","undefined","help","anchor","backHref","backLabel","loading","variant","color","spacing","$id","seed","unreadable","recordId","name","String","taskLink","companyId","companyName","collection","mediaIds","topic","object","record","viewingHostIds","consentGroup","hostIds","displayName"],"mappings":"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"}
@@ -35,6 +35,7 @@ import { crmCustomDraftDocument, crmCustomDraftMissingRequired, crmCustomDraftVa
35
35
  import { CrmCustomFieldControl } from "./crm-custom-field-control.js";
36
36
  import { CrmSitePicker } from "./crm-site-picker.js";
37
37
  import { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
38
+ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
38
39
  const ADDRESS_FIELDS = [
39
40
  {
40
41
  key: 'line1',
@@ -74,7 +75,7 @@ const ADDRESS_FIELDS = [
74
75
  * `acme` or a phone number with no country code is refused — as a message
75
76
  * beside the button, with the typed values left where they are.
76
77
  */ export function CompanyEditDrawer(props) {
77
- var _ref, _members_error;
78
+ var _ref, _ref1, _members_error;
78
79
  const { open, onClose, hostId, org, company, seed, members, onSaved } = props;
79
80
  const firestore = useFirestore();
80
81
  const { data: user } = useUser();
@@ -83,6 +84,8 @@ const ADDRESS_FIELDS = [
83
84
  hostId,
84
85
  org
85
86
  });
87
+ // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).
88
+ const followUpSharing = useCrmSharingFollowUp(hostId, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
86
89
  // The feed the act is logged in is the level it was PERFORMED at
87
90
  // (AGL-2738): this site's under a site, the organization's at the org
88
91
  // hub. `createHostId` is where the record is STAMPED, which at the hub is
@@ -97,7 +100,7 @@ const ADDRESS_FIELDS = [
97
100
  * keys the reader has touched over what the company stores. Saved as the
98
101
  * difference — see `crm-custom-draft` — beside the fixed fields, in the
99
102
  * same write, so a company is never half-saved.
100
- */ const fields = useContactFieldDefinitions((_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null, 'company');
103
+ */ const fields = useContactFieldDefinitions((_ref1 = scope == null ? void 0 : scope[1]) != null ? _ref1 : null, 'company');
101
104
  const storedCustom = useMemo(()=>{
102
105
  var _ref;
103
106
  return (_ref = company == null ? void 0 : company.custom) != null ? _ref : {};
@@ -180,6 +183,9 @@ const ADDRESS_FIELDS = [
180
183
  }));
181
184
  });
182
185
  if (!verdict.ok) return setError(verdict.message);
186
+ followUpSharing('companies', [
187
+ company.$id
188
+ ]);
183
189
  enqueueSnackbar('Company saved', {
184
190
  variant: 'success',
185
191
  persist: false
@@ -217,6 +223,9 @@ const ADDRESS_FIELDS = [
217
223
  // Setup → Activity shows CRM work (AGL-2622): the company is org
218
224
  // data, but the act happened in this site's console and belongs in
219
225
  // its feed. Creation only — an edit is a save the card reports.
226
+ followUpSharing('companies', [
227
+ id
228
+ ]);
220
229
  logActivity('Added company', {
221
230
  type: 'company',
222
231
  id,
@@ -238,6 +247,7 @@ const ADDRESS_FIELDS = [
238
247
  }, [
239
248
  busy,
240
249
  scope,
250
+ followUpSharing,
241
251
  draft,
242
252
  company,
243
253
  bandFull,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n type AglynPostalAddress,\n createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmCompany,\n crmNewRecordListFields,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from '../model/crm-list-query'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, 'address'>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n address: { ...current.address, [field]: value },\n })),\n [],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft)\n if (result.ok === false) return setError(result.error)\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n CRM_CLIENT_SEARCH_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\n )\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed. Creation only — an edit is a save the card reports.\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Industry\"\n value={draft.industry}\n onChange={(event) => patch('industry', event.target.value)}\n />\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={field.key}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress(field.key, event.target.value)}\n />\n ))}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","crmClientListFields","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","companyId","$id","ownerUid","uid","patch","field","value","current","patchAddress","address","handleSave","result","ok","missing","active","length","join","customDocument","ref","companies","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","set","updatedAt","message","variant","persist","id","visibleTo","createdByUid","createdAt","type","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","placeholder","domain","website","phone","industry","select","options","some","option","labelFor","tags","notes","multiline","minRows","definition","direction","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/company-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n type AglynPostalAddress,\n createResourceUid,\n CRM_COLLECTIONS,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n type CrmCompany,\n crmNewRecordListFields,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, HelpTip, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n deleteField,\n doc,\n serverTimestamp,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { useCrmRecordsQuota } from '../hooks/use-crm-records-quota'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyDraft,\n companyDraftFields,\n companyDraftFrom,\n EMPTY_COMPANY_DRAFT,\n} from '../model/companies'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n crmCustomDraftValue,\n crmCustomDraftWrites,\n} from '../model/crm-custom-draft'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\nexport interface CompanyEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW company asks which site captures it and\n * an edit keeps the company's own.\n */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n /**\n * The company being EDITED, or nothing to create one.\n *\n * The two modes differ in what the fields start as and in which write\n * runs: an edit opens on the stored values and updates the document, a\n * create opens empty and stamps the scope, provenance and timestamps a new\n * record must carry.\n */\n company?: (Partial<CrmCompany> & { $id: string }) | null\n /**\n * Whether the stored values the edit opened on can be trusted.\n *\n * Every optional field is written on every save — a blank one is a\n * DELETE — so a form seeded from a cached read would write the cache's\n * blanks over the server's values. The card that holds the listen reports\n * its own `fromCache` and `status`; the drawer refuses to save on a seed\n * the server never confirmed, and says so.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n /**\n * The team, for the owner picker.\n *\n * Handed in rather than fetched here, because the surface opening this\n * drawer already holds the roster to name owners in its rows, and one\n * fetch per surface is the bargain.\n */\n members: OrgMemberOptions\n /** Called with the record's id after a successful write. */\n onSaved: (companyId: string) => void\n}\n\nconst ADDRESS_FIELDS: ReadonlyArray<{\n key: keyof AglynPostalAddress\n label: string\n}> = [\n { key: 'line1', label: 'Address line 1' },\n { key: 'line2', label: 'Address line 2' },\n { key: 'city', label: 'City' },\n { key: 'state', label: 'State or region' },\n { key: 'postalCode', label: 'Postal code' },\n { key: 'country', label: 'Country (two-letter code)' },\n]\n\n/**\n * ONE COMPANY, created or edited (AGL-2597).\n *\n * A drawer rather than a form above the list, and the same drawer for both\n * jobs: a company is eight fields and an address, which is too much to wedge\n * over a table and exactly the same set whether the record exists yet. The\n * list opens it empty; the company's page opens it on the record.\n *\n * Nothing is written per keystroke. The draft is held here and turned into a\n * document by `companyDraftFields` on Save, which is where a domain typed as\n * `acme` or a phone number with no country code is refused — as a message\n * beside the button, with the typed values left where they are.\n */\nexport function CompanyEditDrawer(props: CompanyEditDrawerProps) {\n const { open, onClose, hostId, org, company, seed, members, onSaved } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { scope, createTokens, createHostId } = useCrmScope({ hostId, org })\n // A client-direct write owes the org's sharing rules a re-evaluation (AGL-3336).\n const followUpSharing = useCrmSharingFollowUp(hostId, scope?.[1] ?? null)\n // The feed the act is logged in is the level it was PERFORMED at\n // (AGL-2738): this site's under a site, the organization's at the org\n // hub. `createHostId` is where the record is STAMPED, which at the hub is\n // a site the reader picked and may hold no role on — a different question\n // from where the line belongs, and a gate the record write never faced.\n const logActivity = useCrmActivityLogger(hostId)\n\n const [draft, setDraft] = useState<CompanyDraft>(EMPTY_COMPANY_DRAFT)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n /*\n * THE CUSTOM FIELDS (AGL-2661): the org's company definitions, and the\n * keys the reader has touched over what the company stores. Saved as the\n * difference — see `crm-custom-draft` — beside the fixed fields, in the\n * same write, so a company is never half-saved.\n */\n const fields = useContactFieldDefinitions(scope?.[1] ?? null, 'company')\n const storedCustom = useMemo(() => company?.custom ?? {}, [company?.custom])\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n /*\n * THE RECORDS BAND (AGL-2611), read only for a CREATE: a company is a\n * record of the band the contacts list is banded by, and on an org at a\n * full hard band this drawer refuses the way `upsertHostContact` refuses a\n * capture — same number, same sentence. An edit is not a record and pays\n * for no aggregate. Only a SETTLED refusal refuses: three reads that have\n * not answered are not a full band.\n */\n const records = useCrmRecordsQuota(open && !company ? scope : null, org)\n const bandFull = !company && records.ready && !records.quota.allowed\n\n /*\n * Seeded when the drawer OPENS, and not on every render of the card\n * behind it: the company listen is live, so re-seeding on each snapshot\n * would take a field away from whoever is typing in it — including on the\n * snapshot their own save produces. The create case seeds the owner to the\n * person filing the record, which is the answer nine times in ten and one\n * click to change.\n */\n const companyId = company?.$id\n useEffect(() => {\n if (!open) return\n setDraft(\n company\n ? companyDraftFrom(company)\n : { ...EMPTY_COMPANY_DRAFT, ownerUid: user?.uid ?? '' },\n )\n setCustom({})\n setError('')\n // The stored fields are read once per opening; `company` is a new object\n // on every snapshot and must not re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, companyId])\n\n const patch = useCallback(\n (field: keyof Omit<CompanyDraft, 'address'>, value: string) =>\n setDraft((current) => ({ ...current, [field]: value })),\n [],\n )\n const patchAddress = useCallback(\n (field: keyof AglynPostalAddress, value: string) =>\n setDraft((current) => ({\n ...current,\n address: { ...current.address, [field]: value },\n })),\n [],\n )\n\n const handleSave = useCallback(async () => {\n if (busy || !scope) return\n const result = companyDraftFields(draft)\n if (result.ok === false) return setError(result.error)\n const missing = crmCustomDraftMissingRequired(\n fields.active,\n storedCustom,\n custom,\n company ? 'edit' : 'create',\n )\n if (missing.length) return setError(`${missing.join(', ')} is required.`)\n const customDocument = crmCustomDraftDocument(custom)\n setError('')\n setBusy(true)\n try {\n if (company) {\n const ref = doc(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.companies,\n company.$id,\n )\n // The guard WRAPS the write — an early return is a shape you can\n // keep while losing the protection.\n const verdict = await writeGuardedBySeed(\n {\n subject: 'company',\n unreadable: seed?.unreadable ?? false,\n fromCache: seed?.fromCache ?? false,\n },\n async () => {\n const cleared = Object.fromEntries(\n result.cleared.map((field) => [field, deleteField()]),\n )\n // The stored record the list fields are computed from: the row\n // without the id the listener stamped on it.\n const { $id: _rowId, ...stored } = company\n const searchFields = crmClientListFields(\n 'companies',\n stored,\n { ...result.set, ...cleared },\n CRM_CLIENT_SEARCH_FIELDS,\n )\n await updateDoc(ref, {\n ...result.set,\n // A blank optional field is a DELETE on an edit, or the old\n // domain would stay stored and keep matching people by email.\n ...cleared,\n // The name and domain are what the Companies list searches\n // (AGL-3321), from the listener's row with the edit over it.\n ...searchFields,\n // The custom keys that changed, merged into the stored map.\n ...crmCustomDraftWrites(storedCustom, custom),\n updatedAt: serverTimestamp(),\n })\n },\n )\n if (!verdict.ok) return setError(verdict.message)\n followUpSharing('companies', [company.$id])\n enqueueSnackbar('Company saved', { variant: 'success', persist: false })\n onSaved(company.$id)\n } else {\n if (bandFull) return setError(CRM_RECORDS_BAND_FULL_MESSAGE)\n const id = createResourceUid()\n // Held by the button until the capturing site is known; checked\n // again here because a callback can outlive the render that held it.\n if (!createHostId) return\n await setDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id),\n {\n ...result.set,\n ...(customDocument ? { custom: customDocument } : {}),\n // What the Companies list searches and filters by (AGL-3321).\n ...crmNewRecordListFields('companies', {\n ...result.set,\n visibleTo: [...createTokens],\n }),\n /*\n * The scope every CRM creator stamps: the whole org when the org\n * shares by default, and otherwise the sites that present as one\n * sender. Without it the record is seen by NOBODY — an absent\n * `visibleTo` matches no reader's predicate — and the rules\n * refuse a scoped member creating outside their own tokens.\n */\n visibleTo: [...createTokens],\n // Provenance: the site whose console filed it, never rewritten.\n hostId: createHostId,\n createdByUid: user?.uid ?? '',\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n },\n )\n // Setup → Activity shows CRM work (AGL-2622): the company is org\n // data, but the act happened in this site's console and belongs in\n // its feed. Creation only — an edit is a save the card reports.\n followUpSharing('companies', [id])\n logActivity('Added company', { type: 'company', id, name: draft.name.trim() })\n enqueueSnackbar(`Company \"${draft.name.trim()}\" created`, {\n variant: 'success',\n persist: false,\n })\n onSaved(id)\n }\n onClose()\n } catch (caught) {\n console.error(caught)\n setError('The company could not be saved.')\n } finally {\n setBusy(false)\n }\n }, [\n busy,\n scope,\n followUpSharing,\n draft,\n company,\n bandFull,\n firestore,\n seed,\n createTokens,\n createHostId,\n user,\n logActivity,\n enqueueSnackbar,\n onSaved,\n onClose,\n fields.active,\n storedCustom,\n custom,\n ])\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>{'close drawer'}</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {company ? 'Edit company' : 'New company'}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('companies', { anchor: '#create-a-company' })}\n />\n </>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {/*\n First, because the scope of everything below follows from it: at\n the organization level a new company names the site that\n captures it. Nothing under a site, and nothing on an edit — a\n company keeps the site it was made on (AGL-2630).\n */}\n {company ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n helperText=\"The site this company belongs to — it decides which of your sites may see it.\"\n />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organization behind one or more of your contacts. The ' +\n 'domain is what suggests this company for a person from their ' +\n 'email address, so enter the bare hostname.'}\n </Typography>\n {bandFull ? (\n <Alert severity=\"warning\">{CRM_RECORDS_BAND_FULL_MESSAGE}</Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={draft.name}\n onChange={(event) => patch('name', event.target.value)}\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Domain\"\n placeholder=\"example.com\"\n value={draft.domain}\n onChange={(event) => patch('domain', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n placeholder=\"https://www.example.com\"\n value={draft.website}\n onChange={(event) => patch('website', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n placeholder=\"+1 512 555 0123\"\n value={draft.phone}\n onChange={(event) => patch('phone', event.target.value)}\n />\n <TextField\n size=\"small\"\n label=\"Industry\"\n value={draft.industry}\n onChange={(event) => patch('industry', event.target.value)}\n />\n {/*\n The owner is chosen from the team, never typed: the value is a\n uid, and a picker that is still loading says so rather than\n offering an empty list that reads as \"nobody can own this\".\n */}\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={draft.ownerUid}\n disabled={!members.ready}\n helperText={\n !members.ready\n ? 'Loading your team…'\n : (members.error ?? 'The team member responsible for this account.')\n }\n onChange={(event) => patch('ownerUid', event.target.value)}\n >\n <MenuItem value=\"\">{'Nobody yet'}</MenuItem>\n {draft.ownerUid &&\n !members.options.some((option) => option.uid === draft.ownerUid) ? (\n <MenuItem value={draft.ownerUid}>\n {members.labelFor(draft.ownerUid)}\n </MenuItem>\n ) : null}\n {members.options.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {crmMemberPickerLabel(option)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"enterprise, west\"\n helperText=\"Comma-separated, lowercased — the same tags the bulk bar adds\"\n value={draft.tags}\n onChange={(event) => patch('tags', event.target.value)}\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n {ADDRESS_FIELDS.map((field) => (\n <TextField\n key={field.key}\n size=\"small\"\n label={field.label}\n value={draft.address[field.key] ?? ''}\n onChange={(event) => patchAddress(field.key, event.target.value)}\n />\n ))}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={draft.notes}\n onChange={(event) => patch('notes', event.target.value)}\n multiline\n minRows={3}\n />\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={crmCustomDraftValue(storedCustom, custom, definition.key)}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={busy}\n />\n ))}\n </>\n ) : null}\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n // A new company waits for its capturing site.\n disabled={busy || !scope || !draft.name.trim() || (!company && !createHostId)}\n onClick={() => void handleSave()}\n >\n {busy\n ? 'Saving…'\n : company\n ? 'Save company'\n : 'Create company'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\nCompanyEditDrawer.displayName = 'CompanyEditDrawer'\n\nexport default CompanyEditDrawer\n"],"names":["createResourceUid","CRM_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","crmNewRecordListFields","pluginDocsHelp","crmMemberPickerLabel","ICON_VARIANT_CLOSE","Container","HelpTip","MdiIcon","SrOnly","NavigationDrawerComponent","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","deleteField","doc","serverTimestamp","setDoc","updateDoc","useCallback","useEffect","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","useCrmRecordsQuota","useCrmScope","companyDraftFields","companyDraftFrom","EMPTY_COMPANY_DRAFT","crmCustomDraftDocument","crmCustomDraftMissingRequired","crmCustomDraftValue","crmCustomDraftWrites","CrmCustomFieldControl","CrmSitePicker","CRM_CLIENT_SEARCH_FIELDS","crmClientListFields","useCrmSharingFollowUp","ADDRESS_FIELDS","key","label","CompanyEditDrawer","props","members","open","onClose","hostId","org","company","seed","onSaved","firestore","data","user","enqueueSnackbar","scope","createTokens","createHostId","followUpSharing","logActivity","draft","setDraft","busy","setBusy","error","setError","fields","storedCustom","custom","setCustom","records","bandFull","ready","quota","allowed","companyId","$id","ownerUid","uid","patch","field","value","current","patchAddress","address","handleSave","result","ok","missing","active","length","join","customDocument","ref","companies","verdict","subject","unreadable","fromCache","cleared","Object","fromEntries","map","_rowId","stored","searchFields","set","updatedAt","message","variant","persist","id","visibleTo","createdByUid","createdAt","type","name","trim","caught","console","anchor","AppBarProps","color","appBarLeft","edge","onClick","sx","mr","path","component","gutterY","spacing","disabled","helperText","severity","size","required","onChange","event","target","autoFocus","placeholder","domain","website","phone","industry","select","options","some","option","labelFor","tags","notes","multiline","minRows","definition","direction","displayName"],"mappings":"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"}
@@ -16,7 +16,7 @@ export declare const COMPANIES_IMPORT_URL = "/api/crm/companies-import";
16
16
  /** The company vocabulary, over the org's company custom fields (AGL-2661). */
17
17
  export declare function companyImportVocabulary(fields: readonly ContactFieldDefinition[]): CsvImportVocabulary<CompanyImportField, CompanyImportRawRow & Record<string, unknown>, CompanyImportSkippedRow>;
18
18
  /** The vocabulary with no custom fields — what a surface without an org resolves to. */
19
- export declare const COMPANY_IMPORT_VOCABULARY: CsvImportVocabulary<"name" | "notes" | "domain" | "website" | "phone" | "industry" | "tags" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry" | "ownerEmail", Partial<Record<"name" | "notes" | "domain" | "website" | "phone" | "industry" | "tags" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry" | "ownerEmail", unknown>> & {
19
+ export declare const COMPANY_IMPORT_VOCABULARY: CsvImportVocabulary<"name" | "phone" | "domain" | "notes" | "tags" | "ownerEmail" | "website" | "industry" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry", Partial<Record<"name" | "phone" | "domain" | "notes" | "tags" | "ownerEmail" | "website" | "industry" | "addressLine1" | "addressLine2" | "addressCity" | "addressState" | "addressPostalCode" | "addressCountry", unknown>> & {
20
20
  custom?: unknown;
21
21
  } & Record<string, unknown>, CompanyImportSkippedRow>;
22
22
  export interface CompanyImportDrawerProps {
@@ -24,6 +24,7 @@ import { useCallback, useMemo, useState } from "react";
24
24
  import { readCrmRecordsQuota } from "../hooks/use-crm-records-quota.js";
25
25
  import { crmVisibleToClause, useCrmScope } from "../hooks/use-crm-scope.js";
26
26
  import { companyDraftFields, EMPTY_COMPANY_DRAFT, suggestCompanyForEmail } from "../model/companies.js";
27
+ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
27
28
  export { suggestCompanyForEmail };
28
29
  /**
29
30
  * How many companies the picker lists.
@@ -97,7 +98,7 @@ export { suggestCompanyForEmail };
97
98
  * `readCrmRecordsQuota` — and never for an edit, because linking an
98
99
  * existing company adds no record.
99
100
  */ export function useCreateCompany(props) {
100
- var _ref;
101
+ var _ref, _ref1;
101
102
  const { hostId, org } = props;
102
103
  const firestore = useFirestore();
103
104
  const { data: user } = useUser();
@@ -108,7 +109,8 @@ export { suggestCompanyForEmail };
108
109
  hostId,
109
110
  org
110
111
  });
111
- const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
112
+ const followUpSharing = useCrmSharingFollowUp(hostId, (_ref = scope == null ? void 0 : scope[1]) != null ? _ref : null);
113
+ const uid = (_ref1 = user == null ? void 0 : user.uid) != null ? _ref1 : '';
112
114
  return useMemo(()=>{
113
115
  if (!scope || !createHostId) return null;
114
116
  return async (name)=>{
@@ -133,6 +135,10 @@ export { suggestCompanyForEmail };
133
135
  createdAt: serverTimestamp(),
134
136
  updatedAt: serverTimestamp()
135
137
  }));
138
+ // A rule may share companies created on this site (AGL-3336).
139
+ followUpSharing('companies', [
140
+ id
141
+ ]);
136
142
  return {
137
143
  id,
138
144
  name: String(result.set['name']),
@@ -145,7 +151,8 @@ export { suggestCompanyForEmail };
145
151
  firestore,
146
152
  createHostId,
147
153
  uid,
148
- org
154
+ org,
155
+ followUpSharing
149
156
  ]);
150
157
  }
151
158
  /** The sentinel id of the "Create …" row the list grows when nothing matches. */ const CREATE_OPTION_ID = '__create__';