@aglyn/plugins-crm 1.0.0-beta.200 → 1.0.0-beta.202
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 +13 -13
- package/src/lib/components/activity-list.d.ts +36 -5
- package/src/lib/components/activity-list.js +159 -137
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/activity-timeline.d.ts +84 -0
- package/src/lib/components/activity-timeline.js +224 -0
- package/src/lib/components/activity-timeline.js.map +1 -0
- package/src/lib/components/company-properties-card.js +53 -36
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-associations-card.d.ts +3 -2
- package/src/lib/components/contact-associations-card.js +12 -12
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.d.ts +4 -3
- package/src/lib/components/contact-detail-page.js +4 -10
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +21 -4
- package/src/lib/components/contact-properties-card.js +129 -29
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-timeline-card.d.ts +12 -6
- package/src/lib/components/contact-timeline-card.js +132 -174
- package/src/lib/components/contact-timeline-card.js.map +1 -1
- package/src/lib/components/contacts-section.js +197 -198
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-email-state-chip.js +4 -1
- package/src/lib/components/crm-email-state-chip.js.map +1 -1
- package/src/lib/components/deal-products-card.js +15 -13
- package/src/lib/components/deal-products-card.js.map +1 -1
- package/src/lib/components/deal-properties-card.js +7 -0
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.js +31 -29
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-section.js +9 -14
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.d.ts +4 -0
- package/src/lib/components/fields-section.js +368 -154
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/form-contact-fields-card.js +24 -20
- package/src/lib/components/form-contact-fields-card.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.d.ts +1 -1
- package/src/lib/components/lead-campaigns-card.js +32 -35
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-properties-card.d.ts +14 -6
- package/src/lib/components/lead-properties-card.js +317 -313
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-surfaces-note.js +13 -2
- package/src/lib/components/lead-surfaces-note.js.map +1 -1
- package/src/lib/components/linked-deals-card.js +12 -10
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/recent-activity-feed.d.ts +12 -8
- package/src/lib/components/recent-activity-feed.js +54 -29
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/record-activity-card.d.ts +4 -2
- package/src/lib/components/record-activity-card.js +29 -11
- package/src/lib/components/record-activity-card.js.map +1 -1
- package/src/lib/components/reports/activity-card.js +18 -12
- package/src/lib/components/reports/activity-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.js +47 -37
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/pipeline-card.js +22 -16
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-export.d.ts +4 -11
- package/src/lib/components/reports/report-export.js +16 -33
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports/source-conversion-card.js +18 -12
- package/src/lib/components/reports/source-conversion-card.js.map +1 -1
- package/src/lib/components/reports/tasks-card.js +35 -25
- package/src/lib/components/reports/tasks-card.js.map +1 -1
- package/src/lib/components/tasks-section.js +23 -23
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/field-list-query.d.ts +23 -0
- package/src/lib/constants/field-list-query.js +80 -0
- package/src/lib/constants/field-list-query.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +1 -1
- package/src/lib/server/capture-contact.js +7 -5
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/components/contact-custom-fields-card.d.ts +0 -39
- package/src/lib/components/contact-custom-fields-card.js +0 -204
- package/src/lib/components/contact-custom-fields-card.js.map +0 -1
|
@@ -18,7 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import * as Aglyn from "@aglyn/aglyn";
|
|
20
20
|
import { mdiAccountCancelOutline } from "@aglyn/shared-data-mdi";
|
|
21
|
-
import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
22
22
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
23
23
|
import { useFirestore, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
24
24
|
import { Alert, Button, FormControl, InputLabel, MenuItem, Select, Stack, TextField, Tooltip, Typography } from "@mui/material";
|
|
@@ -89,15 +89,23 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
89
89
|
* What the team knows and decides about a lead: status, owner, notes, and
|
|
90
90
|
* the identity and consent the capture recorded (AGL-2608).
|
|
91
91
|
*
|
|
92
|
+
* Two cards. The record's header carries the identity, the status and owner,
|
|
93
|
+
* the consent the capture recorded and the record's actions. **Details**
|
|
94
|
+
* under it carries the profile, the org's own lead fields under **More
|
|
95
|
+
* fields**, and the notes — one card with one Save in its header, the way
|
|
96
|
+
* Salesforce keeps a record's standard and custom fields in one Details
|
|
97
|
+
* section.
|
|
98
|
+
*
|
|
92
99
|
* Status and owner are single-field client writes — the rules let a site
|
|
93
100
|
* admin, editor or author update `hosts/{hostId}/leads`, and a one-field
|
|
94
|
-
* `update` cannot roll anything else back.
|
|
95
|
-
* from the document, so
|
|
96
|
-
* edited over a cached read would otherwise overwrite a newer note
|
|
97
|
-
*
|
|
101
|
+
* `update` cannot roll anything else back. The details are drafts seeded
|
|
102
|
+
* from the document, so their save goes through `writeGuardedBySeed`: a
|
|
103
|
+
* draft edited over a cached read would otherwise overwrite a newer note or
|
|
104
|
+
* profile with an older one.
|
|
98
105
|
*
|
|
99
|
-
* Converted leads are read-only here. Their status is the
|
|
100
|
-
* the actions become links to what the conversion
|
|
106
|
+
* Converted leads are read-only here, but for the notes. Their status is the
|
|
107
|
+
* conversion, and the header's actions become links to what the conversion
|
|
108
|
+
* made.
|
|
101
109
|
*/ export function LeadPropertiesCard(props) {
|
|
102
110
|
var _lead_phone, _lead_notes, _lead_email, _lead_email1, _lead_name, _lead_leadSource;
|
|
103
111
|
const { hostId, consentGroup, orgId, leadId, lead, leadStatus, fromCache, basePath, roster, onConvert, onUnqualify, extraMenuItems = [], banner, extraChips, erasurePending = false, org } = props;
|
|
@@ -121,16 +129,16 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
121
129
|
// after the status it shows — see `LeadOwnerSelect`.
|
|
122
130
|
const statusLabelId = useId();
|
|
123
131
|
const [notesDirty, setNotesDirty] = useState(false);
|
|
124
|
-
const [savingNotes, setSavingNotes] = useState(false);
|
|
125
132
|
/*
|
|
126
133
|
* THE LEAD'S OWN PROFILE (AGL-3231) — company, title, phone, website,
|
|
127
|
-
* address, tags, lead source — edited
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* not overwrite a newer profile with an
|
|
134
|
+
* address, tags, lead source — edited in the Details card with the notes
|
|
135
|
+
* and the custom fields, under one Save, the way the contact's Properties
|
|
136
|
+
* card saves. Seeded from the document and guarded on save: a draft
|
|
137
|
+
* edited over a cached read must not overwrite a newer profile with an
|
|
138
|
+
* older one.
|
|
131
139
|
*/ const [profile, setProfile] = useState(()=>profileDraftFrom(lead));
|
|
132
140
|
const [profileDirty, setProfileDirty] = useState(false);
|
|
133
|
-
const [
|
|
141
|
+
const [saving, setSaving] = useState(false);
|
|
134
142
|
const [profileErrors, setProfileErrors] = useState({});
|
|
135
143
|
useEffect(()=>{
|
|
136
144
|
if (!profileDirty) setProfile(profileDraftFrom(lead));
|
|
@@ -180,7 +188,7 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
180
188
|
lead.notes,
|
|
181
189
|
notesDirty
|
|
182
190
|
]);
|
|
183
|
-
const write = async (fields, done)=>{
|
|
191
|
+
/** One update of the lead, and whether it landed. */ const write = async (fields, done)=>{
|
|
184
192
|
try {
|
|
185
193
|
const ref = refFor();
|
|
186
194
|
if (!ref) {
|
|
@@ -188,7 +196,7 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
188
196
|
variant: 'warning',
|
|
189
197
|
persist: false
|
|
190
198
|
});
|
|
191
|
-
return;
|
|
199
|
+
return false;
|
|
192
200
|
}
|
|
193
201
|
await updateDoc(ref, _extends({}, fields, {
|
|
194
202
|
updatedAt: serverTimestamp()
|
|
@@ -197,70 +205,76 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
197
205
|
variant: 'success',
|
|
198
206
|
persist: false
|
|
199
207
|
});
|
|
208
|
+
return true;
|
|
200
209
|
} catch (error) {
|
|
201
210
|
enqueueSnackbar(error instanceof Error ? error.message : 'The lead could not be updated.', {
|
|
202
211
|
variant: 'error'
|
|
203
212
|
});
|
|
213
|
+
return false;
|
|
204
214
|
}
|
|
205
215
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
216
|
+
/*
|
|
217
|
+
* What Save is offered for: a profile field edited, a custom value that
|
|
218
|
+
* differs from the stored map, or the notes. Touched-and-put-back does
|
|
219
|
+
* not count for a custom value — the draft keeps the key either way, and
|
|
220
|
+
* a Save that wrote nothing would still bump `updatedAt`.
|
|
221
|
+
*/ const customChanged = crmCustomDraftChanges(storedCustom, custom).length > 0;
|
|
222
|
+
const dirty = profileDirty || customChanged || notesDirty;
|
|
223
|
+
/** Every draft back to what the document holds. */ const discard = ()=>{
|
|
224
|
+
var _lead_notes;
|
|
225
|
+
setProfile(profileDraftFrom(lead));
|
|
226
|
+
setProfileDirty(false);
|
|
227
|
+
setProfileErrors({});
|
|
228
|
+
setCustom({});
|
|
229
|
+
setNotes(String((_lead_notes = lead.notes) != null ? _lead_notes : ''));
|
|
223
230
|
setNotesDirty(false);
|
|
224
231
|
};
|
|
225
232
|
/*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
241
|
-
setProfileErrors(errors);
|
|
242
|
-
if (Object.keys(errors).length) return;
|
|
243
|
-
/*
|
|
244
|
-
* A required field the reader CLEARED is refused; one the lead has
|
|
245
|
-
* always lacked is not this save's to demand, or a field added after
|
|
246
|
-
* the lead was captured would block every later edit to its company.
|
|
247
|
-
*/ const missing = crmCustomDraftMissingRequired(fields.active, storedCustom, custom, 'edit');
|
|
248
|
-
if (missing.length) {
|
|
249
|
-
enqueueSnackbar(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`, {
|
|
250
|
-
variant: 'warning'
|
|
233
|
+
* The Details card's one Save: the profile and the custom values while
|
|
234
|
+
* the lead is open, and the notes, in one update. A converted lead's
|
|
235
|
+
* profile is the contact's now, so only its notes are written.
|
|
236
|
+
*/ const saveDetails = async ()=>{
|
|
237
|
+
const update = {};
|
|
238
|
+
if (!converted && (profileDirty || customChanged)) {
|
|
239
|
+
const { patch, errors } = Aglyn.normalizeCrmLeadProfile({
|
|
240
|
+
company: profile.company,
|
|
241
|
+
jobTitle: profile.jobTitle,
|
|
242
|
+
phone: profile.phone,
|
|
243
|
+
website: profile.website,
|
|
244
|
+
leadSource: profile.leadSource,
|
|
245
|
+
tags: profile.tags,
|
|
246
|
+
address: profile.address
|
|
251
247
|
});
|
|
252
|
-
|
|
248
|
+
setProfileErrors(errors);
|
|
249
|
+
if (Object.keys(errors).length) return;
|
|
250
|
+
/*
|
|
251
|
+
* A required field the reader CLEARED is refused; one the lead has
|
|
252
|
+
* always lacked is not this save's to demand, or a field added after
|
|
253
|
+
* the lead was captured would block every later edit to its company.
|
|
254
|
+
*/ const missing = crmCustomDraftMissingRequired(fields.active, storedCustom, custom, 'edit');
|
|
255
|
+
if (missing.length) {
|
|
256
|
+
enqueueSnackbar(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`, {
|
|
257
|
+
variant: 'warning'
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const profileFields = profileWrite(patch);
|
|
262
|
+
Object.assign(update, profileFields, crmCustomDraftWrites(storedCustom, custom), // The company, title, tags and lead source are what the Leads list
|
|
263
|
+
// searches and filters by (AGL-3321).
|
|
264
|
+
crmClientListFields('leads', lead, profileFields, LEAD_CLIENT_LIST_FIELDS));
|
|
253
265
|
}
|
|
254
|
-
|
|
266
|
+
if (notesDirty) update['notes'] = notes.trim().slice(0, NOTES_MAX);
|
|
267
|
+
if (!Object.keys(update).length) return;
|
|
268
|
+
setSaving(true);
|
|
269
|
+
let landed = false;
|
|
255
270
|
const verdict = await writeGuardedBySeed({
|
|
256
271
|
subject: 'lead',
|
|
257
272
|
fromCache,
|
|
258
273
|
unreadable: leadStatus === 'error'
|
|
259
|
-
}, ()=>{
|
|
260
|
-
|
|
261
|
-
return write(_extends({}, profileFields, crmCustomDraftWrites(storedCustom, custom), crmClientListFields('leads', lead, profileFields, LEAD_CLIENT_LIST_FIELDS)), 'Lead saved');
|
|
274
|
+
}, async ()=>{
|
|
275
|
+
landed = await write(update, 'Lead saved');
|
|
262
276
|
});
|
|
263
|
-
|
|
277
|
+
setSaving(false);
|
|
264
278
|
if (!verdict.ok) {
|
|
265
279
|
var _verdict_message;
|
|
266
280
|
enqueueSnackbar((_verdict_message = verdict.message) != null ? _verdict_message : 'The lead could not be saved.', {
|
|
@@ -268,247 +282,271 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
268
282
|
});
|
|
269
283
|
return;
|
|
270
284
|
}
|
|
285
|
+
if (!landed) return;
|
|
271
286
|
setProfileDirty(false);
|
|
287
|
+
setNotesDirty(false);
|
|
272
288
|
// The draft is spent: what it held is stored, and the controls read
|
|
273
289
|
// the document again.
|
|
274
290
|
setCustom({});
|
|
275
291
|
};
|
|
276
292
|
const consent = Aglyn.readMarketingBasis(lead, consentGroup);
|
|
277
293
|
const consentLine = consent.basis === 'granted' ? `Opted in to marketing${consent.basisAtMs ? ` on ${new Date(consent.basisAtMs).toLocaleDateString()}` : ''}` : consent.basis === 'declined' ? 'Declined marketing' : 'No marketing consent recorded — this lead cannot be emailed marketing';
|
|
278
|
-
return /*#__PURE__*/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
294
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
295
|
+
children: [
|
|
296
|
+
/*#__PURE__*/ _jsx(CrmRecordHeader, {
|
|
297
|
+
kind: "Lead",
|
|
298
|
+
title: String(lead['name'] || lead['email'] || leadId),
|
|
299
|
+
// The name is the heading; the address is the one line under it,
|
|
300
|
+
// unless the address IS the name, in which case there is no second fact.
|
|
301
|
+
subtitle: lead['name'] ? String((_lead_email = lead['email']) != null ? _lead_email : '') : undefined,
|
|
302
|
+
help: Aglyn.pluginDocsHelp('crmLeads', {
|
|
303
|
+
anchor: '#working-a-lead-from-the-row'
|
|
304
|
+
}),
|
|
305
|
+
backHref: routes.section('leads'),
|
|
306
|
+
backLabel: "Back to leads",
|
|
307
|
+
// The booking door (AGL-2660), while the lead is still the record
|
|
308
|
+
// being worked: once converted, the contact is where a meeting is
|
|
309
|
+
// booked from, and the links below lead there.
|
|
310
|
+
booking: converted ? undefined : {
|
|
311
|
+
hostId,
|
|
312
|
+
org,
|
|
313
|
+
kind: 'lead',
|
|
314
|
+
recordId: leadId
|
|
315
|
+
},
|
|
316
|
+
actions: /*#__PURE__*/ _jsxs(_Fragment, {
|
|
317
|
+
children: [
|
|
318
|
+
converted ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
319
|
+
children: [
|
|
320
|
+
/*#__PURE__*/ _jsx(Button, _extends({
|
|
321
|
+
component: AppLink
|
|
322
|
+
}, {
|
|
323
|
+
componentVariant: 'naked',
|
|
324
|
+
nativeButton: false
|
|
325
|
+
}, {
|
|
326
|
+
href: routes.contact(String(lead.convertedContactId)),
|
|
327
|
+
size: "small",
|
|
328
|
+
variant: "outlined",
|
|
329
|
+
children: 'Open contact'
|
|
330
|
+
})),
|
|
331
|
+
lead.companyId ? /*#__PURE__*/ _jsx(Button, _extends({
|
|
332
|
+
component: AppLink
|
|
333
|
+
}, {
|
|
334
|
+
componentVariant: 'naked',
|
|
335
|
+
nativeButton: false
|
|
336
|
+
}, {
|
|
337
|
+
href: routes.company(lead.companyId),
|
|
338
|
+
size: "small",
|
|
339
|
+
variant: "outlined",
|
|
340
|
+
children: 'Open company'
|
|
341
|
+
})) : null,
|
|
342
|
+
lead.dealId ? /*#__PURE__*/ _jsx(Button, _extends({
|
|
343
|
+
component: AppLink
|
|
344
|
+
}, {
|
|
345
|
+
componentVariant: 'naked',
|
|
346
|
+
nativeButton: false
|
|
347
|
+
}, {
|
|
348
|
+
href: routes.deal(lead.dealId),
|
|
349
|
+
size: "small",
|
|
350
|
+
variant: "outlined",
|
|
351
|
+
children: 'Open deal'
|
|
352
|
+
})) : null
|
|
353
|
+
]
|
|
354
|
+
}) : erasurePending ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
355
|
+
title: CONVERT_PENDING_ERASURE_REASON,
|
|
356
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
357
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
358
|
+
size: "small",
|
|
359
|
+
variant: "contained",
|
|
360
|
+
disabled: true,
|
|
361
|
+
children: 'Convert'
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
304
365
|
size: "small",
|
|
305
366
|
variant: "contained",
|
|
306
|
-
|
|
367
|
+
onClick: onConvert,
|
|
307
368
|
children: 'Convert'
|
|
369
|
+
}),
|
|
370
|
+
/*#__PURE__*/ _jsx(CrmCallButton, {
|
|
371
|
+
hostId: hostId,
|
|
372
|
+
org: org,
|
|
373
|
+
link: {
|
|
374
|
+
leadId
|
|
375
|
+
},
|
|
376
|
+
phone: leadPhone
|
|
377
|
+
}),
|
|
378
|
+
/*#__PURE__*/ _jsx(CrmSendEmailButton, {
|
|
379
|
+
hostId: hostId,
|
|
380
|
+
leadId: leadId,
|
|
381
|
+
email: String((_lead_email1 = lead['email']) != null ? _lead_email1 : ''),
|
|
382
|
+
name: String((_lead_name = lead['name']) != null ? _lead_name : ''),
|
|
383
|
+
emailState: emailState
|
|
308
384
|
})
|
|
309
|
-
|
|
310
|
-
}) : /*#__PURE__*/ _jsx(Button, {
|
|
311
|
-
size: "small",
|
|
312
|
-
variant: "contained",
|
|
313
|
-
onClick: onConvert,
|
|
314
|
-
children: 'Convert'
|
|
315
|
-
}),
|
|
316
|
-
/*#__PURE__*/ _jsx(CrmCallButton, {
|
|
317
|
-
hostId: hostId,
|
|
318
|
-
org: org,
|
|
319
|
-
link: {
|
|
320
|
-
leadId
|
|
321
|
-
},
|
|
322
|
-
phone: leadPhone
|
|
323
|
-
}),
|
|
324
|
-
/*#__PURE__*/ _jsx(CrmSendEmailButton, {
|
|
325
|
-
hostId: hostId,
|
|
326
|
-
leadId: leadId,
|
|
327
|
-
email: String((_lead_email1 = lead['email']) != null ? _lead_email1 : ''),
|
|
328
|
-
name: String((_lead_name = lead['name']) != null ? _lead_name : ''),
|
|
329
|
-
emailState: emailState
|
|
330
|
-
})
|
|
331
|
-
]
|
|
332
|
-
}),
|
|
333
|
-
menuItems: [
|
|
334
|
-
...open ? [
|
|
335
|
-
{
|
|
336
|
-
key: 'unqualify',
|
|
337
|
-
label: 'Unqualify',
|
|
338
|
-
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
339
|
-
path: mdiAccountCancelOutline.path,
|
|
340
|
-
size: 0.8
|
|
341
|
-
}),
|
|
342
|
-
destructive: true,
|
|
343
|
-
onClick: onUnqualify
|
|
344
|
-
}
|
|
345
|
-
] : [],
|
|
346
|
-
...extraMenuItems
|
|
347
|
-
],
|
|
348
|
-
chips: /*#__PURE__*/ _jsxs(_Fragment, {
|
|
349
|
-
children: [
|
|
350
|
-
/*#__PURE__*/ _jsx(LeadStatusChip, {
|
|
351
|
-
lead: lead
|
|
352
|
-
}),
|
|
353
|
-
/*#__PURE__*/ _jsx(CrmEmailStateChip, {
|
|
354
|
-
state: emailState
|
|
355
|
-
}),
|
|
356
|
-
/*#__PURE__*/ _jsx(CrmRecordChip, {
|
|
357
|
-
label: "Owner",
|
|
358
|
-
value: lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined
|
|
385
|
+
]
|
|
359
386
|
}),
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
387
|
+
menuItems: [
|
|
388
|
+
...open ? [
|
|
389
|
+
{
|
|
390
|
+
key: 'unqualify',
|
|
391
|
+
label: 'Unqualify',
|
|
392
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
393
|
+
path: mdiAccountCancelOutline.path,
|
|
394
|
+
size: 0.8
|
|
395
|
+
}),
|
|
396
|
+
destructive: true,
|
|
397
|
+
onClick: onUnqualify
|
|
398
|
+
}
|
|
399
|
+
] : [],
|
|
400
|
+
...extraMenuItems
|
|
401
|
+
],
|
|
402
|
+
chips: /*#__PURE__*/ _jsxs(_Fragment, {
|
|
403
|
+
children: [
|
|
404
|
+
/*#__PURE__*/ _jsx(LeadStatusChip, {
|
|
405
|
+
lead: lead
|
|
406
|
+
}),
|
|
407
|
+
/*#__PURE__*/ _jsx(CrmEmailStateChip, {
|
|
408
|
+
state: emailState
|
|
409
|
+
}),
|
|
410
|
+
/*#__PURE__*/ _jsx(CrmRecordChip, {
|
|
411
|
+
label: "Owner",
|
|
412
|
+
value: lead.ownerUid ? roster.labelFor(lead.ownerUid) : undefined
|
|
413
|
+
}),
|
|
414
|
+
extraChips
|
|
415
|
+
]
|
|
376
416
|
}),
|
|
377
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
378
|
-
|
|
379
|
-
xs: 'column',
|
|
380
|
-
md: 'row'
|
|
381
|
-
},
|
|
382
|
-
spacing: 2,
|
|
417
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
418
|
+
spacing: 3,
|
|
383
419
|
children: [
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
sx: {
|
|
390
|
-
alignItems: 'center'
|
|
391
|
-
},
|
|
392
|
-
children: [
|
|
393
|
-
/*#__PURE__*/ _jsx(LeadStatusChip, {
|
|
394
|
-
lead: lead
|
|
395
|
-
}),
|
|
396
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
397
|
-
variant: "body2",
|
|
398
|
-
color: "text.secondary",
|
|
399
|
-
children: lead.convertedAtMs ? `Converted ${new Date(lead.convertedAtMs).toLocaleString()}` : 'Converted'
|
|
400
|
-
})
|
|
401
|
-
]
|
|
420
|
+
banner,
|
|
421
|
+
leadPhone ? /*#__PURE__*/ _jsx(Fact, {
|
|
422
|
+
label: "Phone",
|
|
423
|
+
children: /*#__PURE__*/ _jsx(CrmPhoneLink, {
|
|
424
|
+
phone: leadPhone
|
|
402
425
|
})
|
|
403
|
-
}) :
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
426
|
+
}) : null,
|
|
427
|
+
/*#__PURE__*/ _jsx(Fact, {
|
|
428
|
+
label: "Marketing consent",
|
|
429
|
+
children: consentLine
|
|
430
|
+
}),
|
|
431
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
432
|
+
direction: {
|
|
433
|
+
xs: 'column',
|
|
434
|
+
md: 'row'
|
|
407
435
|
},
|
|
436
|
+
spacing: 2,
|
|
408
437
|
children: [
|
|
409
|
-
/*#__PURE__*/ _jsx(
|
|
410
|
-
id: statusLabelId,
|
|
411
|
-
children: 'Status'
|
|
412
|
-
}),
|
|
413
|
-
/*#__PURE__*/ _jsxs(Select, {
|
|
414
|
-
labelId: statusLabelId,
|
|
438
|
+
converted ? /*#__PURE__*/ _jsx(Fact, {
|
|
415
439
|
label: "Status",
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
440
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
441
|
+
direction: "row",
|
|
442
|
+
spacing: 1,
|
|
443
|
+
sx: {
|
|
444
|
+
alignItems: 'center'
|
|
445
|
+
},
|
|
446
|
+
children: [
|
|
447
|
+
/*#__PURE__*/ _jsx(LeadStatusChip, {
|
|
448
|
+
lead: lead
|
|
449
|
+
}),
|
|
450
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
451
|
+
variant: "body2",
|
|
452
|
+
color: "text.secondary",
|
|
453
|
+
children: lead.convertedAtMs ? `Converted ${new Date(lead.convertedAtMs).toLocaleString()}` : 'Converted'
|
|
454
|
+
})
|
|
455
|
+
]
|
|
456
|
+
})
|
|
457
|
+
}) : /*#__PURE__*/ _jsxs(FormControl, {
|
|
458
|
+
size: "small",
|
|
459
|
+
sx: {
|
|
460
|
+
minWidth: 200
|
|
430
461
|
},
|
|
431
462
|
children: [
|
|
432
|
-
/*#__PURE__*/ _jsx(
|
|
433
|
-
|
|
434
|
-
children:
|
|
435
|
-
}),
|
|
436
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
437
|
-
value: "working",
|
|
438
|
-
children: Aglyn.CRM_LEAD_STATUS_LABELS.working
|
|
463
|
+
/*#__PURE__*/ _jsx(InputLabel, {
|
|
464
|
+
id: statusLabelId,
|
|
465
|
+
children: 'Status'
|
|
439
466
|
}),
|
|
440
|
-
/*#__PURE__*/
|
|
441
|
-
|
|
442
|
-
|
|
467
|
+
/*#__PURE__*/ _jsxs(Select, {
|
|
468
|
+
labelId: statusLabelId,
|
|
469
|
+
label: "Status",
|
|
470
|
+
value: status === 'unqualified' ? 'unqualified' : status,
|
|
471
|
+
onChange: (event)=>{
|
|
472
|
+
const next = String(event.target.value);
|
|
473
|
+
if (next === 'unqualified') {
|
|
474
|
+
onUnqualify();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
// Reopening drops the reason with the closed state: a lead
|
|
478
|
+
// being worked again is not "unqualified because …".
|
|
479
|
+
void write(_extends({
|
|
480
|
+
status: next
|
|
481
|
+
}, status === 'unqualified' ? {
|
|
482
|
+
unqualifiedReason: deleteField()
|
|
483
|
+
} : {}), 'Status updated');
|
|
484
|
+
},
|
|
485
|
+
children: [
|
|
486
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
487
|
+
value: "new",
|
|
488
|
+
children: Aglyn.CRM_LEAD_STATUS_LABELS.new
|
|
489
|
+
}),
|
|
490
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
491
|
+
value: "working",
|
|
492
|
+
children: Aglyn.CRM_LEAD_STATUS_LABELS.working
|
|
493
|
+
}),
|
|
494
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
495
|
+
value: "unqualified",
|
|
496
|
+
children: `${Aglyn.CRM_LEAD_STATUS_LABELS.unqualified}…`
|
|
497
|
+
})
|
|
498
|
+
]
|
|
443
499
|
})
|
|
444
500
|
]
|
|
501
|
+
}),
|
|
502
|
+
/*#__PURE__*/ _jsx(LeadOwnerSelect, {
|
|
503
|
+
value: lead.ownerUid,
|
|
504
|
+
roster: roster,
|
|
505
|
+
fullWidth: false,
|
|
506
|
+
onChange: (uid)=>void write({
|
|
507
|
+
ownerUid: uid || deleteField()
|
|
508
|
+
}, uid ? 'Owner assigned' : 'Owner cleared')
|
|
445
509
|
})
|
|
446
510
|
]
|
|
447
511
|
}),
|
|
448
|
-
/*#__PURE__*/ _jsx(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
onChange: (uid)=>void write({
|
|
453
|
-
ownerUid: uid || deleteField()
|
|
454
|
-
}, uid ? 'Owner assigned' : 'Owner cleared')
|
|
455
|
-
})
|
|
512
|
+
status === 'unqualified' && lead.unqualifiedReason ? /*#__PURE__*/ _jsx(Alert, {
|
|
513
|
+
severity: "info",
|
|
514
|
+
children: `Unqualified: ${lead.unqualifiedReason}`
|
|
515
|
+
}) : null
|
|
456
516
|
]
|
|
517
|
+
})
|
|
518
|
+
}),
|
|
519
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
520
|
+
header: 'Details',
|
|
521
|
+
help: Aglyn.pluginDocsHelp('crmLeads', {
|
|
522
|
+
anchor: '#a-leads-page'
|
|
457
523
|
}),
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
component: AppLink
|
|
483
|
-
}, {
|
|
484
|
-
componentVariant: 'naked',
|
|
485
|
-
nativeButton: false
|
|
486
|
-
}, {
|
|
487
|
-
href: routes.company(lead.companyId),
|
|
488
|
-
size: "small",
|
|
489
|
-
variant: "outlined",
|
|
490
|
-
children: 'Open company'
|
|
491
|
-
})) : null,
|
|
492
|
-
lead.dealId ? /*#__PURE__*/ _jsx(Button, _extends({
|
|
493
|
-
component: AppLink
|
|
494
|
-
}, {
|
|
495
|
-
componentVariant: 'naked',
|
|
496
|
-
nativeButton: false
|
|
497
|
-
}, {
|
|
498
|
-
href: routes.deal(lead.dealId),
|
|
499
|
-
size: "small",
|
|
500
|
-
variant: "outlined",
|
|
501
|
-
children: 'Open deal'
|
|
502
|
-
})) : null
|
|
503
|
-
]
|
|
504
|
-
}) : null,
|
|
505
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
524
|
+
contentGutterX: true,
|
|
525
|
+
contentGutterY: true,
|
|
526
|
+
HeaderProps: {
|
|
527
|
+
action: /*#__PURE__*/ _jsxs(Stack, {
|
|
528
|
+
direction: "row",
|
|
529
|
+
spacing: 1,
|
|
530
|
+
children: [
|
|
531
|
+
dirty ? /*#__PURE__*/ _jsx(Button, {
|
|
532
|
+
size: "small",
|
|
533
|
+
disabled: saving,
|
|
534
|
+
onClick: discard,
|
|
535
|
+
children: 'Discard changes'
|
|
536
|
+
}) : null,
|
|
537
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
538
|
+
size: "small",
|
|
539
|
+
variant: "contained",
|
|
540
|
+
onClick: ()=>void saveDetails(),
|
|
541
|
+
disabled: !dirty || saving,
|
|
542
|
+
children: saving ? 'Saving…' : 'Save'
|
|
543
|
+
})
|
|
544
|
+
]
|
|
545
|
+
})
|
|
546
|
+
},
|
|
547
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
506
548
|
spacing: 2,
|
|
507
549
|
children: [
|
|
508
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
509
|
-
variant: "subtitle2",
|
|
510
|
-
children: 'Profile'
|
|
511
|
-
}),
|
|
512
550
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
513
551
|
direction: {
|
|
514
552
|
xs: 'column',
|
|
@@ -611,9 +649,8 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
611
649
|
fields.active.length ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
612
650
|
children: [
|
|
613
651
|
/*#__PURE__*/ _jsx(Typography, {
|
|
614
|
-
variant: "
|
|
615
|
-
|
|
616
|
-
children: 'Custom fields'
|
|
652
|
+
variant: "subtitle2",
|
|
653
|
+
children: 'More fields'
|
|
617
654
|
}),
|
|
618
655
|
fields.active.map((definition)=>/*#__PURE__*/ _jsx(CrmCustomFieldControl, {
|
|
619
656
|
definition: definition,
|
|
@@ -621,29 +658,10 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
621
658
|
onChange: (value)=>setCustom((current)=>_extends({}, current, {
|
|
622
659
|
[definition.key]: value
|
|
623
660
|
})),
|
|
624
|
-
disabled: converted ||
|
|
661
|
+
disabled: converted || saving
|
|
625
662
|
}, definition.$id))
|
|
626
663
|
]
|
|
627
664
|
}) : null,
|
|
628
|
-
converted ? null : /*#__PURE__*/ _jsx(Stack, {
|
|
629
|
-
direction: "row",
|
|
630
|
-
spacing: 1,
|
|
631
|
-
sx: {
|
|
632
|
-
justifyContent: 'flex-end'
|
|
633
|
-
},
|
|
634
|
-
children: /*#__PURE__*/ _jsx(Button, {
|
|
635
|
-
size: "small",
|
|
636
|
-
variant: "contained",
|
|
637
|
-
onClick: ()=>void saveProfile(),
|
|
638
|
-
disabled: !dirty || savingProfile,
|
|
639
|
-
children: 'Save'
|
|
640
|
-
})
|
|
641
|
-
})
|
|
642
|
-
]
|
|
643
|
-
}),
|
|
644
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
645
|
-
spacing: 1,
|
|
646
|
-
children: [
|
|
647
665
|
/*#__PURE__*/ _jsx(TextField, {
|
|
648
666
|
size: "small",
|
|
649
667
|
label: "Notes",
|
|
@@ -660,25 +678,11 @@ const TEXT_MAX = Aglyn.CRM_LEAD_TEXT_MAX;
|
|
|
660
678
|
maxLength: NOTES_MAX
|
|
661
679
|
}
|
|
662
680
|
}
|
|
663
|
-
}),
|
|
664
|
-
/*#__PURE__*/ _jsx(Stack, {
|
|
665
|
-
direction: "row",
|
|
666
|
-
spacing: 1,
|
|
667
|
-
sx: {
|
|
668
|
-
justifyContent: 'flex-end'
|
|
669
|
-
},
|
|
670
|
-
children: /*#__PURE__*/ _jsx(Button, {
|
|
671
|
-
size: "small",
|
|
672
|
-
variant: "contained",
|
|
673
|
-
onClick: ()=>void saveNotes(),
|
|
674
|
-
disabled: !notesDirty || savingNotes,
|
|
675
|
-
children: 'Save notes'
|
|
676
|
-
})
|
|
677
681
|
})
|
|
678
682
|
]
|
|
679
683
|
})
|
|
680
|
-
|
|
681
|
-
|
|
684
|
+
})
|
|
685
|
+
]
|
|
682
686
|
});
|
|
683
687
|
}
|
|
684
688
|
LeadPropertiesCard.displayName = 'LeadPropertiesCard';
|