@aglyn/plugins-crm 1.0.0-beta.296 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/activity-link-fields.d.ts +34 -0
  3. package/src/lib/components/activity-link-fields.js +141 -0
  4. package/src/lib/components/activity-link-fields.js.map +1 -0
  5. package/src/lib/components/activity-list.d.ts +8 -0
  6. package/src/lib/components/activity-list.js +4 -2
  7. package/src/lib/components/activity-list.js.map +1 -1
  8. package/src/lib/components/activity-queries.d.ts +7 -2
  9. package/src/lib/components/activity-queries.js +7 -3
  10. package/src/lib/components/activity-queries.js.map +1 -1
  11. package/src/lib/components/company-contact-roles-card.d.ts +42 -0
  12. package/src/lib/components/company-contact-roles-card.js +135 -0
  13. package/src/lib/components/company-contact-roles-card.js.map +1 -0
  14. package/src/lib/components/company-detail-page.js +12 -1
  15. package/src/lib/components/company-detail-page.js.map +1 -1
  16. package/src/lib/components/company-edit-drawer.js +9 -6
  17. package/src/lib/components/company-edit-drawer.js.map +1 -1
  18. package/src/lib/components/company-picker.js +4 -2
  19. package/src/lib/components/company-picker.js.map +1 -1
  20. package/src/lib/components/contact-associations-card.js +15 -1
  21. package/src/lib/components/contact-associations-card.js.map +1 -1
  22. package/src/lib/components/contact-detail-page.js +13 -14
  23. package/src/lib/components/contact-detail-page.js.map +1 -1
  24. package/src/lib/components/contact-list-columns.js +5 -4
  25. package/src/lib/components/contact-list-columns.js.map +1 -1
  26. package/src/lib/components/contact-properties-card.js +41 -12
  27. package/src/lib/components/contact-properties-card.js.map +1 -1
  28. package/src/lib/components/contact-roles-editor.d.ts +54 -0
  29. package/src/lib/components/contact-roles-editor.js +270 -0
  30. package/src/lib/components/contact-roles-editor.js.map +1 -0
  31. package/src/lib/components/contacts-section.js +6 -4
  32. package/src/lib/components/contacts-section.js.map +1 -1
  33. package/src/lib/components/crm-console-sections.js +1 -1
  34. package/src/lib/components/crm-console-sections.js.map +1 -1
  35. package/src/lib/components/crm-record-picker.d.ts +19 -0
  36. package/src/lib/components/crm-record-picker.js +40 -11
  37. package/src/lib/components/crm-record-picker.js.map +1 -1
  38. package/src/lib/components/crm-related-to-picker.d.ts +33 -0
  39. package/src/lib/components/crm-related-to-picker.js +135 -0
  40. package/src/lib/components/crm-related-to-picker.js.map +1 -0
  41. package/src/lib/components/crm-site-picker.d.ts +13 -1
  42. package/src/lib/components/crm-site-picker.js +15 -6
  43. package/src/lib/components/crm-site-picker.js.map +1 -1
  44. package/src/lib/components/crm-transfer-buttons.d.ts +5 -0
  45. package/src/lib/components/crm-transfer-buttons.js +9 -6
  46. package/src/lib/components/crm-transfer-buttons.js.map +1 -1
  47. package/src/lib/components/deal-contact-roles-card.js +21 -239
  48. package/src/lib/components/deal-contact-roles-card.js.map +1 -1
  49. package/src/lib/components/deal-detail-page.js +2 -1
  50. package/src/lib/components/deal-detail-page.js.map +1 -1
  51. package/src/lib/components/deal-edit-drawer.js +11 -7
  52. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  53. package/src/lib/components/deals-section.js +5 -4
  54. package/src/lib/components/deals-section.js.map +1 -1
  55. package/src/lib/components/lead-campaigns-card.js +26 -14
  56. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  57. package/src/lib/components/log-activity-dialog.d.ts +27 -9
  58. package/src/lib/components/log-activity-dialog.js +96 -32
  59. package/src/lib/components/log-activity-dialog.js.map +1 -1
  60. package/src/lib/components/new-contact-drawer.d.ts +7 -3
  61. package/src/lib/components/new-contact-drawer.js +27 -15
  62. package/src/lib/components/new-contact-drawer.js.map +1 -1
  63. package/src/lib/components/picklist-values-card.js +7 -4
  64. package/src/lib/components/picklist-values-card.js.map +1 -1
  65. package/src/lib/components/pipeline-stages-dialog.js +5 -1
  66. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  67. package/src/lib/components/record-activity-card.d.ts +11 -2
  68. package/src/lib/components/record-activity-card.js +90 -5
  69. package/src/lib/components/record-activity-card.js.map +1 -1
  70. package/src/lib/components/settings-section.d.ts +4 -2
  71. package/src/lib/components/settings-section.js +7 -5
  72. package/src/lib/components/settings-section.js.map +1 -1
  73. package/src/lib/hooks/crm-activity-client-reads.d.ts +8 -0
  74. package/src/lib/hooks/crm-activity-client-reads.js +50 -0
  75. package/src/lib/hooks/crm-activity-client-reads.js.map +1 -0
  76. package/src/lib/hooks/use-campaign-filing-log.d.ts +2 -1
  77. package/src/lib/hooks/use-campaign-filing-log.js +10 -3
  78. package/src/lib/hooks/use-campaign-filing-log.js.map +1 -1
  79. package/src/lib/hooks/use-crm-org-mount.d.ts +8 -6
  80. package/src/lib/hooks/use-crm-org-mount.js +2 -1
  81. package/src/lib/hooks/use-crm-org-mount.js.map +1 -1
  82. package/src/lib/hooks/use-crm-scope.d.ts +17 -5
  83. package/src/lib/hooks/use-crm-scope.js +12 -5
  84. package/src/lib/hooks/use-crm-scope.js.map +1 -1
  85. package/src/lib/model/contact-holder.d.ts +12 -0
  86. package/src/lib/model/contact-holder.js +20 -1
  87. package/src/lib/model/contact-holder.js.map +1 -1
  88. package/src/lib/model/contact-record.d.ts +5 -0
  89. package/src/lib/model/contact-record.js +7 -1
  90. package/src/lib/model/contact-record.js.map +1 -1
  91. package/src/lib/model/contact-update.d.ts +6 -0
  92. package/src/lib/model/contact-update.js +1 -0
  93. package/src/lib/model/contact-update.js.map +1 -1
  94. package/src/lib/model/crm-activity-link.d.ts +117 -0
  95. package/src/lib/model/crm-activity-link.js +113 -0
  96. package/src/lib/model/crm-activity-link.js.map +1 -0
  97. package/src/lib/model/crm-api.d.ts +4 -1
  98. package/src/lib/model/crm-api.js +9 -5
  99. package/src/lib/model/crm-api.js.map +1 -1
  100. package/src/lib/model/deal-board-model.js +5 -3
  101. package/src/lib/model/deal-board-model.js.map +1 -1
  102. package/src/lib/model/deal-contact-roles.d.ts +15 -1
  103. package/src/lib/model/deal-contact-roles.js +17 -3
  104. package/src/lib/model/deal-contact-roles.js.map +1 -1
  105. package/src/lib/model/settle-stage-names.d.ts +25 -0
  106. package/src/lib/model/settle-stage-names.js +29 -0
  107. package/src/lib/model/settle-stage-names.js.map +1 -0
  108. package/src/lib/native-nav.js +1 -1
  109. package/src/lib/native-nav.js.map +1 -1
  110. package/src/lib/server/add-contact-by-hand.d.ts +84 -0
  111. package/src/lib/server/add-contact-by-hand.js +212 -0
  112. package/src/lib/server/add-contact-by-hand.js.map +1 -0
  113. package/src/lib/server/api-v1/contacts.js +61 -23
  114. package/src/lib/server/api-v1/contacts.js.map +1 -1
  115. package/src/lib/server/api-v1/crm-activities.js +13 -3
  116. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  117. package/src/lib/server/api-v1/crm-pipelines.d.ts +1 -1
  118. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  119. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  120. package/src/lib/server/api-v1/crm-shared.d.ts +16 -9
  121. package/src/lib/server/api-v1/crm-shared.js +23 -11
  122. package/src/lib/server/api-v1/crm-shared.js.map +1 -1
  123. package/src/lib/server/api-v1/openapi.js +3 -3
  124. package/src/lib/server/api-v1/openapi.js.map +1 -1
  125. package/src/lib/server/automation-steps.js +11 -9
  126. package/src/lib/server/automation-steps.js.map +1 -1
  127. package/src/lib/server/campaign-filing-activity.js +7 -2
  128. package/src/lib/server/campaign-filing-activity.js.map +1 -1
  129. package/src/lib/server/contact-update.d.ts +4 -0
  130. package/src/lib/server/contact-update.js +71 -10
  131. package/src/lib/server/contact-update.js.map +1 -1
  132. package/src/lib/server/convert-host-lead.js +13 -2
  133. package/src/lib/server/convert-host-lead.js.map +1 -1
  134. package/src/lib/server/convert-open-lead.js +8 -1
  135. package/src/lib/server/convert-open-lead.js.map +1 -1
  136. package/src/lib/server/crm-activity-company.d.ts +25 -0
  137. package/src/lib/server/crm-activity-company.js +34 -0
  138. package/src/lib/server/crm-activity-company.js.map +1 -0
  139. package/src/lib/server/crm-inbound-email.js +21 -15
  140. package/src/lib/server/crm-inbound-email.js.map +1 -1
  141. package/src/lib/server/crm-sharing.js +9 -2
  142. package/src/lib/server/crm-sharing.js.map +1 -1
  143. package/src/lib/server/email-send.js +10 -8
  144. package/src/lib/server/email-send.js.map +1 -1
  145. package/src/lib/server/merge-contacts.js +6 -2
  146. package/src/lib/server/merge-contacts.js.map +1 -1
  147. package/src/lib/server/org-caller.d.ts +8 -0
  148. package/src/lib/server/org-caller.js +10 -0
  149. package/src/lib/server/org-caller.js.map +1 -1
  150. package/src/lib/server/record-timeline.d.ts +3 -1
  151. package/src/lib/server/record-timeline.js +11 -3
  152. package/src/lib/server/record-timeline.js.map +1 -1
  153. package/src/lib/server/task-reminders-route.d.ts +7 -5
  154. package/src/lib/server/task-reminders-route.js +82 -15
  155. package/src/lib/server/task-reminders-route.js.map +1 -1
  156. package/src/lib/server.js +152 -43
  157. package/src/lib/server.js.map +1 -1
  158. package/src/lib/transfer/contacts.js +83 -48
  159. package/src/lib/transfer/contacts.js.map +1 -1
  160. package/src/lib/transfer/fields.js +1 -2
  161. package/src/lib/transfer/fields.js.map +1 -1
