@aglyn/plugins-crm 1.0.0 → 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 (154) 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 -20
  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/log-activity-dialog.d.ts +27 -9
  56. package/src/lib/components/log-activity-dialog.js +96 -32
  57. package/src/lib/components/log-activity-dialog.js.map +1 -1
  58. package/src/lib/components/new-contact-drawer.d.ts +7 -3
  59. package/src/lib/components/new-contact-drawer.js +27 -15
  60. package/src/lib/components/new-contact-drawer.js.map +1 -1
  61. package/src/lib/components/pipeline-stages-dialog.js +5 -1
  62. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  63. package/src/lib/components/record-activity-card.d.ts +11 -2
  64. package/src/lib/components/record-activity-card.js +90 -5
  65. package/src/lib/components/record-activity-card.js.map +1 -1
  66. package/src/lib/components/settings-section.d.ts +4 -2
  67. package/src/lib/components/settings-section.js +7 -5
  68. package/src/lib/components/settings-section.js.map +1 -1
  69. package/src/lib/hooks/crm-activity-client-reads.d.ts +8 -0
  70. package/src/lib/hooks/crm-activity-client-reads.js +50 -0
  71. package/src/lib/hooks/crm-activity-client-reads.js.map +1 -0
  72. package/src/lib/hooks/use-campaign-filing-log.d.ts +2 -1
  73. package/src/lib/hooks/use-campaign-filing-log.js +10 -3
  74. package/src/lib/hooks/use-campaign-filing-log.js.map +1 -1
  75. package/src/lib/hooks/use-crm-org-mount.d.ts +8 -6
  76. package/src/lib/hooks/use-crm-org-mount.js +2 -1
  77. package/src/lib/hooks/use-crm-org-mount.js.map +1 -1
  78. package/src/lib/hooks/use-crm-scope.d.ts +17 -5
  79. package/src/lib/hooks/use-crm-scope.js +12 -5
  80. package/src/lib/hooks/use-crm-scope.js.map +1 -1
  81. package/src/lib/model/contact-holder.d.ts +12 -0
  82. package/src/lib/model/contact-holder.js +20 -1
  83. package/src/lib/model/contact-holder.js.map +1 -1
  84. package/src/lib/model/contact-record.d.ts +5 -0
  85. package/src/lib/model/contact-record.js +7 -1
  86. package/src/lib/model/contact-record.js.map +1 -1
  87. package/src/lib/model/contact-update.d.ts +6 -0
  88. package/src/lib/model/contact-update.js +1 -0
  89. package/src/lib/model/contact-update.js.map +1 -1
  90. package/src/lib/model/crm-activity-link.d.ts +117 -0
  91. package/src/lib/model/crm-activity-link.js +113 -0
  92. package/src/lib/model/crm-activity-link.js.map +1 -0
  93. package/src/lib/model/crm-api.d.ts +4 -1
  94. package/src/lib/model/crm-api.js +9 -5
  95. package/src/lib/model/crm-api.js.map +1 -1
  96. package/src/lib/model/deal-board-model.js +5 -3
  97. package/src/lib/model/deal-board-model.js.map +1 -1
  98. package/src/lib/model/deal-contact-roles.d.ts +15 -1
  99. package/src/lib/model/deal-contact-roles.js +17 -3
  100. package/src/lib/model/deal-contact-roles.js.map +1 -1
  101. package/src/lib/model/settle-stage-names.d.ts +25 -0
  102. package/src/lib/model/settle-stage-names.js +29 -0
  103. package/src/lib/model/settle-stage-names.js.map +1 -0
  104. package/src/lib/native-nav.js +1 -1
  105. package/src/lib/native-nav.js.map +1 -1
  106. package/src/lib/server/add-contact-by-hand.d.ts +84 -0
  107. package/src/lib/server/add-contact-by-hand.js +212 -0
  108. package/src/lib/server/add-contact-by-hand.js.map +1 -0
  109. package/src/lib/server/api-v1/contacts.js +61 -23
  110. package/src/lib/server/api-v1/contacts.js.map +1 -1
  111. package/src/lib/server/api-v1/crm-activities.js +13 -3
  112. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  113. package/src/lib/server/api-v1/crm-pipelines.d.ts +1 -1
  114. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  115. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  116. package/src/lib/server/api-v1/crm-shared.d.ts +16 -9
  117. package/src/lib/server/api-v1/crm-shared.js +23 -11
  118. package/src/lib/server/api-v1/crm-shared.js.map +1 -1
  119. package/src/lib/server/api-v1/openapi.js +3 -3
  120. package/src/lib/server/api-v1/openapi.js.map +1 -1
  121. package/src/lib/server/automation-steps.js +11 -9
  122. package/src/lib/server/automation-steps.js.map +1 -1
  123. package/src/lib/server/campaign-filing-activity.js +7 -2
  124. package/src/lib/server/campaign-filing-activity.js.map +1 -1
  125. package/src/lib/server/contact-update.d.ts +4 -0
  126. package/src/lib/server/contact-update.js +71 -10
  127. package/src/lib/server/contact-update.js.map +1 -1
  128. package/src/lib/server/convert-host-lead.js +13 -2
  129. package/src/lib/server/convert-host-lead.js.map +1 -1
  130. package/src/lib/server/convert-open-lead.js +8 -1
  131. package/src/lib/server/convert-open-lead.js.map +1 -1
  132. package/src/lib/server/crm-activity-company.d.ts +25 -0
  133. package/src/lib/server/crm-activity-company.js +34 -0
  134. package/src/lib/server/crm-activity-company.js.map +1 -0
  135. package/src/lib/server/crm-inbound-email.js +21 -15
  136. package/src/lib/server/crm-inbound-email.js.map +1 -1
  137. package/src/lib/server/crm-sharing.js +9 -2
  138. package/src/lib/server/crm-sharing.js.map +1 -1
  139. package/src/lib/server/email-send.js +10 -8
  140. package/src/lib/server/email-send.js.map +1 -1
  141. package/src/lib/server/merge-contacts.js +6 -2
  142. package/src/lib/server/merge-contacts.js.map +1 -1
  143. package/src/lib/server/org-caller.d.ts +8 -0
  144. package/src/lib/server/org-caller.js +10 -0
  145. package/src/lib/server/org-caller.js.map +1 -1
  146. package/src/lib/server/record-timeline.d.ts +3 -1
  147. package/src/lib/server/record-timeline.js +11 -3
  148. package/src/lib/server/record-timeline.js.map +1 -1
  149. package/src/lib/server.js +152 -43
  150. package/src/lib/server.js.map +1 -1
  151. package/src/lib/transfer/contacts.js +83 -48
  152. package/src/lib/transfer/contacts.js.map +1 -1
  153. package/src/lib/transfer/fields.js +1 -2
  154. package/src/lib/transfer/fields.js.map +1 -1
