@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.
- package/package.json +14 -14
- package/src/lib/components/activity-link-fields.d.ts +34 -0
- package/src/lib/components/activity-link-fields.js +141 -0
- package/src/lib/components/activity-link-fields.js.map +1 -0
- package/src/lib/components/activity-list.d.ts +8 -0
- package/src/lib/components/activity-list.js +4 -2
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/activity-queries.d.ts +7 -2
- package/src/lib/components/activity-queries.js +7 -3
- package/src/lib/components/activity-queries.js.map +1 -1
- package/src/lib/components/company-contact-roles-card.d.ts +42 -0
- package/src/lib/components/company-contact-roles-card.js +135 -0
- package/src/lib/components/company-contact-roles-card.js.map +1 -0
- package/src/lib/components/company-detail-page.js +12 -1
- package/src/lib/components/company-detail-page.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +9 -6
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-picker.js +4 -2
- package/src/lib/components/company-picker.js.map +1 -1
- package/src/lib/components/contact-associations-card.js +15 -1
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +13 -14
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.js +5 -4
- package/src/lib/components/contact-list-columns.js.map +1 -1
- package/src/lib/components/contact-properties-card.js +41 -12
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-roles-editor.d.ts +54 -0
- package/src/lib/components/contact-roles-editor.js +270 -0
- package/src/lib/components/contact-roles-editor.js.map +1 -0
- package/src/lib/components/contacts-section.js +6 -4
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-console-sections.js +1 -1
- package/src/lib/components/crm-console-sections.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +19 -0
- package/src/lib/components/crm-record-picker.js +40 -11
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-related-to-picker.d.ts +33 -0
- package/src/lib/components/crm-related-to-picker.js +135 -0
- package/src/lib/components/crm-related-to-picker.js.map +1 -0
- package/src/lib/components/crm-site-picker.d.ts +13 -1
- package/src/lib/components/crm-site-picker.js +15 -6
- package/src/lib/components/crm-site-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +5 -0
- package/src/lib/components/crm-transfer-buttons.js +9 -6
- package/src/lib/components/crm-transfer-buttons.js.map +1 -1
- package/src/lib/components/deal-contact-roles-card.js +21 -239
- package/src/lib/components/deal-contact-roles-card.js.map +1 -1
- package/src/lib/components/deal-detail-page.js +2 -1
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +11 -7
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-section.js +5 -4
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.js +26 -14
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.d.ts +27 -9
- package/src/lib/components/log-activity-dialog.js +96 -32
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +7 -3
- package/src/lib/components/new-contact-drawer.js +27 -15
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/picklist-values-card.js +7 -4
- package/src/lib/components/picklist-values-card.js.map +1 -1
- package/src/lib/components/pipeline-stages-dialog.js +5 -1
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/record-activity-card.d.ts +11 -2
- package/src/lib/components/record-activity-card.js +90 -5
- package/src/lib/components/record-activity-card.js.map +1 -1
- package/src/lib/components/settings-section.d.ts +4 -2
- package/src/lib/components/settings-section.js +7 -5
- package/src/lib/components/settings-section.js.map +1 -1
- package/src/lib/hooks/crm-activity-client-reads.d.ts +8 -0
- package/src/lib/hooks/crm-activity-client-reads.js +50 -0
- package/src/lib/hooks/crm-activity-client-reads.js.map +1 -0
- package/src/lib/hooks/use-campaign-filing-log.d.ts +2 -1
- package/src/lib/hooks/use-campaign-filing-log.js +10 -3
- package/src/lib/hooks/use-campaign-filing-log.js.map +1 -1
- package/src/lib/hooks/use-crm-org-mount.d.ts +8 -6
- package/src/lib/hooks/use-crm-org-mount.js +2 -1
- package/src/lib/hooks/use-crm-org-mount.js.map +1 -1
- package/src/lib/hooks/use-crm-scope.d.ts +17 -5
- package/src/lib/hooks/use-crm-scope.js +12 -5
- package/src/lib/hooks/use-crm-scope.js.map +1 -1
- package/src/lib/model/contact-holder.d.ts +12 -0
- package/src/lib/model/contact-holder.js +20 -1
- package/src/lib/model/contact-holder.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +5 -0
- package/src/lib/model/contact-record.js +7 -1
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +6 -0
- package/src/lib/model/contact-update.js +1 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-activity-link.d.ts +117 -0
- package/src/lib/model/crm-activity-link.js +113 -0
- package/src/lib/model/crm-activity-link.js.map +1 -0
- package/src/lib/model/crm-api.d.ts +4 -1
- package/src/lib/model/crm-api.js +9 -5
- package/src/lib/model/crm-api.js.map +1 -1
- package/src/lib/model/deal-board-model.js +5 -3
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +15 -1
- package/src/lib/model/deal-contact-roles.js +17 -3
- package/src/lib/model/deal-contact-roles.js.map +1 -1
- package/src/lib/model/settle-stage-names.d.ts +25 -0
- package/src/lib/model/settle-stage-names.js +29 -0
- package/src/lib/model/settle-stage-names.js.map +1 -0
- package/src/lib/native-nav.js +1 -1
- package/src/lib/native-nav.js.map +1 -1
- package/src/lib/server/add-contact-by-hand.d.ts +84 -0
- package/src/lib/server/add-contact-by-hand.js +212 -0
- package/src/lib/server/add-contact-by-hand.js.map +1 -0
- package/src/lib/server/api-v1/contacts.js +61 -23
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +13 -3
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +1 -1
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-shared.d.ts +16 -9
- package/src/lib/server/api-v1/crm-shared.js +23 -11
- package/src/lib/server/api-v1/crm-shared.js.map +1 -1
- package/src/lib/server/api-v1/openapi.js +3 -3
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/automation-steps.js +11 -9
- package/src/lib/server/automation-steps.js.map +1 -1
- package/src/lib/server/campaign-filing-activity.js +7 -2
- package/src/lib/server/campaign-filing-activity.js.map +1 -1
- package/src/lib/server/contact-update.d.ts +4 -0
- package/src/lib/server/contact-update.js +71 -10
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.js +13 -2
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +8 -1
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-activity-company.d.ts +25 -0
- package/src/lib/server/crm-activity-company.js +34 -0
- package/src/lib/server/crm-activity-company.js.map +1 -0
- package/src/lib/server/crm-inbound-email.js +21 -15
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-sharing.js +9 -2
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/email-send.js +10 -8
- package/src/lib/server/email-send.js.map +1 -1
- package/src/lib/server/merge-contacts.js +6 -2
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/org-caller.d.ts +8 -0
- package/src/lib/server/org-caller.js +10 -0
- package/src/lib/server/org-caller.js.map +1 -1
- package/src/lib/server/record-timeline.d.ts +3 -1
- package/src/lib/server/record-timeline.js +11 -3
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/task-reminders-route.d.ts +7 -5
- package/src/lib/server/task-reminders-route.js +82 -15
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server.js +152 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/transfer/contacts.js +83 -48
- package/src/lib/transfer/contacts.js.map +1 -1
- package/src/lib/transfer/fields.js +1 -2
- 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
|
})
|