@@ -15,23 +15,18 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
- import { CRM_COLLECTIONS, dealContactRolesOf, dealContactRolesWithout, dealContactRolesWithPrimary, pluginDocsHelp } from "@aglyn/aglyn";
20
- import { mdiAccountPlusOutline, mdiAccountRemoveOutline } from "@aglyn/shared-data-mdi";
21
- import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
22
- import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
18
+ import { jsx as _jsx } from "react/jsx-runtime";
19
+ import { CRM_COLLECTIONS, dealContactRolesOf, pluginDocsHelp } from "@aglyn/aglyn";
23
20
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
21
  import { useFirestore, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
25
- import { Box, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, IconButton, Stack, Tooltip, Typography } from "@mui/material";
26
22
  import { deleteField, doc, updateDoc } from "firebase/firestore";
27
23
  import { useCallback, useMemo, useState } from "react";
28
24
  import { useCrmPicklist } from "../hooks/use-crm-picklist.js";
29
25
  import { useCrmRecordNames } from "../hooks/use-crm-record-names.js";
30
26
  import { useCrmScope } from "../hooks/use-crm-scope.js";
31
27
  import { CRM_DEAL_CONTACT_ROLE_LIST_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
32
- import { dealContactRoleAddProblem, dealContactRolesAdded, dealContactRolesSave, dealContactRolesWithRole } from "../model/deal-contact-roles.js";
33
- import CrmRecordPicker from "./crm-record-picker.js";
34
- import { CrmPicklistSelect } from "./picklist-select.js";
28
+ import { dealContactRolesSave } from "../model/deal-contact-roles.js";
29
+ import { ContactRolesEditor } from "./contact-roles-editor.js";
35
30
  /**
36
31
  * Salesforce's Opportunity Contact Roles on a deal's page (AGL-3521): the
37
32
  * people on the deal, the part each plays, and which one is Primary.
@@ -130,239 +125,26 @@ import { CrmPicklistSelect } from "./picklist-select.js";
130
125
  firestore,
131
126
  enqueueSnackbar
132
127
  ]);
133
- const addButton = (variant)=>/*#__PURE__*/ _jsx(Button, {
134
- size: "small",
135
- variant: variant,
136
- startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
137
- path: mdiAccountPlusOutline.path,
138
- size: 0.8
139
- }),
140
- disabled: busy || !scope.orgId,
141
- onClick: ()=>setAdding(true),
142
- children: 'Add contact'
143
- });
144
- return /*#__PURE__*/ _jsxs(_Fragment, {
145
- children: [
146
- /*#__PURE__*/ _jsx(CardDisplay, {
147
- header: 'Contact roles',
148
- help: pluginDocsHelp('deals', {
149
- anchor: '#contact-roles'
150
- }),
151
- HeaderProps: {
152
- action: addButton('text')
153
- },
154
- contentGutterX: true,
155
- contentGutterY: true,
156
- children: roles.length === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
157
- compact: true,
158
- label: 'No contacts on this deal',
159
- description: 'Name the people on the deal and the part each plays — one of them Primary.',
160
- action: addButton('contained')
161
- }) : /*#__PURE__*/ _jsx(Stack, {
162
- spacing: 1.5,
163
- children: roles.map((row)=>{
164
- var _row_role;
165
- const name = nameOf('contact', row.contactId) || row.contactId;
166
- return /*#__PURE__*/ _jsxs(Stack, {
167
- direction: {
168
- xs: 'column',
169
- sm: 'row'
170
- },
171
- spacing: 1,
172
- sx: {
173
- alignItems: {
174
- sm: 'center'
175
- }
176
- },
177
- children: [
178
- /*#__PURE__*/ _jsxs(Stack, {
179
- direction: "row",
180
- spacing: 1,
181
- sx: {
182
- flex: 1,
183
- minWidth: 0,
184
- alignItems: 'center'
185
- },
186
- children: [
187
- /*#__PURE__*/ _jsx(Typography, {
188
- variant: "body2",
189
- noWrap: true,
190
- children: /*#__PURE__*/ _jsx(AppLink, {
191
- href: routes.contact(row.contactId),
192
- children: name
193
- })
194
- }),
195
- row.primary ? /*#__PURE__*/ _jsx(Chip, {
196
- size: "small",
197
- color: "primary",
198
- label: "Primary"
199
- }) : null
200
- ]
201
- }),
202
- /*#__PURE__*/ _jsx(Box, {
203
- sx: {
204
- width: {
205
- xs: '100%',
206
- sm: 220
207
- }
208
- },
209
- children: /*#__PURE__*/ _jsx(CrmPicklistSelect, {
210
- picklistId: "opportunityContactRole",
211
- picklist: roleList.picklist,
212
- label: "Role",
213
- value: (_row_role = row.role) != null ? _row_role : '',
214
- stored: row.role,
215
- disabled: busy,
216
- onChange: (role)=>void save(dealContactRolesWithRole(roles, row.contactId, role), 'Role saved')
217
- })
218
- }),
219
- /*#__PURE__*/ _jsxs(Stack, {
220
- direction: "row",
221
- spacing: 0.5,
222
- sx: {
223
- alignItems: 'center'
224
- },
225
- children: [
226
- row.primary ? null : /*#__PURE__*/ _jsx(Button, {
227
- size: "small",
228
- disabled: busy,
229
- onClick: ()=>void save(dealContactRolesWithPrimary(roles, row.contactId), 'Primary contact set'),
230
- children: 'Make primary'
231
- }),
232
- /*#__PURE__*/ _jsx(Tooltip, {
233
- title: `Remove ${name} from this deal`,
234
- children: /*#__PURE__*/ _jsx("span", {
235
- children: /*#__PURE__*/ _jsx(IconButton, {
236
- size: "small",
237
- "aria-label": `Remove ${name} from this deal`,
238
- disabled: busy,
239
- onClick: ()=>void save(dealContactRolesWithout(roles, row.contactId), 'Contact removed'),
240
- children: /*#__PURE__*/ _jsx(MdiIcon, {
241
- path: mdiAccountRemoveOutline.path,
242
- size: 0.8
243
- })
244
- })
245
- })
246
- })
247
- ]
248
- })
249
- ]
250
- }, row.contactId);
251
- })
252
- })
253
- }),
254
- /*#__PURE__*/ _jsx(AddContactRoleDialog, {
255
- open: adding,
256
- busy: busy,
257
- roles: roles,
258
- hostId: hostId,
259
- org: org,
260
- picklist: roleList.picklist,
261
- onClose: ()=>setAdding(false),
262
- onAdd: async (added)=>{
263
- const ok = await save(dealContactRolesAdded(roles, added), 'Contact added');
264
- if (ok) setAdding(false);
265
- return ok;
266
- }
267
- })
268
- ]
128
+ return /*#__PURE__*/ _jsx(ContactRolesEditor, {
129
+ noun: "deal",
130
+ roles: roles,
131
+ busy: busy,
132
+ adding: adding,
133
+ onAddingChange: setAdding,
134
+ ready: Boolean(scope.orgId),
135
+ hostId: hostId,
136
+ org: org,
137
+ routes: routes,
138
+ picklist: roleList.picklist,
139
+ nameOf: nameOf,
140
+ help: pluginDocsHelp('deals', {
141
+ anchor: '#contact-roles'
142
+ }),
143
+ emptyDescription: "Name the people on the deal and the part each plays — one of them Primary.",
144
+ save: save
269
145
  });
270
146
  }
271
147
  DealContactRolesCard.displayName = 'DealContactRolesCard';