@@ -0,0 +1,270 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { dealContactRolesWithout, dealContactRolesWithPrimary } from "@aglyn/aglyn";
19
+ import { mdiAccountPlusOutline, mdiAccountRemoveOutline } from "@aglyn/shared-data-mdi";
20
+ import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
21
+ import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
22
+ import { Box, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, IconButton, Stack, Tooltip, Typography } from "@mui/material";
23
+ import { useState } from "react";
24
+ import { useCrmScope } from "../hooks/use-crm-scope.js";
25
+ import { dealContactRoleAddProblem, dealContactRolesAdded, dealContactRolesWithRole } from "../model/deal-contact-roles.js";
26
+ import CrmRecordPicker from "./crm-record-picker.js";
27
+ import { CrmPicklistSelect } from "./picklist-select.js";
28
+ /**
29
+ * THE PEOPLE ON A RECORD, AND THE PART EACH PLAYS: Salesforce's Contact Roles,
30
+ * on a deal (AGL-3521) and on a company (AGL-4122).
31
+ *
32
+ * One card, because the two differ only in what a save writes, which is each
33
+ * record's own. A role is one of the org's `opportunityContactRole` values, and
34
+ * adding opens a dialog rather than a row of fields above the list.
35
+ */ export function ContactRolesEditor(props) {
36
+ const { noun, roles, busy, adding, onAddingChange, ready, hostId, org, routes, picklist, nameOf, help, emptyDescription, save } = props;
37
+ const addButton = (variant)=>/*#__PURE__*/ _jsx(Button, {
38
+ size: "small",
39
+ variant: variant,
40
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
41
+ path: mdiAccountPlusOutline.path,
42
+ size: 0.8
43
+ }),
44
+ disabled: busy || !ready,
45
+ onClick: ()=>onAddingChange(true),
46
+ children: 'Add contact'
47
+ });
48
+ return /*#__PURE__*/ _jsxs(_Fragment, {
49
+ children: [
50
+ /*#__PURE__*/ _jsx(CardDisplay, {
51
+ header: 'Contact roles',
52
+ help: help,
53
+ HeaderProps: {
54
+ action: addButton('text')
55
+ },
56
+ contentGutterX: true,
57
+ contentGutterY: true,
58
+ children: roles.length === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
59
+ compact: true,
60
+ label: `No contacts on this ${noun}`,
61
+ description: emptyDescription,
62
+ action: addButton('contained')
63
+ }) : /*#__PURE__*/ _jsx(Stack, {
64
+ spacing: 1.5,
65
+ children: roles.map((row)=>{
66
+ var _row_role;
67
+ const name = nameOf('contact', row.contactId) || row.contactId;
68
+ return /*#__PURE__*/ _jsxs(Stack, {
69
+ direction: {
70
+ xs: 'column',
71
+ sm: 'row'
72
+ },
73
+ spacing: 1,
74
+ sx: {
75
+ alignItems: {
76
+ sm: 'center'
77
+ }
78
+ },
79
+ children: [
80
+ /*#__PURE__*/ _jsxs(Stack, {
81
+ direction: "row",
82
+ spacing: 1,
83
+ sx: {
84
+ flex: 1,
85
+ minWidth: 0,
86
+ alignItems: 'center'
87
+ },
88
+ children: [
89
+ /*#__PURE__*/ _jsx(Typography, {
90
+ variant: "body2",
91
+ noWrap: true,
92
+ children: /*#__PURE__*/ _jsx(AppLink, {
93
+ href: routes.contact(row.contactId),
94
+ children: name
95
+ })
96
+ }),
97
+ row.primary ? /*#__PURE__*/ _jsx(Chip, {
98
+ size: "small",
99
+ color: "primary",
100
+ label: "Primary"
101
+ }) : null
102
+ ]
103
+ }),
104
+ /*#__PURE__*/ _jsx(Box, {
105
+ sx: {
106
+ width: {
107
+ xs: '100%',
108
+ sm: 220
109
+ }
110
+ },
111
+ children: /*#__PURE__*/ _jsx(CrmPicklistSelect, {
112
+ picklistId: "opportunityContactRole",
113
+ picklist: picklist,
114
+ label: "Role",
115
+ value: (_row_role = row.role) != null ? _row_role : '',
116
+ stored: row.role,
117
+ disabled: busy,
118
+ onChange: (role)=>void save(dealContactRolesWithRole(roles, row.contactId, role), 'Role saved')
119
+ })
120
+ }),
121
+ /*#__PURE__*/ _jsxs(Stack, {
122
+ direction: "row",
123
+ spacing: 0.5,
124
+ sx: {
125
+ alignItems: 'center'
126
+ },
127
+ children: [
128
+ row.primary ? null : /*#__PURE__*/ _jsx(Button, {
129
+ size: "small",
130
+ disabled: busy,
131
+ onClick: ()=>void save(dealContactRolesWithPrimary(roles, row.contactId), 'Primary contact set'),
132
+ children: 'Make primary'
133
+ }),
134
+ /*#__PURE__*/ _jsx(Tooltip, {
135
+ title: `Remove ${name} from this ${noun}`,
136
+ children: /*#__PURE__*/ _jsx("span", {
137
+ children: /*#__PURE__*/ _jsx(IconButton, {
138
+ size: "small",
139
+ "aria-label": `Remove ${name} from this ${noun}`,
140
+ disabled: busy,
141
+ onClick: ()=>void save(dealContactRolesWithout(roles, row.contactId), 'Contact removed'),
142
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
143
+ path: mdiAccountRemoveOutline.path,
144
+ size: 0.8
145
+ })
146
+ })
147
+ })
148
+ })
149
+ ]
150
+ })
151
+ ]
152
+ }, row.contactId);
153
+ })
154
+ })
155
+ }),
156
+ /*#__PURE__*/ _jsx(AddContactRoleDialog, {
157
+ noun: noun,
158
+ open: adding,
159
+ busy: busy,
160
+ roles: roles,
161
+ hostId: hostId,
162
+ org: org,
163
+ picklist: picklist,
164
+ onClose: ()=>onAddingChange(false),
165
+ onAdd: async (added)=>{
166
+ const ok = await save(dealContactRolesAdded(roles, added), 'Contact added');
167
+ if (ok) onAddingChange(false);
168
+ return ok;
169
+ }
170
+ })
171
+ ]
172
+ });
173
+ }
174
+ ContactRolesEditor.displayName = 'ContactRolesEditor';
175
+ /** Pick a contact, the part they play, and whether they are the Primary. */ function AddContactRoleDialog(props) {
176
+ var _ref;
177
+ var _scope_consentGroup;
178
+ const { noun, open, busy, roles, hostId, org, picklist, onClose, onAdd } = props;
179
+ const scope = useCrmScope({
180
+ hostId,
181
+ org
182
+ });
183
+ const [contactId, setContactId] = useState(null);
184
+ const [role, setRole] = useState('');
185
+ // The first contact on a record is always its Primary; later ones when ticked.
186
+ const [primary, setPrimary] = useState(false);
187
+ const problem = contactId ? dealContactRoleAddProblem(roles, contactId, noun) : null;
188
+ const reset = ()=>{
189
+ setContactId(null);
190
+ setRole('');
191
+ setPrimary(false);
192
+ };
193
+ const close = ()=>{
194
+ reset();
195
+ onClose();
196
+ };
197
+ return /*#__PURE__*/ _jsxs(Dialog, {
198
+ open: open,
199
+ onClose: busy ? undefined : close,
200
+ fullWidth: true,
201
+ maxWidth: "xs",
202
+ children: [
203
+ /*#__PURE__*/ _jsx(DialogTitle, {
204
+ children: `Add a contact to this ${noun}`
205
+ }),
206
+ /*#__PURE__*/ _jsx(DialogContent, {
207
+ children: /*#__PURE__*/ _jsxs(Stack, {
208
+ spacing: 2,
209
+ sx: {
210
+ pt: 1
211
+ },
212
+ children: [
213
+ /*#__PURE__*/ _jsx(CrmRecordPicker, {
214
+ kind: "contact",
215
+ scope: scope.scope,
216
+ readTokens: scope.visibleTo,
217
+ groupId: (_ref = (_scope_consentGroup = scope.consentGroup) == null ? void 0 : _scope_consentGroup.groupId) != null ? _ref : null,
218
+ org: org != null ? org : null,
219
+ value: contactId,
220
+ onChange: setContactId,
221
+ disabled: busy,
222
+ helperText: problem != null ? problem : undefined
223
+ }),
224
+ /*#__PURE__*/ _jsx(CrmPicklistSelect, {
225
+ picklistId: "opportunityContactRole",
226
+ picklist: picklist,
227
+ label: "Role",
228
+ value: role,
229
+ onChange: setRole,
230
+ disabled: busy
231
+ }),
232
+ /*#__PURE__*/ _jsx(FormControlLabel, {
233
+ control: /*#__PURE__*/ _jsx(Checkbox, {
234
+ checked: primary || roles.length === 0,
235
+ disabled: busy || roles.length === 0,
236
+ onChange: (event)=>setPrimary(event.target.checked)
237
+ }),
238
+ label: "Primary contact"
239
+ })
240
+ ]
241
+ })
242
+ }),
243
+ /*#__PURE__*/ _jsxs(DialogActions, {
244
+ children: [
245
+ /*#__PURE__*/ _jsx(Button, {
246
+ onClick: close,
247
+ disabled: busy,
248
+ children: 'Cancel'
249
+ }),
250
+ /*#__PURE__*/ _jsx(Button, {
251
+ variant: "contained",
252
+ disabled: busy || !contactId || Boolean(problem),
253
+ onClick: async ()=>{
254
+ if (!contactId) return;
255
+ const added = await onAdd({
256
+ contactId,
257
+ role: role || undefined,
258
+ primary: primary || roles.length === 0
259
+ });
260
+ if (added) reset();
261
+ },
262
+ children: 'Add'
263
+ })
264
+ ]
265
+ })
266
+ ]
267
+ });
268
+ }
269
+
270
+ //# sourceMappingURL=contact-roles-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/contact-roles-editor.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 type CrmDealContactRole,\n dealContactRolesWithout,\n dealContactRolesWithPrimary,\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 {\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 { type ComponentProps, useState } from 'react'\nimport type { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport { type CrmOrgDoc, useCrmScope } from '../hooks/use-crm-scope'\nimport type { CrmRoutes } from '../model/crm-routes'\nimport {\n dealContactRoleAddProblem,\n dealContactRolesAdded,\n dealContactRolesWithRole,\n} from '../model/deal-contact-roles'\nimport CrmRecordPicker from './crm-record-picker'\nimport { CrmPicklistSelect } from './picklist-select'\n\n/** What the roles are on, as a sentence names it. */\nexport type ContactRolesNoun = 'deal' | 'company'\n\nexport interface ContactRolesEditorProps {\n noun: ContactRolesNoun\n roles: readonly CrmDealContactRole[]\n busy: boolean\n adding: boolean\n onAddingChange: (adding: boolean) => void\n /** The org has loaded, so a contact can be picked. */\n ready: boolean\n hostId: string | null\n org: CrmOrgDoc\n routes: CrmRoutes\n picklist: ReturnType<typeof useCrmPicklist>['picklist']\n nameOf: (kind: 'contact', id: string) => string | undefined\n help: ComponentProps<typeof CardDisplay>['help']\n emptyDescription: string\n /** Writes the whole list, and answers whether it landed. */\n save: (next: CrmDealContactRole[], done: string) => Promise<boolean>\n}\n\n/**\n * THE PEOPLE ON A RECORD, AND THE PART EACH PLAYS: Salesforce's Contact Roles,\n * on a deal (AGL-3521) and on a company (AGL-4122).\n *\n * One card, because the two differ only in what a save writes, which is each\n * record's own. A role is one of the org's `opportunityContactRole` values, and\n * adding opens a dialog rather than a row of fields above the list.\n */\nexport function ContactRolesEditor(props: ContactRolesEditorProps) {\n const {\n noun,\n roles,\n busy,\n adding,\n onAddingChange,\n ready,\n hostId,\n org,\n routes,\n picklist,\n nameOf,\n help,\n emptyDescription,\n save,\n } = props\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 || !ready}\n onClick={() => onAddingChange(true)}\n >\n {'Add contact'}\n </Button>\n )\n\n return (\n <>\n <CardDisplay\n header={'Contact roles'}\n help={help}\n HeaderProps={{ action: addButton('text') }}\n contentGutterX\n contentGutterY\n >\n {roles.length === 0 ? (\n <EmptyStateComponent\n compact\n label={`No contacts on this ${noun}`}\n description={emptyDescription}\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={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 ${noun}`}>\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Remove ${name} from this ${noun}`}\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 noun={noun}\n open={adding}\n busy={busy}\n roles={roles}\n hostId={hostId}\n org={org}\n picklist={picklist}\n onClose={() => onAddingChange(false)}\n onAdd={async (added) => {\n const ok = await save(dealContactRolesAdded(roles, added), 'Contact added')\n if (ok) onAddingChange(false)\n return ok\n }}\n />\n </>\n )\n}\nContactRolesEditor.displayName = 'ContactRolesEditor'\n\ninterface AddContactRoleDialogProps {\n noun: ContactRolesNoun\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 { noun, 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 record is always its Primary; later ones when ticked.\n const [primary, setPrimary] = useState(false)\n const problem = contactId ? dealContactRoleAddProblem(roles, contactId, noun) : 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 ${noun}`}</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"],"names":["dealContactRolesWithout","dealContactRolesWithPrimary","mdiAccountPlusOutline","mdiAccountRemoveOutline","AppLink","CardDisplay","MdiIcon","EmptyStateComponent","Box","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","IconButton","Stack","Tooltip","Typography","useState","useCrmScope","dealContactRoleAddProblem","dealContactRolesAdded","dealContactRolesWithRole","CrmRecordPicker","CrmPicklistSelect","ContactRolesEditor","props","noun","roles","busy","adding","onAddingChange","ready","hostId","org","routes","picklist","nameOf","help","emptyDescription","save","addButton","variant","size","startIcon","path","disabled","onClick","header","HeaderProps","action","contentGutterX","contentGutterY","length","compact","label","description","spacing","map","row","name","contactId","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","ok","displayName","scope","setContactId","setRole","setPrimary","problem","reset","close","undefined","fullWidth","maxWidth","pt","kind","readTokens","visibleTo","groupId","consentGroup","helperText","control","checked","event","target","Boolean"],"mappings":"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"}
@@ -702,9 +702,9 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
702
702
  setCreateError(null);
703
703
  try {
704
704
  var _payload_contactId;
705
- const { response, payload } = await crmApi('contacts-create', _extends({
705
+ const { response, payload } = await crmApi('contacts-create', _extends({}, values.email ? {
706
706
  email: values.email
707
- }, values.name ? {
707
+ } : {}, values.name ? {
708
708
  name: values.name
709
709
  } : {}, values.salutation ? {
710
710
  salutation: values.salutation
@@ -798,7 +798,8 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
798
798
  resource: CRM_CONTACTS_RESOURCE,
799
799
  noun: "contacts",
800
800
  hostId: hostId,
801
- mappingZone: "contacts"
801
+ mappingZone: "contacts",
802
+ siteOptional: !crmScope.createNeedsSite
802
803
  }) : /*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
803
804
  children: 'Import'
804
805
  }),
@@ -932,7 +933,8 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
932
933
  resource: CRM_CONTACTS_RESOURCE,
933
934
  noun: "contacts",
934
935
  hostId: hostId,
935
- mappingZone: "contacts"
936
+ mappingZone: "contacts",
937
+ siteOptional: !crmScope.createNeedsSite
936
938
  })
937
939
  ]
938
940
  })