272
- /** Pick a contact, the part they play, and whether they are the Primary. */ function AddContactRoleDialog(props) {
273
- var _ref;
274
- var _scope_consentGroup;
275
- const { open, busy, roles, hostId, org, picklist, onClose, onAdd } = props;
276
- const scope = useCrmScope({
277
- hostId,
278
- org
279
- });
280
- const [contactId, setContactId] = useState(null);
281
- const [role, setRole] = useState('');
282
- // The first contact on a deal is always its Primary; later ones when ticked.
283
- const [primary, setPrimary] = useState(false);
284
- const problem = contactId ? dealContactRoleAddProblem(roles, contactId) : null;
285
- const reset = ()=>{
286
- setContactId(null);
287
- setRole('');
288
- setPrimary(false);
289
- };
290
- const close = ()=>{
291
- reset();
292
- onClose();
293
- };
294
- return /*#__PURE__*/ _jsxs(Dialog, {
295
- open: open,
296
- onClose: busy ? undefined : close,
297
- fullWidth: true,
298
- maxWidth: "xs",
299
- children: [
300
- /*#__PURE__*/ _jsx(DialogTitle, {
301
- children: 'Add a contact to this deal'
302
- }),
303
- /*#__PURE__*/ _jsx(DialogContent, {
304
- children: /*#__PURE__*/ _jsxs(Stack, {
305
- spacing: 2,
306
- sx: {
307
- pt: 1
308
- },
309
- children: [
310
- /*#__PURE__*/ _jsx(CrmRecordPicker, {
311
- kind: "contact",
312
- scope: scope.scope,
313
- readTokens: scope.visibleTo,
314
- groupId: (_ref = (_scope_consentGroup = scope.consentGroup) == null ? void 0 : _scope_consentGroup.groupId) != null ? _ref : null,
315
- org: org != null ? org : null,
316
- value: contactId,
317
- onChange: setContactId,
318
- disabled: busy,
319
- helperText: problem != null ? problem : undefined
320
- }),
321
- /*#__PURE__*/ _jsx(CrmPicklistSelect, {
322
- picklistId: "opportunityContactRole",
323
- picklist: picklist,
324
- label: "Role",
325
- value: role,
326
- onChange: setRole,
327
- disabled: busy
328
- }),
329
- /*#__PURE__*/ _jsx(FormControlLabel, {
330
- control: /*#__PURE__*/ _jsx(Checkbox, {
331
- checked: primary || roles.length === 0,
332
- disabled: busy || roles.length === 0,
333
- onChange: (event)=>setPrimary(event.target.checked)
334
- }),
335
- label: "Primary contact"
336
- })
337
- ]
338
- })
339
- }),
340
- /*#__PURE__*/ _jsxs(DialogActions, {
341
- children: [
342
- /*#__PURE__*/ _jsx(Button, {
343
- onClick: close,
344
- disabled: busy,
345
- children: 'Cancel'
346
- }),
347
- /*#__PURE__*/ _jsx(Button, {
348
- variant: "contained",
349
- disabled: busy || !contactId || Boolean(problem),
350
- onClick: async ()=>{
351
- if (!contactId) return;
352
- const added = await onAdd({
353
- contactId,
354
- role: role || undefined,
355
- primary: primary || roles.length === 0
356
- });
357
- if (added) reset();
358
- },
359
- children: 'Add'
360
- })
361
- ]
362
- })
363
- ]
364
- });
365
- }
366
148
  export default DealContactRolesCard;
367
149
 
368
150
  //# sourceMappingURL=deal-contact-roles-card.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-contact-roles-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport {\n CRM_COLLECTIONS,\n type CrmDealContactRole,\n dealContactRolesOf,\n dealContactRolesWithout,\n dealContactRolesWithPrimary,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiAccountPlusOutline, mdiAccountRemoveOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n IconButton,\n Stack,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, updateDoc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmOrgDoc, useCrmScope } from '../hooks/use-crm-scope'\nimport { CRM_DEAL_CONTACT_ROLE_LIST_FIELDS, crmClientListFields } from '../model/crm-list-query'\nimport type { CrmRoutes } from '../model/crm-routes'\nimport type { DealDoc } from '../model/deal-board-model'\nimport {\n dealContactRoleAddProblem,\n dealContactRolesAdded,\n dealContactRolesSave,\n dealContactRolesWithRole,\n} from '../model/deal-contact-roles'\nimport CrmRecordPicker from './crm-record-picker'\nimport { CrmPicklistSelect } from './picklist-select'\n\nexport interface DealContactRolesCardProps {\n deal: DealDoc\n /** The site the page is read under, or `null` at the organization level. */\n hostId: string | null\n org: CrmOrgDoc\n routes: CrmRoutes\n /** The listener's verdict on the deal, for the stale-seed guard. */\n fromCache: boolean\n unreadable: boolean\n}\n\n/**\n * Salesforce's Opportunity Contact Roles on a deal's page (AGL-3521): the\n * people on the deal, the part each plays, and which one is Primary.\n *\n * Every edit is one client-direct write of the whole list beside the\n * `contactId` its Primary names — the field every reader that came before\n * roles still reads — and the list fields a contact's page finds the deal\n * by. A role is one of the org's `opportunityContactRole` values, kept on\n * the Deals tab of Fields; a contact keeps the role it holds even once that\n * value is deactivated. Adding opens a dialog rather than a row of fields\n * above the list.\n */\nexport function DealContactRolesCard(props: DealContactRolesCardProps) {\n const { deal, hostId, org, routes, fromCache, unreadable } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const groupId = scope.consentGroup?.groupId ?? null\n const roles = useMemo(() => dealContactRolesOf(deal), [deal])\n const roleList = useCrmPicklist('opportunityContactRole', scope.orgId)\n const nameOf = useCrmRecordNames({\n orgId: scope.orgId,\n groupId,\n org: (org ?? null) as Record<string, unknown> | null,\n records: roles.map((row) => ({ kind: 'contact' as const, id: row.contactId })),\n })\n const [busy, setBusy] = useState(false)\n const [adding, setAdding] = useState(false)\n\n /** The one write: the list, its Primary as `contactId`, and the list fields. */\n const save = useCallback(\n async (next: CrmDealContactRole[], done: string) => {\n if (!scope.orgId) return false\n const verdict = dealContactRolesSave(deal, next, roleList.picklist)\n if (verdict.ok === false) {\n enqueueSnackbar(verdict.error, { variant: 'warning', persist: false })\n return false\n }\n const primaryName = verdict.contactId ? nameOf('contact', verdict.contactId) : undefined\n const patch: Record<string, unknown> = {\n contactRoles: verdict.contactRoles,\n contactId: verdict.contactId ?? deleteField(),\n // The name the drawer copies beside `contactId`, moved with the Primary.\n ...(verdict.primaryChanged ? { contactName: primaryName || deleteField() } : {}),\n }\n setBusy(true)\n try {\n const written = await writeGuardedBySeed(\n { subject: 'deal', unreadable, fromCache },\n async () => {\n await updateDoc(doc(firestore, 'orgs', scope.orgId as string, CRM_COLLECTIONS.deals, deal.$id), {\n ...patch,\n ...crmClientListFields(\n 'deals',\n deal as unknown as Record<string, unknown>,\n patch,\n CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n ),\n updatedAt: new Date(),\n })\n },\n )\n if (!written.ok) {\n enqueueSnackbar(written.message, { variant: 'warning', persist: false })\n return false\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n return false\n } finally {\n setBusy(false)\n }\n },\n [scope.orgId, deal, roleList.picklist, nameOf, unreadable, fromCache, firestore, enqueueSnackbar],\n )\n\n const addButton = (variant: 'text' | 'contained') => (\n <Button\n size=\"small\"\n variant={variant}\n startIcon={<MdiIcon path={mdiAccountPlusOutline.path} size={0.8} />}\n disabled={busy || !scope.orgId}\n onClick={() => setAdding(true)}\n >\n {'Add contact'}\n </Button>\n )\n\n return (\n <>\n <CardDisplay\n header={'Contact roles'}\n help={pluginDocsHelp('deals', { anchor: '#contact-roles' })}\n HeaderProps={{ action: addButton('text') }}\n contentGutterX\n contentGutterY\n >\n {roles.length === 0 ? (\n <EmptyStateComponent\n compact\n label={'No contacts on this deal'}\n description={'Name the people on the deal and the part each plays — one of them Primary.'}\n action={addButton('contained')}\n />\n ) : (\n <Stack spacing={1.5}>\n {roles.map((row) => {\n const name = nameOf('contact', row.contactId) || row.contactId\n return (\n <Stack\n key={row.contactId}\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ flex: 1, minWidth: 0, alignItems: 'center' }}>\n <Typography variant=\"body2\" noWrap>\n <AppLink href={routes.contact(row.contactId)}>{name}</AppLink>\n </Typography>\n {row.primary ? <Chip size=\"small\" color=\"primary\" label=\"Primary\" /> : null}\n </Stack>\n <Box sx={{ width: { xs: '100%', sm: 220 } }}>\n <CrmPicklistSelect\n picklistId=\"opportunityContactRole\"\n picklist={roleList.picklist}\n label=\"Role\"\n value={row.role ?? ''}\n stored={row.role}\n disabled={busy}\n onChange={(role) =>\n void save(dealContactRolesWithRole(roles, row.contactId, role), 'Role saved')\n }\n />\n </Box>\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n {row.primary ? null : (\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() =>\n void save(dealContactRolesWithPrimary(roles, row.contactId), 'Primary contact set')\n }\n >\n {'Make primary'}\n </Button>\n )}\n <Tooltip title={`Remove ${name} from this deal`}>\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Remove ${name} from this deal`}\n disabled={busy}\n onClick={() =>\n void save(dealContactRolesWithout(roles, row.contactId), 'Contact removed')\n }\n >\n <MdiIcon path={mdiAccountRemoveOutline.path} size={0.8} />\n </IconButton>\n </span>\n </Tooltip>\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n )}\n </CardDisplay>\n <AddContactRoleDialog\n open={adding}\n busy={busy}\n roles={roles}\n hostId={hostId}\n org={org}\n picklist={roleList.picklist}\n onClose={() => setAdding(false)}\n onAdd={async (added) => {\n const ok = await save(dealContactRolesAdded(roles, added), 'Contact added')\n if (ok) setAdding(false)\n return ok\n }}\n />\n </>\n )\n}\nDealContactRolesCard.displayName = 'DealContactRolesCard'\n\ninterface AddContactRoleDialogProps {\n open: boolean\n busy: boolean\n roles: readonly CrmDealContactRole[]\n hostId: string | null\n org: CrmOrgDoc\n picklist: ReturnType<typeof useCrmPicklist>['picklist']\n onClose: () => void\n /** Answers whether the contact was added — the dialog keeps its picks when not. */\n onAdd: (added: { contactId: string; role?: string; primary: boolean }) => Promise<boolean>\n}\n\n/** Pick a contact, the part they play, and whether they are the Primary. */\nfunction AddContactRoleDialog(props: AddContactRoleDialogProps) {\n const { open, busy, roles, hostId, org, picklist, onClose, onAdd } = props\n const scope = useCrmScope({ hostId, org })\n const [contactId, setContactId] = useState<string | null>(null)\n const [role, setRole] = useState('')\n // The first contact on a deal is always its Primary; later ones when ticked.\n const [primary, setPrimary] = useState(false)\n const problem = contactId ? dealContactRoleAddProblem(roles, contactId) : null\n\n const reset = () => {\n setContactId(null)\n setRole('')\n setPrimary(false)\n }\n const close = () => {\n reset()\n onClose()\n }\n\n return (\n <Dialog open={open} onClose={busy ? undefined : close} fullWidth maxWidth=\"xs\">\n <DialogTitle>{'Add a contact to this deal'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <CrmRecordPicker\n kind=\"contact\"\n scope={scope.scope}\n readTokens={scope.visibleTo}\n groupId={scope.consentGroup?.groupId ?? null}\n org={(org ?? null) as Record<string, unknown> | null}\n value={contactId}\n onChange={setContactId}\n disabled={busy}\n helperText={problem ?? undefined}\n />\n <CrmPicklistSelect\n picklistId=\"opportunityContactRole\"\n picklist={picklist}\n label=\"Role\"\n value={role}\n onChange={setRole}\n disabled={busy}\n />\n <FormControlLabel\n control={\n <Checkbox\n checked={primary || roles.length === 0}\n disabled={busy || roles.length === 0}\n onChange={(event) => setPrimary(event.target.checked)}\n />\n }\n label=\"Primary contact\"\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={close} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={busy || !contactId || Boolean(problem)}\n onClick={async () => {\n if (!contactId) return\n const added = await onAdd({\n contactId,\n role: role || undefined,\n primary: primary || roles.length === 0,\n })\n if (added) reset()\n }}\n >\n {'Add'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default DealContactRolesCard\n"],"names":["CRM_COLLECTIONS","dealContactRolesOf","dealContactRolesWithout","dealContactRolesWithPrimary","pluginDocsHelp","mdiAccountPlusOutline","mdiAccountRemoveOutline","AppLink","CardDisplay","MdiIcon","EmptyStateComponent","useSnackbar","useFirestore","writeGuardedBySeed","Box","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","IconButton","Stack","Tooltip","Typography","deleteField","doc","updateDoc","useCallback","useMemo","useState","useCrmPicklist","useCrmRecordNames","useCrmScope","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS","crmClientListFields","dealContactRoleAddProblem","dealContactRolesAdded","dealContactRolesSave","dealContactRolesWithRole","CrmRecordPicker","CrmPicklistSelect","DealContactRolesCard","props","scope","deal","hostId","org","routes","fromCache","unreadable","firestore","enqueueSnackbar","groupId","consentGroup","roles","roleList","orgId","nameOf","records","map","row","kind","id","contactId","busy","setBusy","adding","setAdding","save","next","done","verdict","picklist","ok","error","variant","persist","primaryName","undefined","patch","contactRoles","primaryChanged","contactName","written","subject","deals","$id","updatedAt","Date","message","console","allowDuplicate","addButton","size","startIcon","path","disabled","onClick","header","help","anchor","HeaderProps","action","contentGutterX","contentGutterY","length","compact","label","description","spacing","name","direction","xs","sm","sx","alignItems","flex","minWidth","noWrap","href","contact","primary","color","width","picklistId","value","role","stored","onChange","title","span","aria-label","AddContactRoleDialog","open","onClose","onAdd","added","displayName","setContactId","setRole","setPrimary","problem","reset","close","fullWidth","maxWidth","pt","readTokens","visibleTo","helperText","control","checked","event","target","Boolean"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,SACEA,eAAe,EAEfC,kBAAkB,EAClBC,uBAAuB,EACvBC,2BAA2B,EAC3BC,cAAc,QACT,eAAc;AACrB,SAASC,qBAAqB,EAAEC,uBAAuB,QAAQ,yBAAwB;AACvF,SAASC,OAAO,EAAEC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AACpE,OAAOC,yBAAyB,wDAAuD;AACvF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SAASC,YAAY,EAAEC,kBAAkB,QAAQ,iCAAgC;AACjF,SACEC,GAAG,EACHC,MAAM,EACNC,QAAQ,EACRC,IAAI,EACJC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,gBAAgB,EAChBC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,GAAG,EAAEC,SAAS,QAAQ,qBAAoB;AAChE,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACtD,SAASC,cAAc,QAAQ,+BAA2B;AAC1D,SAASC,iBAAiB,QAAQ,mCAA+B;AACjE,SAAyBC,WAAW,QAAQ,4BAAwB;AACpE,SAASC,iCAAiC,EAAEC,mBAAmB,QAAQ,6BAAyB;AAGhG,SACEC,yBAAyB,EACzBC,qBAAqB,EACrBC,oBAAoB,EACpBC,wBAAwB,QACnB,iCAA6B;AACpC,OAAOC,qBAAqB,yBAAqB;AACjD,SAASC,iBAAiB,QAAQ,uBAAmB;AAarD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,qBAAqBC,KAAgC;;QAKnDC;IAJhB,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAEC,GAAG,EAAEC,MAAM,EAAEC,SAAS,EAAEC,UAAU,EAAE,GAAGP;IAC7D,MAAMQ,YAAYzC;IAClB,MAAM,EAAE0C,eAAe,EAAE,GAAG3C;IAC5B,MAAMmC,QAAQX,YAAY;QAAEa;QAAQC;IAAI;IACxC,MAAMM,mBAAUT,sBAAAA,MAAMU,YAAY,qBAAlBV,oBAAoBS,OAAO,mBAAI;IAC/C,MAAME,QAAQ1B,QAAQ,IAAM9B,mBAAmB8C,OAAO;QAACA;KAAK;IAC5D,MAAMW,WAAWzB,eAAe,0BAA0Ba,MAAMa,KAAK;IACrE,MAAMC,SAAS1B,kBAAkB;QAC/ByB,OAAOb,MAAMa,KAAK;QAClBJ;QACAN,GAAG,EAAGA,cAAAA,MAAO;QACbY,SAASJ,MAAMK,GAAG,CAAC,CAACC,MAAS,CAAA;gBAAEC,MAAM;gBAAoBC,IAAIF,IAAIG,SAAS;YAAC,CAAA;IAC7E;IACA,MAAM,CAACC,MAAMC,QAAQ,GAAGpC,SAAS;IACjC,MAAM,CAACqC,QAAQC,UAAU,GAAGtC,SAAS;IAErC,8EAA8E,GAC9E,MAAMuC,OAAOzC,YACX,OAAO0C,MAA4BC;YAUpBC;QATb,IAAI,CAAC5B,MAAMa,KAAK,EAAE,OAAO;QACzB,MAAMe,UAAUlC,qBAAqBO,MAAMyB,MAAMd,SAASiB,QAAQ;QAClE,IAAID,QAAQE,EAAE,KAAK,OAAO;YACxBtB,gBAAgBoB,QAAQG,KAAK,EAAE;gBAAEC,SAAS;gBAAWC,SAAS;YAAM;YACpE,OAAO;QACT;QACA,MAAMC,cAAcN,QAAQR,SAAS,GAAGN,OAAO,WAAWc,QAAQR,SAAS,IAAIe;QAC/E,MAAMC,QAAiC;YACrCC,cAAcT,QAAQS,YAAY;YAClCjB,SAAS,GAAEQ,qBAAAA,QAAQR,SAAS,YAAjBQ,qBAAqB/C;WAE5B+C,QAAQU,cAAc,GAAG;YAAEC,aAAaL,eAAerD;QAAc,IAAI,CAAC;QAEhFyC,QAAQ;QACR,IAAI;YACF,MAAMkB,UAAU,MAAMzE,mBACpB;gBAAE0E,SAAS;gBAAQnC;gBAAYD;YAAU,GACzC;gBACE,MAAMtB,UAAUD,IAAIyB,WAAW,QAAQP,MAAMa,KAAK,EAAY3D,gBAAgBwF,KAAK,EAAEzC,KAAK0C,GAAG,GAAG,aAC3FP,OACA7C,oBACD,SACAU,MACAmC,OACA9C;oBAEFsD,WAAW,IAAIC;;YAEnB;YAEF,IAAI,CAACL,QAAQV,EAAE,EAAE;gBACftB,gBAAgBgC,QAAQM,OAAO,EAAE;oBAAEd,SAAS;oBAAWC,SAAS;gBAAM;gBACtE,OAAO;YACT;YACAzB,gBAAgBmB,MAAM;gBAAEK,SAAS;gBAAWC,SAAS;YAAM;YAC3D,OAAO;QACT,EAAE,OAAOF,OAAO;YACdgB,QAAQhB,KAAK,CAACA;YACdvB,gBAAgB,yBAAyB;gBAAEwB,SAAS;gBAASgB,gBAAgB;YAAK;YAClF,OAAO;QACT,SAAU;YACR1B,QAAQ;QACV;IACF,GACA;QAACtB,MAAMa,KAAK;QAAEZ;QAAMW,SAASiB,QAAQ;QAAEf;QAAQR;QAAYD;QAAWE;QAAWC;KAAgB;IAGnG,MAAMyC,YAAY,CAACjB,wBACjB,KAAC/D;YACCiF,MAAK;YACLlB,SAASA;YACTmB,yBAAW,KAACxF;gBAAQyF,MAAM7F,sBAAsB6F,IAAI;gBAAEF,MAAM;;YAC5DG,UAAUhC,QAAQ,CAACrB,MAAMa,KAAK;YAC9ByC,SAAS,IAAM9B,UAAU;sBAExB;;IAIL,qBACE;;0BACE,KAAC9D;gBACC6F,QAAQ;gBACRC,MAAMlG,eAAe,SAAS;oBAAEmG,QAAQ;gBAAiB;gBACzDC,aAAa;oBAAEC,QAAQV,UAAU;gBAAQ;gBACzCW,cAAc;gBACdC,cAAc;0BAEblD,MAAMmD,MAAM,KAAK,kBAChB,KAAClG;oBACCmG,OAAO;oBACPC,OAAO;oBACPC,aAAa;oBACbN,QAAQV,UAAU;mCAGpB,KAACvE;oBAAMwF,SAAS;8BACbvD,MAAMK,GAAG,CAAC,CAACC;4BAoBKA;wBAnBf,MAAMkD,OAAOrD,OAAO,WAAWG,IAAIG,SAAS,KAAKH,IAAIG,SAAS;wBAC9D,qBACE,MAAC1C;4BAEC0F,WAAW;gCAAEC,IAAI;gCAAUC,IAAI;4BAAM;4BACrCJ,SAAS;4BACTK,IAAI;gCAAEC,YAAY;oCAAEF,IAAI;gCAAS;4BAAE;;8CAEnC,MAAC5F;oCAAM0F,WAAU;oCAAMF,SAAS;oCAAGK,IAAI;wCAAEE,MAAM;wCAAGC,UAAU;wCAAGF,YAAY;oCAAS;;sDAClF,KAAC5F;4CAAWoD,SAAQ;4CAAQ2C,MAAM;sDAChC,cAAA,KAAClH;gDAAQmH,MAAMxE,OAAOyE,OAAO,CAAC5D,IAAIG,SAAS;0DAAI+C;;;wCAEhDlD,IAAI6D,OAAO,iBAAG,KAAC3G;4CAAK+E,MAAK;4CAAQ6B,OAAM;4CAAUf,OAAM;6CAAe;;;8CAEzE,KAAChG;oCAAIuG,IAAI;wCAAES,OAAO;4CAAEX,IAAI;4CAAQC,IAAI;wCAAI;oCAAE;8CACxC,cAAA,KAACzE;wCACCoF,YAAW;wCACXpD,UAAUjB,SAASiB,QAAQ;wCAC3BmC,OAAM;wCACNkB,KAAK,GAAEjE,YAAAA,IAAIkE,IAAI,YAARlE,YAAY;wCACnBmE,QAAQnE,IAAIkE,IAAI;wCAChB9B,UAAUhC;wCACVgE,UAAU,CAACF,OACT,KAAK1D,KAAK9B,yBAAyBgB,OAAOM,IAAIG,SAAS,EAAE+D,OAAO;;;8CAItE,MAACzG;oCAAM0F,WAAU;oCAAMF,SAAS;oCAAKK,IAAI;wCAAEC,YAAY;oCAAS;;wCAC7DvD,IAAI6D,OAAO,GAAG,qBACb,KAAC7G;4CACCiF,MAAK;4CACLG,UAAUhC;4CACViC,SAAS,IACP,KAAK7B,KAAKpE,4BAA4BsD,OAAOM,IAAIG,SAAS,GAAG;sDAG9D;;sDAGL,KAACzC;4CAAQ2G,OAAO,CAAC,OAAO,EAAEnB,KAAK,eAAe,CAAC;sDAC7C,cAAA,KAACoB;0DACC,cAAA,KAAC9G;oDACCyE,MAAK;oDACLsC,cAAY,CAAC,OAAO,EAAErB,KAAK,eAAe,CAAC;oDAC3Cd,UAAUhC;oDACViC,SAAS,IACP,KAAK7B,KAAKrE,wBAAwBuD,OAAOM,IAAIG,SAAS,GAAG;8DAG3D,cAAA,KAACzD;wDAAQyF,MAAM5F,wBAAwB4F,IAAI;wDAAEF,MAAM;;;;;;;;2BA9CtDjC,IAAIG,SAAS;oBAqDxB;;;0BAIN,KAACqE;gBACCC,MAAMnE;gBACNF,MAAMA;gBACNV,OAAOA;gBACPT,QAAQA;gBACRC,KAAKA;gBACL0B,UAAUjB,SAASiB,QAAQ;gBAC3B8D,SAAS,IAAMnE,UAAU;gBACzBoE,OAAO,OAAOC;oBACZ,MAAM/D,KAAK,MAAML,KAAKhC,sBAAsBkB,OAAOkF,QAAQ;oBAC3D,IAAI/D,IAAIN,UAAU;oBAClB,OAAOM;gBACT;;;;AAIR;AACAhC,qBAAqBgG,WAAW,GAAG;AAcnC,0EAA0E,GAC1E,SAASL,qBAAqB1F,KAAgC;;QA4BzCC;IA3BnB,MAAM,EAAE0F,IAAI,EAAErE,IAAI,EAAEV,KAAK,EAAET,MAAM,EAAEC,GAAG,EAAE0B,QAAQ,EAAE8D,OAAO,EAAEC,KAAK,EAAE,GAAG7F;IACrE,MAAMC,QAAQX,YAAY;QAAEa;QAAQC;IAAI;IACxC,MAAM,CAACiB,WAAW2E,aAAa,GAAG7G,SAAwB;IAC1D,MAAM,CAACiG,MAAMa,QAAQ,GAAG9G,SAAS;IACjC,6EAA6E;IAC7E,MAAM,CAAC4F,SAASmB,WAAW,GAAG/G,SAAS;IACvC,MAAMgH,UAAU9E,YAAY5B,0BAA0BmB,OAAOS,aAAa;IAE1E,MAAM+E,QAAQ;QACZJ,aAAa;QACbC,QAAQ;QACRC,WAAW;IACb;IACA,MAAMG,QAAQ;QACZD;QACAR;IACF;IAEA,qBACE,MAACvH;QAAOsH,MAAMA;QAAMC,SAAStE,OAAOc,YAAYiE;QAAOC,SAAS;QAACC,UAAS;;0BACxE,KAAC/H;0BAAa;;0BACd,KAACD;0BACC,cAAA,MAACI;oBAAMwF,SAAS;oBAAGK,IAAI;wBAAEgC,IAAI;oBAAE;;sCAC7B,KAAC3G;4BACCsB,MAAK;4BACLlB,OAAOA,MAAMA,KAAK;4BAClBwG,YAAYxG,MAAMyG,SAAS;4BAC3BhG,OAAO,WAAET,sBAAAA,MAAMU,YAAY,qBAAlBV,oBAAoBS,OAAO,mBAAI;4BACxCN,GAAG,EAAGA,cAAAA,MAAO;4BACb+E,OAAO9D;4BACPiE,UAAUU;4BACV1C,UAAUhC;4BACVqF,UAAU,EAAER,kBAAAA,UAAW/D;;sCAEzB,KAACtC;4BACCoF,YAAW;4BACXpD,UAAUA;4BACVmC,OAAM;4BACNkB,OAAOC;4BACPE,UAAUW;4BACV3C,UAAUhC;;sCAEZ,KAAC7C;4BACCmI,uBACE,KAACzI;gCACC0I,SAAS9B,WAAWnE,MAAMmD,MAAM,KAAK;gCACrCT,UAAUhC,QAAQV,MAAMmD,MAAM,KAAK;gCACnCuB,UAAU,CAACwB,QAAUZ,WAAWY,MAAMC,MAAM,CAACF,OAAO;;4BAGxD5C,OAAM;;;;;0BAIZ,MAAC3F;;kCACC,KAACJ;wBAAOqF,SAAS8C;wBAAO/C,UAAUhC;kCAC/B;;kCAEH,KAACpD;wBACC+D,SAAQ;wBACRqB,UAAUhC,QAAQ,CAACD,aAAa2F,QAAQb;wBACxC5C,SAAS;4BACP,IAAI,CAAClC,WAAW;4BAChB,MAAMyE,QAAQ,MAAMD,MAAM;gCACxBxE;gCACA+D,MAAMA,QAAQhD;gCACd2C,SAASA,WAAWnE,MAAMmD,MAAM,KAAK;4BACvC;4BACA,IAAI+B,OAAOM;wBACb;kCAEC;;;;;;AAKX;AAEA,eAAerG,qBAAoB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-contact-roles-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport {\n CRM_COLLECTIONS,\n type CrmDealContactRole,\n dealContactRolesOf,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, writeGuardedBySeed } from '@aglyn/tenant-feature-instance'\nimport { deleteField, doc, updateDoc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmOrgDoc, useCrmScope } from '../hooks/use-crm-scope'\nimport { CRM_DEAL_CONTACT_ROLE_LIST_FIELDS, crmClientListFields } from '../model/crm-list-query'\nimport type { CrmRoutes } from '../model/crm-routes'\nimport type { DealDoc } from '../model/deal-board-model'\nimport { dealContactRolesSave } from '../model/deal-contact-roles'\nimport { ContactRolesEditor } from './contact-roles-editor'\n\nexport interface DealContactRolesCardProps {\n deal: DealDoc\n /** The site the page is read under, or `null` at the organization level. */\n hostId: string | null\n org: CrmOrgDoc\n routes: CrmRoutes\n /** The listener's verdict on the deal, for the stale-seed guard. */\n fromCache: boolean\n unreadable: boolean\n}\n\n/**\n * Salesforce's Opportunity Contact Roles on a deal's page (AGL-3521): the\n * people on the deal, the part each plays, and which one is Primary.\n *\n * Every edit is one client-direct write of the whole list beside the\n * `contactId` its Primary names — the field every reader that came before\n * roles still reads — and the list fields a contact's page finds the deal\n * by. A role is one of the org's `opportunityContactRole` values, kept on\n * the Deals tab of Fields; a contact keeps the role it holds even once that\n * value is deactivated. Adding opens a dialog rather than a row of fields\n * above the list.\n */\nexport function DealContactRolesCard(props: DealContactRolesCardProps) {\n const { deal, hostId, org, routes, fromCache, unreadable } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const scope = useCrmScope({ hostId, org })\n const groupId = scope.consentGroup?.groupId ?? null\n const roles = useMemo(() => dealContactRolesOf(deal), [deal])\n const roleList = useCrmPicklist('opportunityContactRole', scope.orgId)\n const nameOf = useCrmRecordNames({\n orgId: scope.orgId,\n groupId,\n org: (org ?? null) as Record<string, unknown> | null,\n records: roles.map((row) => ({ kind: 'contact' as const, id: row.contactId })),\n })\n const [busy, setBusy] = useState(false)\n const [adding, setAdding] = useState(false)\n\n /** The one write: the list, its Primary as `contactId`, and the list fields. */\n const save = useCallback(\n async (next: CrmDealContactRole[], done: string) => {\n if (!scope.orgId) return false\n const verdict = dealContactRolesSave(deal, next, roleList.picklist)\n if (verdict.ok === false) {\n enqueueSnackbar(verdict.error, { variant: 'warning', persist: false })\n return false\n }\n const primaryName = verdict.contactId ? nameOf('contact', verdict.contactId) : undefined\n const patch: Record<string, unknown> = {\n contactRoles: verdict.contactRoles,\n contactId: verdict.contactId ?? deleteField(),\n // The name the drawer copies beside `contactId`, moved with the Primary.\n ...(verdict.primaryChanged ? { contactName: primaryName || deleteField() } : {}),\n }\n setBusy(true)\n try {\n const written = await writeGuardedBySeed(\n { subject: 'deal', unreadable, fromCache },\n async () => {\n await updateDoc(doc(firestore, 'orgs', scope.orgId as string, CRM_COLLECTIONS.deals, deal.$id), {\n ...patch,\n ...crmClientListFields(\n 'deals',\n deal as unknown as Record<string, unknown>,\n patch,\n CRM_DEAL_CONTACT_ROLE_LIST_FIELDS,\n ),\n updatedAt: new Date(),\n })\n },\n )\n if (!written.ok) {\n enqueueSnackbar(written.message, { variant: 'warning', persist: false })\n return false\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n return false\n } finally {\n setBusy(false)\n }\n },\n [scope.orgId, deal, roleList.picklist, nameOf, unreadable, fromCache, firestore, enqueueSnackbar],\n )\n\n return (\n <ContactRolesEditor\n noun=\"deal\"\n roles={roles}\n busy={busy}\n adding={adding}\n onAddingChange={setAdding}\n ready={Boolean(scope.orgId)}\n hostId={hostId}\n org={org}\n routes={routes}\n picklist={roleList.picklist}\n nameOf={nameOf}\n help={pluginDocsHelp('deals', { anchor: '#contact-roles' })}\n emptyDescription=\"Name the people on the deal and the part each plays — one of them Primary.\"\n save={save}\n />\n )\n}\nDealContactRolesCard.displayName = 'DealContactRolesCard'\n\nexport default DealContactRolesCard\n"],"names":["CRM_COLLECTIONS","dealContactRolesOf","pluginDocsHelp","useSnackbar","useFirestore","writeGuardedBySeed","deleteField","doc","updateDoc","useCallback","useMemo","useState","useCrmPicklist","useCrmRecordNames","useCrmScope","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS","crmClientListFields","dealContactRolesSave","ContactRolesEditor","DealContactRolesCard","props","scope","deal","hostId","org","routes","fromCache","unreadable","firestore","enqueueSnackbar","groupId","consentGroup","roles","roleList","orgId","nameOf","records","map","row","kind","id","contactId","busy","setBusy","adding","setAdding","save","next","done","verdict","picklist","ok","error","variant","persist","primaryName","undefined","patch","contactRoles","primaryChanged","contactName","written","subject","deals","$id","updatedAt","Date","message","console","allowDuplicate","noun","onAddingChange","ready","Boolean","help","anchor","emptyDescription","displayName"],"mappings":"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"}
@@ -332,7 +332,8 @@ import { RecordTasksCard } from "./record-tasks-card.js";
332
332
  /*#__PURE__*/ _jsx(RecordActivityCard, {
333
333
  hostId: hostId,
334
334
  org: org,
335
- dealId: deal.$id
335
+ dealId: deal.$id,
336
+ deal: deal
336
337
  })
337
338
  ]
338
339
  }) : null
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-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, dealStageById, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport { useDeal } from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport { DEAL_STATUS_LABELS, formatMoney } from '../model/deal-board-model'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { RecordActivityCard } from './record-activity-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport { DealContactRolesCard } from './deal-contact-roles-card'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { DealProductsCard } from './deal-products-card'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { DealPropertiesCard } from './deal-properties-card'\nimport { DealStageCard } from './deal-stage-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\n\n/**\n * `/crm/deals/{dealId}` — one deal (AGL-2598).\n *\n * The record behind a card: its stage and the controls that move it, what\n * it is worth and who it is with — every contact on it and the part each\n * plays (AGL-3521) — the products behind the amount (AGL-2620), and the\n * tasks and activity filed against it. One live\n * document read; the pipeline and the roster are the same bounded reads\n * the board makes. Editing opens the same drawer the board creates with,\n * and deleting is the one destructive act here — confirmed, then a\n * client-direct delete the rules allow the same people who could have\n * created it.\n */\nexport function DealDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const scope = useCrmScope({ hostId, org })\n const { data: deal, status, fromCache } = useDeal(scope.orgId, id)\n // The feed the act is logged in, decided by the level it was performed\n // at (AGL-2738): this site's under a site, the organization's at the org\n // hub, where a client-direct append to the deal's own site would face a\n // gate the record write never had to pass.\n const logActivity = useCrmActivityLogger(hostId)\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const pipeline = pipelineState.pipelineById(deal?.pipelineId)\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n const [editing, setEditing] = useState(false)\n const [deleting, setDeleting] = useState(false)\n // The org's deal fields (AGL-2661), for the properties card's rows.\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // The deal's campaign by name (AGL-3516), read only for a deal that names one.\n const campaigns = useCrmCampaigns(\n { hostId, orgId: scope.orgId },\n { enabled: Boolean(deal?.campaignId) },\n )\n\n const notFound = scope.ready && (!scope.orgId || (status !== 'loading' && !deal))\n\n const handleDelete = useCallback(async () => {\n if (!deal || !scope.orgId) return\n const agreed = await confirm({\n title: 'Delete this deal?',\n description:\n `\"${deal.title}\" is removed from the pipeline along with its notes. ` +\n 'The contact and the company it names are not touched.',\n confirmationText: 'Delete deal',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!agreed) return\n setDeleting(true)\n try {\n await deleteDoc(doc(firestore, 'orgs', scope.orgId, CRM_COLLECTIONS.deals, deal.$id))\n // Setup → Activity shows CRM work (AGL-2622): the deal is org data,\n // but the act happened in this site's console and belongs in its feed.\n logActivity('Deleted deal', { type: 'deal', id: deal.$id, name: deal.title })\n enqueueSnackbar('Deal deleted', { variant: 'success', persist: false })\n router.push(routes.section('deals'))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n setDeleting(false)\n }\n }, [deal, scope.orgId, confirm, firestore, logActivity, enqueueSnackbar, router, routes])\n\n const stage = deal ? dealStageById(pipeline, deal.stageId) : undefined\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the deal's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={deal?.hostId ?? null}>\n <Stack spacing={2}>\n <CrmRecordHeader\n kind=\"Deal\"\n title={deal?.title}\n subtitle={\n deal\n ? [pipeline?.name, stage?.name ?? deal.stageId].filter(Boolean).join(' · ')\n : undefined\n }\n help={pluginDocsHelp('deals', { anchor: '#a-deals-page' })}\n backHref={routes.section('deals')}\n backLabel=\"Back to deals\"\n loading={!deal && !notFound}\n // The booking door (AGL-2660): the deal's own site's services.\n booking={\n deal\n ? { hostId: hostId ?? deal.hostId ?? null, org, kind: 'deal', recordId: deal.$id }\n : undefined\n }\n actions={\n deal ? (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n onClick={() => setEditing(true)}\n >\n {'Edit'}\n </Button>\n <CrmSendEmailButton\n hostId={hostId ?? deal.hostId ?? null}\n org={org}\n dealId={deal.$id}\n contactId={deal.contactId}\n name={deal.contactName}\n />\n </>\n ) : null\n }\n menuItems={\n deal\n ? [\n {\n key: 'delete',\n label: 'Delete deal',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: deleting,\n disabledReason: 'The deal is being deleted',\n onClick: () => void handleDelete(),\n },\n ]\n : undefined\n }\n chips={\n deal ? (\n <>\n <CrmRecordChip\n label=\"Status\"\n value={DEAL_STATUS_LABELS[deal.status] ?? deal.status}\n color={deal.status === 'won' ? 'success' : deal.status === 'lost' ? 'default' : 'primary'}\n />\n <CrmRecordChip\n label=\"Amount\"\n value={\n typeof deal.amountCents === 'number'\n ? formatMoney(deal.amountCents, deal.currency)\n : undefined\n }\n />\n <CrmRecordChip label=\"Owner\" value={roster.nameOf(deal.ownerUid) || undefined} />\n {/* Seen here only through a share (AGL-3336). */}\n <CrmShareChipView\n chip={crmShareChipFor(\n deal as unknown as Record<string, unknown>,\n scope.consentGroup?.hostIds ?? [],\n )}\n org={org}\n />\n </>\n ) : null\n }\n >\n {notFound ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This deal does not exist, or is not visible to this site.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n {deal ? (\n <>\n {/* What an assistant proposes about the deal, its stage included (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"deal\"\n recordId={deal.$id}\n name={deal.title}\n taskLink={{ dealId: deal.$id }}\n deal={{ pipeline, ref: deal, stageId: deal.stageId, status: deal.status, api }}\n />\n <DealStageCard deal={deal} pipeline={pipeline} api={api} nowMs={nowMs} />\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2} sx={{ alignItems: 'stretch' }}>\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <DealPropertiesCard\n deal={deal}\n pipeline={pipeline}\n ownerLabel={roster.nameOf(deal.ownerUid)}\n routes={routes}\n customFields={dealFields.active}\n campaignName={\n deal.campaignId\n ? (campaigns.options.find((option) => option.value === deal.campaignId)?.label ??\n deal.campaignId)\n : undefined\n }\n />\n </Stack>\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} dealId={deal.$id} />\n </Stack>\n </Stack>\n {/* The people on the deal and the part each plays (AGL-3521). */}\n <DealContactRolesCard\n deal={deal}\n hostId={hostId}\n org={org}\n routes={routes}\n fromCache={fromCache}\n unreadable={status === 'error'}\n />\n {scope.orgId ? (\n <DealProductsCard\n deal={deal}\n orgId={scope.orgId}\n hostId={hostId ?? deal.hostId ?? null}\n org={org}\n fromCache={fromCache}\n unreadable={status === 'error'}\n />\n ) : null}\n <RecordFilesCard\n scope={scope.scope}\n collection={CRM_COLLECTIONS.deals}\n recordId={deal.$id}\n mediaIds={deal.mediaIds}\n topic=\"deals\"\n />\n {/* Where the deal is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"deals\"\n id={deal.$id}\n record={deal as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={scope.orgId}\n org={org}\n viewingHostIds={scope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} dealId={deal.$id} />\n </>\n ) : null}\n </Stack>\n <DealEditDrawer\n open={editing}\n onClose={() => setEditing(false)}\n hostId={hostId}\n org={org}\n deal={deal ?? null}\n pipelines={pipelineState.activePipelines}\n defaultPipeline={pipeline ?? pipelineState.pipeline}\n unreadable={status === 'error'}\n fromCache={fromCache}\n />\n </CrmCreateSiteDefault>\n )\n}\nDealDetailPage.displayName = 'DealDetailPage'\n\nexport default DealDetailPage\n"],"names":["CRM_COLLECTIONS","dealStageById","pluginDocsHelp","mdiDeleteOutline","mdiPencilOutline","MdiIcon","useConfirmationContext","useSnackbar","useFirestore","Button","Stack","Typography","deleteDoc","doc","useRouter","useCallback","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","CrmCreateSiteDefault","useCrmScope","useDealStageApi","useDeal","useOrgMemberDirectory","usePipeline","crmRoutes","DEAL_STATUS_LABELS","formatMoney","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","RecordActivityCard","CrmShareChipView","RecordSharingCard","crmShareChipFor","CrmSendEmailButton","DealContactRolesCard","DealEditDrawer","DealProductsCard","useCrmCampaigns","DealPropertiesCard","DealStageCard","RecordFilesCard","RecordTasksCard","DealDetailPage","props","hostId","scope","campaigns","id","basePath","org","routes","router","firestore","enqueueSnackbar","confirm","data","deal","status","fromCache","orgId","logActivity","pipelineState","pipeline","pipelineById","pipelineId","roster","api","nowMs","Date","now","editing","setEditing","deleting","setDeleting","dealFields","enabled","Boolean","campaignId","notFound","ready","handleDelete","agreed","title","description","confirmationText","confirmationButtonProps","color","then","catch","deals","$id","type","name","variant","persist","push","section","error","console","allowDuplicate","stage","stageId","undefined","spacing","kind","subtitle","filter","join","help","anchor","backHref","backLabel","loading","booking","recordId","actions","size","startIcon","path","onClick","dealId","contactId","contactName","menuItems","key","label","icon","destructive","disabled","disabledReason","chips","value","amountCents","currency","nameOf","ownerUid","chip","consentGroup","hostIds","taskLink","ref","direction","xs","md","sx","alignItems","flex","minWidth","ownerLabel","customFields","active","campaignName","options","find","option","unreadable","collection","mediaIds","topic","object","record","viewingHostIds","open","onClose","pipelines","activePipelines","defaultPipeline","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-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, dealStageById, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmActivityLogger } from '../hooks/use-crm-activity-logger'\nimport { CrmCreateSiteDefault } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useDealStageApi } from '../hooks/use-deal-stage-api'\nimport { useDeal } from '../hooks/use-deals'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { usePipeline } from '../hooks/use-pipeline'\nimport { type CrmDetailPageProps, crmRoutes } from '../model/crm-routes'\nimport { DEAL_STATUS_LABELS, formatMoney } from '../model/deal-board-model'\nimport { CrmRecordChip, CrmRecordHeader } from './crm-record-header'\nimport { CrmRecordInsightsZone } from './crm-record-insights-zone'\nimport { RecordActivityCard } from './record-activity-card'\nimport { CrmShareChipView, RecordSharingCard } from './record-sharing-card'\nimport { crmShareChipFor } from '../model/crm-sharing'\nimport { CrmSendEmailButton } from './crm-send-email-button'\nimport { DealContactRolesCard } from './deal-contact-roles-card'\nimport { DealEditDrawer } from './deal-edit-drawer'\nimport { DealProductsCard } from './deal-products-card'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { DealPropertiesCard } from './deal-properties-card'\nimport { DealStageCard } from './deal-stage-card'\nimport RecordFilesCard from './record-files-card'\nimport { RecordTasksCard } from './record-tasks-card'\n\n/**\n * `/crm/deals/{dealId}` — one deal (AGL-2598).\n *\n * The record behind a card: its stage and the controls that move it, what\n * it is worth and who it is with — every contact on it and the part each\n * plays (AGL-3521) — the products behind the amount (AGL-2620), and the\n * tasks and activity filed against it. One live\n * document read; the pipeline and the roster are the same bounded reads\n * the board makes. Editing opens the same drawer the board creates with,\n * and deleting is the one destructive act here — confirmed, then a\n * client-direct delete the rules allow the same people who could have\n * created it.\n */\nexport function DealDetailPage(props: CrmDetailPageProps) {\n const { id, basePath, hostId, org } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const scope = useCrmScope({ hostId, org })\n const { data: deal, status, fromCache } = useDeal(scope.orgId, id)\n // The feed the act is logged in, decided by the level it was performed\n // at (AGL-2738): this site's under a site, the organization's at the org\n // hub, where a client-direct append to the deal's own site would face a\n // gate the record write never had to pass.\n const logActivity = useCrmActivityLogger(hostId)\n const pipelineState = usePipeline(scope.orgId, {\n hostId,\n org: (org ?? null) as Record<string, unknown> | null,\n })\n const pipeline = pipelineState.pipelineById(deal?.pipelineId)\n const roster = useOrgMemberDirectory(scope.orgId)\n const api = useDealStageApi(hostId)\n const nowMs = useMemo(() => Date.now(), [])\n const [editing, setEditing] = useState(false)\n const [deleting, setDeleting] = useState(false)\n // The org's deal fields (AGL-2661), for the properties card's rows.\n const dealFields = useContactFieldDefinitions(scope.orgId, 'deal')\n // The deal's campaign by name (AGL-3516), read only for a deal that names one.\n const campaigns = useCrmCampaigns(\n { hostId, orgId: scope.orgId },\n { enabled: Boolean(deal?.campaignId) },\n )\n\n const notFound = scope.ready && (!scope.orgId || (status !== 'loading' && !deal))\n\n const handleDelete = useCallback(async () => {\n if (!deal || !scope.orgId) return\n const agreed = await confirm({\n title: 'Delete this deal?',\n description:\n `\"${deal.title}\" is removed from the pipeline along with its notes. ` +\n 'The contact and the company it names are not touched.',\n confirmationText: 'Delete deal',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!agreed) return\n setDeleting(true)\n try {\n await deleteDoc(doc(firestore, 'orgs', scope.orgId, CRM_COLLECTIONS.deals, deal.$id))\n // Setup → Activity shows CRM work (AGL-2622): the deal is org data,\n // but the act happened in this site's console and belongs in its feed.\n logActivity('Deleted deal', { type: 'deal', id: deal.$id, name: deal.title })\n enqueueSnackbar('Deal deleted', { variant: 'success', persist: false })\n router.push(routes.section('deals'))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error', allowDuplicate: true })\n setDeleting(false)\n }\n }, [deal, scope.orgId, confirm, firestore, logActivity, enqueueSnackbar, router, routes])\n\n const stage = deal ? dealStageById(pipeline, deal.stageId) : undefined\n\n return (\n // A create opened from this page — a task, an activity — files under\n // the deal's own site unless the reader says otherwise (AGL-2630).\n <CrmCreateSiteDefault hostId={deal?.hostId ?? null}>\n <Stack spacing={2}>\n <CrmRecordHeader\n kind=\"Deal\"\n title={deal?.title}\n subtitle={\n deal\n ? [pipeline?.name, stage?.name ?? deal.stageId].filter(Boolean).join(' · ')\n : undefined\n }\n help={pluginDocsHelp('deals', { anchor: '#a-deals-page' })}\n backHref={routes.section('deals')}\n backLabel=\"Back to deals\"\n loading={!deal && !notFound}\n // The booking door (AGL-2660): the deal's own site's services.\n booking={\n deal\n ? { hostId: hostId ?? deal.hostId ?? null, org, kind: 'deal', recordId: deal.$id }\n : undefined\n }\n actions={\n deal ? (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n onClick={() => setEditing(true)}\n >\n {'Edit'}\n </Button>\n <CrmSendEmailButton\n hostId={hostId ?? deal.hostId ?? null}\n org={org}\n dealId={deal.$id}\n contactId={deal.contactId}\n name={deal.contactName}\n />\n </>\n ) : null\n }\n menuItems={\n deal\n ? [\n {\n key: 'delete',\n label: 'Delete deal',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: deleting,\n disabledReason: 'The deal is being deleted',\n onClick: () => void handleDelete(),\n },\n ]\n : undefined\n }\n chips={\n deal ? (\n <>\n <CrmRecordChip\n label=\"Status\"\n value={DEAL_STATUS_LABELS[deal.status] ?? deal.status}\n color={deal.status === 'won' ? 'success' : deal.status === 'lost' ? 'default' : 'primary'}\n />\n <CrmRecordChip\n label=\"Amount\"\n value={\n typeof deal.amountCents === 'number'\n ? formatMoney(deal.amountCents, deal.currency)\n : undefined\n }\n />\n <CrmRecordChip label=\"Owner\" value={roster.nameOf(deal.ownerUid) || undefined} />\n {/* Seen here only through a share (AGL-3336). */}\n <CrmShareChipView\n chip={crmShareChipFor(\n deal as unknown as Record<string, unknown>,\n scope.consentGroup?.hostIds ?? [],\n )}\n org={org}\n />\n </>\n ) : null\n }\n >\n {notFound ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This deal does not exist, or is not visible to this site.'}\n </Typography>\n ) : null}\n </CrmRecordHeader>\n {deal ? (\n <>\n {/* What an assistant proposes about the deal, its stage included (AGL-2917). */}\n <CrmRecordInsightsZone\n hostId={hostId}\n org={org as Record<string, unknown> | undefined}\n kind=\"deal\"\n recordId={deal.$id}\n name={deal.title}\n taskLink={{ dealId: deal.$id }}\n deal={{ pipeline, ref: deal, stageId: deal.stageId, status: deal.status, api }}\n />\n <DealStageCard deal={deal} pipeline={pipeline} api={api} nowMs={nowMs} />\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2} sx={{ alignItems: 'stretch' }}>\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <DealPropertiesCard\n deal={deal}\n pipeline={pipeline}\n ownerLabel={roster.nameOf(deal.ownerUid)}\n routes={routes}\n customFields={dealFields.active}\n campaignName={\n deal.campaignId\n ? (campaigns.options.find((option) => option.value === deal.campaignId)?.label ??\n deal.campaignId)\n : undefined\n }\n />\n </Stack>\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <RecordTasksCard hostId={hostId} org={org} basePath={basePath} dealId={deal.$id} />\n </Stack>\n </Stack>\n {/* The people on the deal and the part each plays (AGL-3521). */}\n <DealContactRolesCard\n deal={deal}\n hostId={hostId}\n org={org}\n routes={routes}\n fromCache={fromCache}\n unreadable={status === 'error'}\n />\n {scope.orgId ? (\n <DealProductsCard\n deal={deal}\n orgId={scope.orgId}\n hostId={hostId ?? deal.hostId ?? null}\n org={org}\n fromCache={fromCache}\n unreadable={status === 'error'}\n />\n ) : null}\n <RecordFilesCard\n scope={scope.scope}\n collection={CRM_COLLECTIONS.deals}\n recordId={deal.$id}\n mediaIds={deal.mediaIds}\n topic=\"deals\"\n />\n {/* Where the deal is visible and why, and a manager's share (AGL-3336). */}\n <RecordSharingCard\n object=\"deals\"\n id={deal.$id}\n record={deal as unknown as Record<string, unknown>}\n hostId={hostId ?? null}\n orgId={scope.orgId}\n org={org}\n viewingHostIds={scope.consentGroup?.hostIds}\n />\n <RecordActivityCard hostId={hostId} org={org} dealId={deal.$id} deal={deal} />\n </>\n ) : null}\n </Stack>\n <DealEditDrawer\n open={editing}\n onClose={() => setEditing(false)}\n hostId={hostId}\n org={org}\n deal={deal ?? null}\n pipelines={pipelineState.activePipelines}\n defaultPipeline={pipeline ?? pipelineState.pipeline}\n unreadable={status === 'error'}\n fromCache={fromCache}\n />\n </CrmCreateSiteDefault>\n )\n}\nDealDetailPage.displayName = 'DealDetailPage'\n\nexport default DealDetailPage\n"],"names":["CRM_COLLECTIONS","dealStageById","pluginDocsHelp","mdiDeleteOutline","mdiPencilOutline","MdiIcon","useConfirmationContext","useSnackbar","useFirestore","Button","Stack","Typography","deleteDoc","doc","useRouter","useCallback","useMemo","useState","useContactFieldDefinitions","useCrmActivityLogger","CrmCreateSiteDefault","useCrmScope","useDealStageApi","useDeal","useOrgMemberDirectory","usePipeline","crmRoutes","DEAL_STATUS_LABELS","formatMoney","CrmRecordChip","CrmRecordHeader","CrmRecordInsightsZone","RecordActivityCard","CrmShareChipView","RecordSharingCard","crmShareChipFor","CrmSendEmailButton","DealContactRolesCard","DealEditDrawer","DealProductsCard","useCrmCampaigns","DealPropertiesCard","DealStageCard","RecordFilesCard","RecordTasksCard","DealDetailPage","props","hostId","scope","campaigns","id","basePath","org","routes","router","firestore","enqueueSnackbar","confirm","data","deal","status","fromCache","orgId","logActivity","pipelineState","pipeline","pipelineById","pipelineId","roster","api","nowMs","Date","now","editing","setEditing","deleting","setDeleting","dealFields","enabled","Boolean","campaignId","notFound","ready","handleDelete","agreed","title","description","confirmationText","confirmationButtonProps","color","then","catch","deals","$id","type","name","variant","persist","push","section","error","console","allowDuplicate","stage","stageId","undefined","spacing","kind","subtitle","filter","join","help","anchor","backHref","backLabel","loading","booking","recordId","actions","size","startIcon","path","onClick","dealId","contactId","contactName","menuItems","key","label","icon","destructive","disabled","disabledReason","chips","value","amountCents","currency","nameOf","ownerUid","chip","consentGroup","hostIds","taskLink","ref","direction","xs","md","sx","alignItems","flex","minWidth","ownerLabel","customFields","active","campaignName","options","find","option","unreadable","collection","mediaIds","topic","object","record","viewingHostIds","open","onClose","pipelines","activePipelines","defaultPipeline","displayName"],"mappings":"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"}
@@ -103,7 +103,7 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
103
103
  const firestore = useFirestore();
104
104
  const { enqueueSnackbar } = useSnackbar();
105
105
  const { data: user } = useUser();
106
- const { orgId, consentGroup, visibleTo, createHostId, createTokens } = useCrmScope({
106
+ const { orgId, consentGroup, visibleTo, createHostId, createTokens, createNeedsSite } = useCrmScope({
107
107
  hostId,
108
108
  org
109
109
  });
@@ -344,9 +344,11 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
344
344
  });
345
345
  onSaved == null ? void 0 : onSaved(deal.$id, 'edit');
346
346
  } else {
347
- // Held by the button until the capturing site is known; checked
348
- // again here because a callback can outlive the render that held it.
349
- if (!createHostId) return;
347
+ // Held by the button until it knows who may see the deal — the whole
348
+ // org, or (in an org that keeps its sites apart) a picked site's
349
+ // group; checked again here because a callback can outlive the
350
+ // render that held it.
351
+ if (!createTokens.length) return;
350
352
  const customDocument = crmCustomDraftDocument(custom);
351
353
  const record = _extends({}, dealDocumentFromForm(saved, {
352
354
  visibleTo: [
@@ -466,7 +468,8 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
466
468
  mode === 'create' ? /*#__PURE__*/ _jsx(CrmSitePicker, {
467
469
  hostId: hostId,
468
470
  disabled: busy,
469
- helperText: "The site this deal belongs to — it decides which of your sites may see it."
471
+ optional: !createNeedsSite,
472
+ helperText: createNeedsSite ? 'The site this deal belongs to — it decides which of your sites may see it.' : undefined
470
473
  }) : null,
471
474
  /*#__PURE__*/ _jsx(TextField, {
472
475
  label: "Title",
@@ -849,8 +852,9 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
849
852
  }) : null,
850
853
  /*#__PURE__*/ _jsx(Button, {
851
854
  variant: "contained",
852
- disabled: busy || Boolean(problem) || !orgId || bandFull || // A new deal waits for its capturing site.
853
- mode === 'create' && !createHostId,
855
+ disabled: busy || Boolean(problem) || !orgId || bandFull || // A new deal waits for its scope: a site, where the org keeps
856
+ // its sites apart; nothing, where every site sees it.
857
+ mode === 'create' && !createTokens.length,
854
858
  onClick: ()=>void handleSave(),
855
859
  children: deal ? 'Save deal' : 'Create deal'
856
860
  })