@aglyn/plugins-crm 1.0.0-beta.199 → 1.0.0-beta.201
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/companies-section.d.ts +6 -6
- package/src/lib/components/companies-section.js +54 -84
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +17 -4
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-picker.js +6 -2
- package/src/lib/components/company-picker.js.map +1 -1
- 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 +18 -12
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.d.ts +3 -5
- package/src/lib/components/contact-list-columns.js +6 -8
- package/src/lib/components/contact-list-columns.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-bulk-bar.js +10 -1
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +282 -331
- 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/crm-next-activity-column.d.ts +4 -2
- package/src/lib/components/crm-next-activity-column.js +6 -3
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +8 -4
- package/src/lib/components/deal-edit-drawer.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.d.ts +2 -2
- package/src/lib/components/deals-section.js +67 -97
- 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 +43 -38
- 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 +323 -311
- 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/leads-bulk-bar.js +16 -3
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.d.ts +2 -1
- package/src/lib/components/leads-section.js +110 -148
- package/src/lib/components/leads-section.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 +151 -144
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +13 -0
- package/src/lib/constants/company-filters.js +47 -17
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +61 -10
- package/src/lib/constants/contact-filters.js +373 -141
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +44 -0
- package/src/lib/constants/deal-filters.js +78 -0
- package/src/lib/constants/deal-filters.js.map +1 -0
- 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/hooks/use-crm-list-query.d.ts +68 -0
- package/src/lib/hooks/use-crm-list-query.js +182 -0
- package/src/lib/hooks/use-crm-list-query.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +23 -6
- package/src/lib/hooks/use-crm-tasks.js +44 -42
- package/src/lib/hooks/use-crm-tasks.js.map +1 -1
- package/src/lib/hooks/use-deals.d.ts +2 -34
- package/src/lib/hooks/use-deals.js +3 -62
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +91 -0
- package/src/lib/model/crm-list-query.js +182 -0
- package/src/lib/model/crm-list-query.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +50 -44
- package/src/lib/model/lead-filters.js +194 -88
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/list-fields-api.d.ts +26 -0
- package/src/lib/model/list-fields-api.js +47 -0
- package/src/lib/model/list-fields-api.js.map +1 -0
- package/src/lib/model/list-fields.d.ts +47 -0
- package/src/lib/model/list-fields.js +39 -0
- package/src/lib/model/list-fields.js.map +1 -0
- package/src/lib/model/task-views.d.ts +15 -10
- package/src/lib/model/task-views.js +68 -9
- package/src/lib/model/task-views.js.map +1 -1
- package/src/lib/server/capture-contact.js +7 -5
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/companies-import.js +3 -3
- package/src/lib/server/companies-import.js.map +1 -1
- package/src/lib/server/company-delete.js +3 -1
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/consent-group-participant.js +4 -0
- package/src/lib/server/consent-group-participant.js.map +1 -1
- package/src/lib/server/contact-update.js +14 -8
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/contacts-import.js +4 -2
- package/src/lib/server/contacts-import.js.map +1 -1
- package/src/lib/server/deals-import.js +4 -2
- package/src/lib/server/deals-import.js.map +1 -1
- package/src/lib/server/erase-person.js +2 -1
- package/src/lib/server/erase-person.js.map +1 -1
- package/src/lib/server/lead-create.js +4 -1
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-source-values.js +9 -5
- package/src/lib/server/lead-source-values.js.map +1 -1
- package/src/lib/server/leads-import.js +4 -1
- package/src/lib/server/leads-import.js.map +1 -1
- package/src/lib/server/list-fields-routes.d.ts +29 -0
- package/src/lib/server/list-fields-routes.js +85 -0
- package/src/lib/server/list-fields-routes.js.map +1 -0
- package/src/lib/server/record-email-state.js +5 -2
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-timeline.js +3 -2
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/task-routes.js +6 -3
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server/tasks-import.js +5 -2
- package/src/lib/server/tasks-import.js.map +1 -1
- package/src/lib/server.js +10 -1
- package/src/lib/server.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
- package/src/lib/hooks/use-org-leads.d.ts +0 -50
- package/src/lib/hooks/use-org-leads.js +0 -104
- package/src/lib/hooks/use-org-leads.js.map +0 -1
|
@@ -18,12 +18,16 @@ 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 { CONTACT_LIFECYCLE_STAGE_LABELS, CONTACT_LIFECYCLE_STAGES, CONTACT_SOURCE_LABELS, CRM_CONTACT_VIEW_FIELDS, crmContactCustomColumn, crmViewFiltersFromSegment, crmViewSegmentFilters, newResourceScopeFields, ORG_SCOPE_TOKEN, pluginDocsHelp } from "@aglyn/aglyn";
|
|
21
|
-
import { hiddenFilterVisibility
|
|
21
|
+
import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
|
|
22
22
|
import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
23
|
-
import {
|
|
23
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
24
|
+
import ListQueryNotices from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
|
|
25
|
+
import { CONTACT_LIST_DECLARATION, CONTACT_LIST_FILTER_FIELDS, CONTACT_LIST_FILTER_HEADERS, CONTACT_PREFIX_SEARCH, contactClauseImpliesScope, contactSoloClause, contactCustomFilterFields, contactCustomFilterHeaders, contactQueryClause } from "../constants/contact-filters.js";
|
|
26
|
+
import { crmAskClauses, crmQueryRefusals } from "../model/crm-list-query.js";
|
|
27
|
+
import { useCrmFoldsScope, useCrmListQuery } from "../hooks/use-crm-list-query.js";
|
|
24
28
|
import { contactPrimaryGroup, contactRecordFromDoc } from "../model/contact-record.js";
|
|
25
29
|
import { useCrmOrgMount } from "../hooks/use-crm-org-mount.js";
|
|
26
|
-
import {
|
|
30
|
+
import { useCrmScope } from "../hooks/use-crm-scope.js";
|
|
27
31
|
import { contactsListSeed } from "../model/contacts-list-seed.js";
|
|
28
32
|
import { crmRoutes } from "../model/crm-routes.js";
|
|
29
33
|
import { contactsCsv, downloadTextFile } from "../model/contacts-csv.js";
|
|
@@ -31,9 +35,10 @@ import { useCrmRecordsQuota } from "../hooks/use-crm-records-quota.js";
|
|
|
31
35
|
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
32
36
|
import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
|
|
33
37
|
import ContactsBulkBar from "./contacts-bulk-bar.js";
|
|
38
|
+
import { CrmListActions } from "./crm-list-toolbar.js";
|
|
34
39
|
import { ContactImportButton } from "./contact-import-drawer.js";
|
|
35
40
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
36
|
-
import {
|
|
41
|
+
import { useFirestore, useFirestoreCollection, useFirestoreDoc, useUser } from "@aglyn/tenant-feature-instance";
|
|
37
42
|
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
|
|
38
43
|
import { addDoc, collection, deleteDoc, doc, limit, orderBy, query } from "firebase/firestore";
|
|
39
44
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
@@ -50,7 +55,7 @@ import { useCrmViewGrid } from "../hooks/use-crm-view-grid.js";
|
|
|
50
55
|
import { CRM_LIST_SLOTS, CrmColumnOrderProvider } from "./crm-column-menu.js";
|
|
51
56
|
import { useCompanyOptions } from "./company-picker.js";
|
|
52
57
|
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
53
|
-
import { listFilterGridColumns
|
|
58
|
+
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
54
59
|
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
55
60
|
import CrmViewsControl from "./crm-views-control.js";
|
|
56
61
|
import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-suite-lock.js";
|
|
@@ -60,13 +65,6 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
60
65
|
* The map lives beside the `ContactSource` union so the dynamic-list rule
|
|
61
66
|
* editor and this filter cannot disagree about what `order` is called.
|
|
62
67
|
*/ const SOURCE_LABELS = CONTACT_SOURCE_LABELS;
|
|
63
|
-
/** What the grid's quick search reads on a contact: who they are, and their tags. */ const CONTACT_SEARCH_FIELDS = [
|
|
64
|
-
'name',
|
|
65
|
-
'email',
|
|
66
|
-
'phone',
|
|
67
|
-
'company',
|
|
68
|
-
'tags'
|
|
69
|
-
];
|
|
70
68
|
/**
|
|
71
69
|
* What the list keeps out of sight until a view says otherwise: the
|
|
72
70
|
* filter-only columns, which never show, and the optional columns
|
|
@@ -108,8 +106,7 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
108
106
|
* body is the v1 page unchanged, and it takes the shell's full prop bag so
|
|
109
107
|
* nothing the v1 page read has to be threaded through the hub by name.
|
|
110
108
|
*/ export function ContactsPeopleSection(props) {
|
|
111
|
-
var _ref, _ref1, _ref2,
|
|
112
|
-
var _plan_served;
|
|
109
|
+
var _ref, _ref1, _ref2, _ref3, _records_contactsCount, _ref4, _ref5, _ref6, _ref7;
|
|
113
110
|
const { hostId, org, releaseFlag, basePath } = props;
|
|
114
111
|
/*
|
|
115
112
|
* A row is a LINK now (AGL-2596): opening a person navigates to their own
|
|
@@ -211,7 +208,9 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
211
208
|
...seed.filter ? [
|
|
212
209
|
seed.filter
|
|
213
210
|
] : [],
|
|
214
|
-
|
|
211
|
+
// A form's captures all came in through a form: the source beside it
|
|
212
|
+
// would ask a second array clause of the one query (AGL-3321).
|
|
213
|
+
...seed.source && !seed.formId ? [
|
|
215
214
|
{
|
|
216
215
|
field: CRM_CONTACT_VIEW_FIELDS.source,
|
|
217
216
|
op: 'equals',
|
|
@@ -247,97 +246,47 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
247
246
|
customFields.active
|
|
248
247
|
]);
|
|
249
248
|
/*
|
|
250
|
-
*
|
|
249
|
+
* EVERY CLAUSE AND THE SEARCH ON ONE QUERY (AGL-3321).
|
|
251
250
|
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
|
|
258
|
-
* Under a site the
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*/ const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
} : {}), [
|
|
270
|
-
filterFields,
|
|
251
|
+
* Each stored clause is asked through the field its writers keep
|
|
252
|
+
* (`contactQueryClause`): a holder's owner, stage, source, company, tags,
|
|
253
|
+
* custom value or "has bought" as that holder's facet key, the verdict as
|
|
254
|
+
* `emailStatus`, the rest as stored. The query pages the list newest
|
|
255
|
+
* change first and carries all of it; what one query cannot hold is
|
|
256
|
+
* refused by name above the list, and nothing is matched over the rows a
|
|
257
|
+
* page happened to load. Under a site the scope clause is the query's one
|
|
258
|
+
* array clause — the search folds into it, and a facet or form clause
|
|
259
|
+
* stands in its place, for a reader whose access is not limited to
|
|
260
|
+
* particular sites.
|
|
261
|
+
*/ const foldsScope = useCrmFoldsScope(orgId, visibleToTokens);
|
|
262
|
+
const groupId = (_ref3 = consentGroup == null ? void 0 : consentGroup.groupId) != null ? _ref3 : null;
|
|
263
|
+
const asked = useMemo(()=>crmAskClauses(views.state.filters, (clause)=>contactQueryClause(clause, {
|
|
264
|
+
groupId,
|
|
265
|
+
foldsScope
|
|
266
|
+
})), [
|
|
271
267
|
views.state.filters,
|
|
272
|
-
|
|
268
|
+
groupId,
|
|
269
|
+
foldsScope
|
|
273
270
|
]);
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
* save and both come off the seed. Edit the notes against a cached read
|
|
291
|
-
* and the tags go back with them, and tags are what
|
|
292
|
-
* `contactMatchesSegment` runs on: a saved segment is a campaign
|
|
293
|
-
* audience, so a rollback here silently changes who gets emailed.
|
|
294
|
-
*/ fromCache: contactsFromCache } = useFirestoreCollection(()=>{
|
|
295
|
-
if (!dataScope) return null;
|
|
296
|
-
/*
|
|
297
|
-
* ORDERED, and filtered by the QUERY (AGL-2501, AGL-2292).
|
|
298
|
-
*
|
|
299
|
-
* Two bugs shared this one line. `limit(1000)` with no `orderBy` returns
|
|
300
|
-
* documents in ID order — contacts are created with `.add()` and
|
|
301
|
-
* `createResourceUid()`, so that is a pseudo-random SAMPLE of a thousand,
|
|
302
|
-
* and the client `.sort()` below made it look reliably newest-first. An
|
|
303
|
-
* org with forty thousand contacts saw a thousand arbitrary ones,
|
|
304
|
-
* convincingly sorted.
|
|
305
|
-
*
|
|
306
|
-
* The search then ran over that sample, so a name on the wrong side of
|
|
307
|
-
* the cap answered "no contacts match" — the answer a search must never
|
|
308
|
-
* give wrongly, on the list a merchant uses to find one person.
|
|
309
|
-
*
|
|
310
|
-
* The cap STAYS: nobody needs forty thousand rows streamed into a table,
|
|
311
|
-
* and the head-count has been a server aggregate since AGL-1706. What
|
|
312
|
-
* changes is that the thousand are now the newest thousand, and that a
|
|
313
|
-
* filter reaches the whole collection before the cap applies.
|
|
314
|
-
*/ const constraints = plan.constraints;
|
|
315
|
-
/*
|
|
316
|
-
* SCOPED, and this is the leak it closes.
|
|
317
|
-
*
|
|
318
|
-
* The listener had no `where()` at all: `hostId` reached it only to
|
|
319
|
-
* resolve which ORG owns the data, so every site in the account listed
|
|
320
|
-
* every contact in the account. An agency's client opened Contacts and
|
|
321
|
-
* read the other clients' customers.
|
|
322
|
-
*
|
|
323
|
-
* `array-contains-any` over the group's tokens is the same predicate
|
|
324
|
-
* the rules evaluate with `hasAny`, so a filtered query is provable
|
|
325
|
-
* per-document and an UNFILTERED one is now permission-denied rather
|
|
326
|
-
* than quietly returning everything.
|
|
327
|
-
*/ return query(collection(firestore, dataScope[0], dataScope[1], 'contacts'), // Dropped for the form mirror alone — see `byForm` — and absent at
|
|
328
|
-
// the organization level, where the tokens are `null` (AGL-2630).
|
|
329
|
-
...byForm ? [] : crmVisibleToClause(visibleToTokens), ...constraints != null ? constraints : [
|
|
330
|
-
orderBy('updatedAt', 'desc')
|
|
331
|
-
], limit(1000));
|
|
332
|
-
}, [
|
|
333
|
-
firestore,
|
|
334
|
-
dataScope,
|
|
335
|
-
plan.constraints,
|
|
336
|
-
byForm,
|
|
337
|
-
visibleToTokens
|
|
338
|
-
], {
|
|
339
|
-
idField: '$id'
|
|
271
|
+
const impliesScope = useMemo(()=>contactClauseImpliesScope(groupId), [
|
|
272
|
+
groupId
|
|
273
|
+
]);
|
|
274
|
+
// The quick search is the grid's box, answered by the same query.
|
|
275
|
+
const [searchWords, setSearchWords] = useState([]);
|
|
276
|
+
const paged = useCrmListQuery({
|
|
277
|
+
scope: dataScope,
|
|
278
|
+
collection: 'contacts',
|
|
279
|
+
visibleTo: visibleToTokens,
|
|
280
|
+
foldsScope,
|
|
281
|
+
declaration: CONTACT_LIST_DECLARATION,
|
|
282
|
+
clauses: asked.clauses,
|
|
283
|
+
search: searchWords,
|
|
284
|
+
impliesScope,
|
|
285
|
+
soloClause: contactSoloClause,
|
|
286
|
+
prefixSearch: CONTACT_PREFIX_SEARCH
|
|
340
287
|
});
|
|
288
|
+
const contactDocs = paged.rows;
|
|
289
|
+
const contactsStatus = paged.status;
|
|
341
290
|
/*
|
|
342
291
|
* OPENED FOR ONE ADDRESS: move straight on to the record (AGL-2612).
|
|
343
292
|
*
|
|
@@ -353,15 +302,17 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
353
302
|
useEffect(()=>{
|
|
354
303
|
if (openedByEmail.current || !seed.openEmail) return;
|
|
355
304
|
if (contactsStatus !== 'success') return;
|
|
356
|
-
|
|
357
|
-
|
|
305
|
+
const served = paged.plan.served.some((clause)=>clause.field === 'email' && clause.op === 'equals' && clause.value === seed.openEmail);
|
|
306
|
+
if (!served) return;
|
|
307
|
+
const only = contactDocs.length === 1 && !paged.hasMore ? contactDocs[0] : null;
|
|
358
308
|
if (!only) return;
|
|
359
309
|
openedByEmail.current = true;
|
|
360
310
|
router.replace(routes.contact(String(only.$id)));
|
|
361
311
|
}, [
|
|
362
312
|
seed.openEmail,
|
|
363
313
|
contactsStatus,
|
|
364
|
-
|
|
314
|
+
paged.plan,
|
|
315
|
+
paged.hasMore,
|
|
365
316
|
contactDocs,
|
|
366
317
|
router,
|
|
367
318
|
routes
|
|
@@ -377,7 +328,7 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
377
328
|
* The canonical `name` is the fallback and the shared identity; a holder's
|
|
378
329
|
* own override wins, so one business renaming a person cannot change what
|
|
379
330
|
* an unrelated business sees.
|
|
380
|
-
*/ const contacts = useMemo(()=>
|
|
331
|
+
*/ const contacts = useMemo(()=>contactDocs.map((row)=>contactRecordFromDoc(row, consentGroup != null ? consentGroup : contactPrimaryGroup(row, org))), [
|
|
381
332
|
contactDocs,
|
|
382
333
|
consentGroup,
|
|
383
334
|
org
|
|
@@ -399,10 +350,10 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
399
350
|
/**
|
|
400
351
|
* The HEAD-COUNT, read as a server-side aggregate (AGL-1706).
|
|
401
352
|
*
|
|
402
|
-
* The
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* saturated at 1,000, which is *exactly* the smallest paid included band
|
|
353
|
+
* The list above reads a page at a time (AGL-3321) — nobody needs 40,000
|
|
354
|
+
* rows streamed into a table. What it must not do is answer "how many
|
|
355
|
+
* contacts does this org have", and it did when it was a `limit(1000)`
|
|
356
|
+
* window: `contacts.length` saturated at 1,000, which is *exactly* the smallest paid included band
|
|
406
357
|
* (`starter`). So `overageContacts = max(0, used − included)` was 0 on
|
|
407
358
|
* every stock plan and the alert below could not render at all.
|
|
408
359
|
*
|
|
@@ -450,7 +401,7 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
450
401
|
firestore,
|
|
451
402
|
hostId
|
|
452
403
|
]);
|
|
453
|
-
const droppedTotal = Number((
|
|
404
|
+
const droppedTotal = Number((_ref4 = droppedCounter == null ? void 0 : droppedCounter['total']) != null ? _ref4 : 0);
|
|
454
405
|
/*==========================================
|
|
455
406
|
* REFUNDS THAT REACHED NO CONTACT (AGL-2329).
|
|
456
407
|
*
|
|
@@ -470,10 +421,12 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
470
421
|
firestore,
|
|
471
422
|
hostId
|
|
472
423
|
]);
|
|
473
|
-
const unmatchedRefundTotal = Number((
|
|
474
|
-
const unmatchedRefundReason = String((
|
|
424
|
+
const unmatchedRefundTotal = Number((_ref5 = unmatchedRefundCounter == null ? void 0 : unmatchedRefundCounter['total']) != null ? _ref5 : 0);
|
|
425
|
+
const unmatchedRefundReason = String((_ref6 = unmatchedRefundCounter == null ? void 0 : unmatchedRefundCounter['lastReason']) != null ? _ref6 : '');
|
|
475
426
|
// Saved segments (AGL-199): reusable audience filters.
|
|
476
|
-
const { data: segmentDocs } = useFirestoreCollection(()=>dataScope ? query(collection(firestore, dataScope[0], dataScope[1], 'contactSegments'),
|
|
427
|
+
const { data: segmentDocs } = useFirestoreCollection(()=>dataScope ? query(collection(firestore, dataScope[0], dataScope[1], 'contactSegments'), // The first fifty by name — the menu's own order — rather than
|
|
428
|
+
// whichever fifty the ids happen to sort first.
|
|
429
|
+
orderBy('name'), limit(50)) : null, [
|
|
477
430
|
firestore,
|
|
478
431
|
dataScope
|
|
479
432
|
], {
|
|
@@ -510,32 +463,7 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
510
463
|
dataScope,
|
|
511
464
|
firestore
|
|
512
465
|
]);
|
|
513
|
-
|
|
514
|
-
* THE WINDOW CLAUSES narrow in the browser, and the caption says so.
|
|
515
|
-
*
|
|
516
|
-
* An owner, a stage, a source, a company and a custom value all live on
|
|
517
|
-
* the viewing group's FACET, and a facet path is per group, so none of
|
|
518
|
-
* them is a field the query grammar can reach without an index per
|
|
519
|
-
* group; a second tag clause, or "any of these tags", is an array clause
|
|
520
|
-
* the scope predicate already occupies. So every clause the plan could
|
|
521
|
-
* not serve refines the ordered window rather than the collection —
|
|
522
|
-
* through the same matcher the grammar declares, so a chip and the rows
|
|
523
|
-
* under it agree. Ordinary orgs load their whole list into the window
|
|
524
|
-
* anyway; the caption below says what these narrow for the ones that do
|
|
525
|
-
* not.
|
|
526
|
-
*/ const uid = (_ref6 = user == null ? void 0 : user.uid) != null ? _ref6 : '';
|
|
527
|
-
const windowClauses = plan.window;
|
|
528
|
-
// The quick search is the grid's box, answered here over the same window.
|
|
529
|
-
const [searchWords, setSearchWords] = useState([]);
|
|
530
|
-
const searchKey = searchWords.join(' ');
|
|
531
|
-
const visible = useMemo(()=>windowClauses.length || searchWords.length ? contacts.filter((contact)=>windowClauses.every((clause)=>matchListFilter(contact, filterFields, clause)) && listRowMatchesSearch(contact, CONTACT_SEARCH_FIELDS, searchWords)) : contacts, // `searchKey` stands for the words, which are a new array each render.
|
|
532
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
533
|
-
[
|
|
534
|
-
contacts,
|
|
535
|
-
windowClauses,
|
|
536
|
-
filterFields,
|
|
537
|
-
searchKey
|
|
538
|
-
]);
|
|
466
|
+
const uid = (_ref7 = user == null ? void 0 : user.uid) != null ? _ref7 : '';
|
|
539
467
|
/*
|
|
540
468
|
* The choices the filter bar picks from. The companies are read only once
|
|
541
469
|
* the reader has reached for a filter or is looking at a view that names
|
|
@@ -595,6 +523,18 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
595
523
|
companies.options,
|
|
596
524
|
customFields.active
|
|
597
525
|
]);
|
|
526
|
+
// What the query could not hold, named by the clause the reader set.
|
|
527
|
+
const refused = useMemo(()=>crmQueryRefusals(paged.plan, asked, {
|
|
528
|
+
fields: filterFields,
|
|
529
|
+
headers: filterHeaders,
|
|
530
|
+
options: filterOptions
|
|
531
|
+
}), [
|
|
532
|
+
paged.plan,
|
|
533
|
+
asked,
|
|
534
|
+
filterFields,
|
|
535
|
+
filterHeaders,
|
|
536
|
+
filterOptions
|
|
537
|
+
]);
|
|
598
538
|
/* One row grammar, the console's (AGL-2501) — the same table everywhere. */ const contactColumns = useMemo(()=>[
|
|
599
539
|
...contactListColumns({
|
|
600
540
|
memberName: members.memberName,
|
|
@@ -688,6 +628,12 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
688
628
|
enqueueSnackbar
|
|
689
629
|
]);
|
|
690
630
|
/** The rows ticked for a bulk action (AGL-2603); the bar above the table acts on them. */ const [selectedIds, setSelectedIds] = useState([]);
|
|
631
|
+
// A filter, a search or a page is a different set of rows; a selection
|
|
632
|
+
// made on the last one would be a count over rows no longer on screen.
|
|
633
|
+
useEffect(()=>setSelectedIds([]), [
|
|
634
|
+
paged.plan,
|
|
635
|
+
paged.page
|
|
636
|
+
]);
|
|
691
637
|
/*==========================================
|
|
692
638
|
* ADDING ONE PERSON BY HAND (AGL-2596).
|
|
693
639
|
*
|
|
@@ -778,150 +724,40 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
778
724
|
customFields.active
|
|
779
725
|
]);
|
|
780
726
|
const handleExport = useCallback(()=>{
|
|
781
|
-
downloadTextFile('contacts.csv', 'text/csv', contactsCsv(
|
|
727
|
+
downloadTextFile('contacts.csv', 'text/csv', contactsCsv(contacts, csvOptions));
|
|
782
728
|
}, [
|
|
783
|
-
|
|
729
|
+
contacts,
|
|
784
730
|
csvOptions
|
|
785
731
|
]);
|
|
786
732
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
787
733
|
children: [
|
|
788
|
-
/*#__PURE__*/
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
796
|
-
spacing: 2,
|
|
797
|
-
children: [
|
|
798
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
799
|
-
direction: "row",
|
|
800
|
-
spacing: 1,
|
|
801
|
-
sx: {
|
|
802
|
-
alignItems: 'center',
|
|
803
|
-
flexWrap: 'wrap',
|
|
804
|
-
rowGap: 1
|
|
805
|
-
},
|
|
806
|
-
children: [
|
|
807
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
808
|
-
variant: "body2",
|
|
809
|
-
color: "text.secondary",
|
|
810
|
-
sx: {
|
|
811
|
-
flex: 1
|
|
812
|
-
},
|
|
813
|
-
children: `${contactCount.toLocaleString()} contacts · ${Number.isFinite(quota.included) ? `${quota.used.toLocaleString()} of ${quota.included.toLocaleString()} CRM records` : `${quota.used.toLocaleString()} CRM records`}`
|
|
814
|
-
}),
|
|
815
|
-
suiteIncluded ? /*#__PURE__*/ _jsx(ContactImportButton, {
|
|
816
|
-
hostId: hostId,
|
|
817
|
-
org: org
|
|
818
|
-
}) : /*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
819
|
-
children: 'Import CSV'
|
|
820
|
-
}),
|
|
821
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
822
|
-
size: "small",
|
|
823
|
-
onClick: handleExport,
|
|
824
|
-
disabled: !visible.length,
|
|
825
|
-
children: 'Export CSV'
|
|
826
|
-
}),
|
|
827
|
-
suiteIncluded ? /*#__PURE__*/ _jsx(Button, {
|
|
828
|
-
size: "small",
|
|
829
|
-
variant: "contained",
|
|
830
|
-
color: "primary",
|
|
831
|
-
disabled: !dataScope,
|
|
832
|
-
onClick: ()=>{
|
|
833
|
-
setCreateError(null);
|
|
834
|
-
setCreateOpen(true);
|
|
835
|
-
},
|
|
836
|
-
children: 'New contact'
|
|
837
|
-
}) : /*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
838
|
-
variant: "contained",
|
|
839
|
-
color: "primary",
|
|
840
|
-
children: 'New contact'
|
|
841
|
-
})
|
|
842
|
-
]
|
|
843
|
-
}),
|
|
844
|
-
suiteIncluded ? null : /*#__PURE__*/ _jsx(CrmSuiteNotice, {
|
|
845
|
-
children: 'Contacts arrive here on their own from your forms, sign-ups, ' + 'orders and bookings. Adding one by hand, importing a CSV, ' + "saved views, and a contact's owner, stage and company are " + 'part of the CRM.'
|
|
846
|
-
}),
|
|
847
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
848
|
-
spacing: 1,
|
|
849
|
-
children: [
|
|
850
|
-
/*#__PURE__*/ _jsx(CrmViewsControl, {
|
|
851
|
-
controller: views,
|
|
852
|
-
allLabel: "All contacts",
|
|
853
|
-
suiteLocked: !suiteIncluded,
|
|
854
|
-
presets: segmentPresets,
|
|
855
|
-
onSaveAsSegment: segmentFilters && dataScope ? ()=>setSegmentName('') : null
|
|
856
|
-
}),
|
|
857
|
-
/*#__PURE__*/ _jsx(ListFilterChips, {
|
|
858
|
-
fields: filterFields,
|
|
859
|
-
headers: filterHeaders,
|
|
860
|
-
clauses: views.state.filters,
|
|
861
|
-
onChange: views.setFilters,
|
|
862
|
-
options: filterOptions,
|
|
863
|
-
servedField: (_ref7 = (_plan_served = plan.served) == null ? void 0 : _plan_served.field) != null ? _ref7 : null
|
|
864
|
-
})
|
|
865
|
-
]
|
|
734
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
735
|
+
spacing: 3,
|
|
736
|
+
children: [
|
|
737
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
738
|
+
header: 'Contacts',
|
|
739
|
+
help: pluginDocsHelp('contacts', {
|
|
740
|
+
anchor: '#the-contacts-page'
|
|
866
741
|
}),
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
// reads its registry default, which is no org's verdict, so an
|
|
873
|
-
// ungated alert could assert the wrong wording for one paint.
|
|
874
|
-
releaseFlagsReady ? /*#__PURE__*/ _jsx(Alert, {
|
|
875
|
-
severity: "info",
|
|
876
|
-
children: contactsBilled ? // basis (AGL-2399): the count here is LIVE, the invoice
|
|
877
|
-
// charges the last reading before the month closes, so the
|
|
878
|
-
// dollar figure is a projection until the month ends. Staff
|
|
879
|
-
// and customer must not read different sentences about the
|
|
880
|
-
// same org's money — that applies to WHEN it is measured as
|
|
881
|
-
// much as to how much.
|
|
882
|
-
`${quota.overageRecords.toLocaleString()} CRM records over ` + `your plan's included ${quota.included.toLocaleString()} — ` + `metered at $${quota.overageRateUsd}/1,000 per month ` + `(≈$${quota.overageMonthlyUsd.toFixed(2)} if your list ends ` + 'the month at this size). ' + 'Upgrade in Billing for a larger included audience.' : // itself the wording `1a2aed5cb` published to
|
|
883
|
-
// `billing-and-plans/overview.md` (AGL-1601/1603). Staff and
|
|
884
|
-
// customer must not read different sentences about the same
|
|
885
|
-
// org's money.
|
|
886
|
-
//
|
|
887
|
-
// THE COUNT STAYS, THE TOTAL GOES: the head-count is real —
|
|
888
|
-
// ingestion captured those records — and is not a claim
|
|
889
|
-
// about money. The upgrade nudge goes with the total, since
|
|
890
|
-
// it prompts a purchase premised on a charge that is not
|
|
891
|
-
// happening.
|
|
892
|
-
`${quota.overageRecords.toLocaleString()} CRM records over ` + `your plan's included ${quota.included.toLocaleString()} — ` + 'not billed while the CRM is unavailable. ' + `The $${quota.overageRateUsd}/1,000 rate applies once ` + 'Contacts opens.'
|
|
893
|
-
}) : droppedTotal > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
894
|
-
severity: "info",
|
|
895
|
-
children: `${droppedTotal.toLocaleString()} earlier visitor${droppedTotal === 1 ? ' was' : 's were'} not captured while your CRM records band was full.`
|
|
896
|
-
}) : null,
|
|
897
|
-
unmatchedRefundTotal > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
898
|
-
severity: "warning",
|
|
899
|
-
children: `${unmatchedRefundTotal.toLocaleString()} refund${unmatchedRefundTotal === 1 ? '' : 's'} could not be recorded against a contact${UNMATCHED_REFUND_REASON[unmatchedRefundReason] ? ` — most recently because ${UNMATCHED_REFUND_REASON[unmatchedRefundReason]}` : ''}. The money moved; the customer's timeline does not show it.`
|
|
900
|
-
}) : null,
|
|
901
|
-
byForm && contactsStatus === 'error' ? /*#__PURE__*/ _jsx(Alert, {
|
|
902
|
-
severity: "info",
|
|
903
|
-
children: 'The contacts this form captured could not be listed. Your ' + 'access is limited to specific sites, and a form filter ' + 'cannot be narrowed to them — an organization administrator ' + 'can see it.'
|
|
904
|
-
}) : contacts.length === 0 && !views.state.filters.length ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
|
|
905
|
-
label: contactsStatus === 'loading' ? 'Loading contacts…' : 'No contacts yet',
|
|
906
|
-
description: contactsStatus === 'loading' ? undefined : suiteIncluded ? 'Form submissions, member sign-ups, orders and bookings become contacts on their own; add one by hand or bring a list in from a CSV.' : 'Form submissions, member sign-ups, orders and bookings become contacts on their own.',
|
|
907
|
-
action: contactsStatus === 'loading' ? undefined : !suiteIncluded ? /*#__PURE__*/ _jsxs(Stack, {
|
|
908
|
-
direction: "row",
|
|
909
|
-
spacing: 1,
|
|
742
|
+
contentGutterX: true,
|
|
743
|
+
contentGutterY: true,
|
|
744
|
+
HeaderProps: {
|
|
745
|
+
// The record actions, top right and never clipped (AGL-3311).
|
|
746
|
+
action: /*#__PURE__*/ _jsxs(CrmListActions, {
|
|
910
747
|
children: [
|
|
911
|
-
/*#__PURE__*/ _jsx(
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}),
|
|
916
|
-
/*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
748
|
+
suiteIncluded ? /*#__PURE__*/ _jsx(ContactImportButton, {
|
|
749
|
+
hostId: hostId,
|
|
750
|
+
org: org
|
|
751
|
+
}) : /*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
917
752
|
children: 'Import CSV'
|
|
918
|
-
})
|
|
919
|
-
]
|
|
920
|
-
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
921
|
-
direction: "row",
|
|
922
|
-
spacing: 1,
|
|
923
|
-
children: [
|
|
753
|
+
}),
|
|
924
754
|
/*#__PURE__*/ _jsx(Button, {
|
|
755
|
+
size: "small",
|
|
756
|
+
onClick: handleExport,
|
|
757
|
+
disabled: !contacts.length,
|
|
758
|
+
children: 'Export CSV'
|
|
759
|
+
}),
|
|
760
|
+
suiteIncluded ? /*#__PURE__*/ _jsx(Button, {
|
|
925
761
|
size: "small",
|
|
926
762
|
variant: "contained",
|
|
927
763
|
color: "primary",
|
|
@@ -931,73 +767,188 @@ import { CrmSuiteLockedButton, CrmSuiteNotice, crmSuiteIncluded } from "./crm-su
|
|
|
931
767
|
setCreateOpen(true);
|
|
932
768
|
},
|
|
933
769
|
children: 'New contact'
|
|
934
|
-
}),
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
770
|
+
}) : /*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
771
|
+
variant: "contained",
|
|
772
|
+
color: "primary",
|
|
773
|
+
children: 'New contact'
|
|
938
774
|
})
|
|
939
775
|
]
|
|
940
776
|
})
|
|
941
|
-
}
|
|
777
|
+
},
|
|
778
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
779
|
+
spacing: 2,
|
|
942
780
|
children: [
|
|
943
|
-
|
|
944
|
-
variant: "
|
|
781
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
782
|
+
variant: "body2",
|
|
945
783
|
color: "text.secondary",
|
|
946
|
-
children: (
|
|
784
|
+
children: `${contactCount.toLocaleString()} contacts · ${Number.isFinite(quota.included) ? `${quota.used.toLocaleString()} of ${quota.included.toLocaleString()} CRM records` : `${quota.used.toLocaleString()} CRM records`}`
|
|
785
|
+
}),
|
|
786
|
+
suiteIncluded ? null : /*#__PURE__*/ _jsx(CrmSuiteNotice, {
|
|
787
|
+
children: 'Contacts arrive here on their own from your forms, sign-ups, ' + 'orders and bookings. Adding one by hand, importing a CSV, ' + "saved views, and a contact's owner, stage and company are " + 'part of the CRM.'
|
|
788
|
+
}),
|
|
789
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
790
|
+
spacing: 1,
|
|
791
|
+
children: [
|
|
792
|
+
/*#__PURE__*/ _jsx(CrmViewsControl, {
|
|
793
|
+
controller: views,
|
|
794
|
+
allLabel: "All contacts",
|
|
795
|
+
suiteLocked: !suiteIncluded,
|
|
796
|
+
presets: segmentPresets,
|
|
797
|
+
onSaveAsSegment: segmentFilters && dataScope ? ()=>setSegmentName('') : null
|
|
798
|
+
}),
|
|
799
|
+
/*#__PURE__*/ _jsx(ListFilterChips, {
|
|
800
|
+
fields: filterFields,
|
|
801
|
+
headers: filterHeaders,
|
|
802
|
+
clauses: views.state.filters,
|
|
803
|
+
onChange: views.setFilters,
|
|
804
|
+
options: filterOptions,
|
|
805
|
+
marksServed: false
|
|
806
|
+
})
|
|
807
|
+
]
|
|
808
|
+
}),
|
|
809
|
+
!quota.allowed ? /*#__PURE__*/ _jsx(Alert, {
|
|
810
|
+
severity: "warning",
|
|
811
|
+
children: 'CRM records limit reached — new visitors are no longer ' + 'captured' + (droppedTotal > 0 ? ` (${droppedTotal.toLocaleString()} missed so far)` : '') + '. Upgrade in Billing to keep collecting.'
|
|
812
|
+
}) : quota.overageRecords > 0 && quota.overageRateUsd != null && // No claim about money until the verdict that decides it has
|
|
813
|
+
// settled (AGL-1662). Before Remote Config activation the flag
|
|
814
|
+
// reads its registry default, which is no org's verdict, so an
|
|
815
|
+
// ungated alert could assert the wrong wording for one paint.
|
|
816
|
+
releaseFlagsReady ? /*#__PURE__*/ _jsx(Alert, {
|
|
817
|
+
severity: "info",
|
|
818
|
+
children: contactsBilled ? // basis (AGL-2399): the count here is LIVE, the invoice
|
|
819
|
+
// charges the last reading before the month closes, so the
|
|
820
|
+
// dollar figure is a projection until the month ends. Staff
|
|
821
|
+
// and customer must not read different sentences about the
|
|
822
|
+
// same org's money — that applies to WHEN it is measured as
|
|
823
|
+
// much as to how much.
|
|
824
|
+
`${quota.overageRecords.toLocaleString()} CRM records over ` + `your plan's included ${quota.included.toLocaleString()} — ` + `metered at $${quota.overageRateUsd}/1,000 per month ` + `(≈$${quota.overageMonthlyUsd.toFixed(2)} if your list ends ` + 'the month at this size). ' + 'Upgrade in Billing for a larger included audience.' : // itself the wording `1a2aed5cb` published to
|
|
825
|
+
// `billing-and-plans/overview.md` (AGL-1601/1603). Staff and
|
|
826
|
+
// customer must not read different sentences about the same
|
|
827
|
+
// org's money.
|
|
828
|
+
//
|
|
829
|
+
// THE COUNT STAYS, THE TOTAL GOES: the head-count is real —
|
|
830
|
+
// ingestion captured those records — and is not a claim
|
|
831
|
+
// about money. The upgrade nudge goes with the total, since
|
|
832
|
+
// it prompts a purchase premised on a charge that is not
|
|
833
|
+
// happening.
|
|
834
|
+
`${quota.overageRecords.toLocaleString()} CRM records over ` + `your plan's included ${quota.included.toLocaleString()} — ` + 'not billed while the CRM is unavailable. ' + `The $${quota.overageRateUsd}/1,000 rate applies once ` + 'Contacts opens.'
|
|
835
|
+
}) : droppedTotal > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
836
|
+
severity: "info",
|
|
837
|
+
children: `${droppedTotal.toLocaleString()} earlier visitor${droppedTotal === 1 ? ' was' : 's were'} not captured while your CRM records band was full.`
|
|
838
|
+
}) : null,
|
|
839
|
+
unmatchedRefundTotal > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
840
|
+
severity: "warning",
|
|
841
|
+
children: `${unmatchedRefundTotal.toLocaleString()} refund${unmatchedRefundTotal === 1 ? '' : 's'} could not be recorded against a contact${UNMATCHED_REFUND_REASON[unmatchedRefundReason] ? ` — most recently because ${UNMATCHED_REFUND_REASON[unmatchedRefundReason]}` : ''}. The money moved; the customer's timeline does not show it.`
|
|
947
842
|
}) : null,
|
|
948
|
-
/*#__PURE__*/ _jsx(
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
scope: dataScope,
|
|
952
|
-
consentGroup: consentGroup,
|
|
953
|
-
rows: visible,
|
|
954
|
-
selected: selectedIds,
|
|
955
|
-
onSelectedChange: setSelectedIds,
|
|
956
|
-
csv: csvOptions,
|
|
957
|
-
suiteLocked: !suiteIncluded
|
|
843
|
+
/*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
844
|
+
refused: refused,
|
|
845
|
+
notices: paged.plan.notices
|
|
958
846
|
}),
|
|
959
|
-
/*#__PURE__*/ _jsx(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
847
|
+
contacts.length === 0 && !views.state.filters.length && !searchWords.some((word)=>word.trim()) ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
|
|
848
|
+
label: contactsStatus === 'loading' ? 'Loading contacts…' : 'No contacts yet',
|
|
849
|
+
description: contactsStatus === 'loading' ? undefined : suiteIncluded ? 'Form submissions, member sign-ups, orders and bookings become contacts on their own; add one by hand or bring a list in from a CSV.' : 'Form submissions, member sign-ups, orders and bookings become contacts on their own.',
|
|
850
|
+
action: contactsStatus === 'loading' ? undefined : !suiteIncluded ? /*#__PURE__*/ _jsxs(Stack, {
|
|
851
|
+
direction: "row",
|
|
852
|
+
spacing: 1,
|
|
853
|
+
children: [
|
|
854
|
+
/*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
855
|
+
variant: "contained",
|
|
856
|
+
color: "primary",
|
|
857
|
+
children: 'New contact'
|
|
858
|
+
}),
|
|
859
|
+
/*#__PURE__*/ _jsx(CrmSuiteLockedButton, {
|
|
860
|
+
children: 'Import CSV'
|
|
861
|
+
})
|
|
862
|
+
]
|
|
863
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
864
|
+
direction: "row",
|
|
865
|
+
spacing: 1,
|
|
866
|
+
children: [
|
|
867
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
868
|
+
size: "small",
|
|
869
|
+
variant: "contained",
|
|
870
|
+
color: "primary",
|
|
871
|
+
disabled: !dataScope,
|
|
872
|
+
onClick: ()=>{
|
|
873
|
+
setCreateError(null);
|
|
874
|
+
setCreateOpen(true);
|
|
875
|
+
},
|
|
876
|
+
children: 'New contact'
|
|
877
|
+
}),
|
|
878
|
+
/*#__PURE__*/ _jsx(ContactImportButton, {
|
|
879
|
+
hostId: hostId,
|
|
880
|
+
org: org
|
|
881
|
+
})
|
|
882
|
+
]
|
|
990
883
|
})
|
|
884
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
885
|
+
children: [
|
|
886
|
+
/*#__PURE__*/ _jsx(ContactsBulkBar, {
|
|
887
|
+
hostId: hostId,
|
|
888
|
+
org: org,
|
|
889
|
+
scope: dataScope,
|
|
890
|
+
consentGroup: consentGroup,
|
|
891
|
+
rows: contacts,
|
|
892
|
+
selected: selectedIds,
|
|
893
|
+
onSelectedChange: setSelectedIds,
|
|
894
|
+
csv: csvOptions,
|
|
895
|
+
suiteLocked: !suiteIncluded
|
|
896
|
+
}),
|
|
897
|
+
/*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
|
|
898
|
+
value: grid.columnOrder,
|
|
899
|
+
children: /*#__PURE__*/ _jsx(ListTable, {
|
|
900
|
+
rows: contacts,
|
|
901
|
+
columns: grid.columns,
|
|
902
|
+
slots: CRM_LIST_SLOTS,
|
|
903
|
+
selectable: {
|
|
904
|
+
selected: selectedIds,
|
|
905
|
+
onChange: setSelectedIds
|
|
906
|
+
},
|
|
907
|
+
onOpen: (id)=>router.push(routes.contact(id)),
|
|
908
|
+
/*
|
|
909
|
+
* The grid's Filters panel and quick search edit the view's
|
|
910
|
+
* clauses; the list's query answers them (AGL-3321), so the
|
|
911
|
+
* grid must not filter again itself. Opening the panel is
|
|
912
|
+
* what reads the roster and the companies its pickers name.
|
|
913
|
+
* Columns are the view's, controlled so a saved arrangement
|
|
914
|
+
* is what the grid shows; the query orders the rows —
|
|
915
|
+
* newest change first — so the grid sorts nothing itself.
|
|
916
|
+
*/ filterMode: "server",
|
|
917
|
+
filterModel: gridFilter.filterModel,
|
|
918
|
+
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
919
|
+
quickFilter: true,
|
|
920
|
+
onPreferencePanelOpen: ()=>setFilterOpened(true),
|
|
921
|
+
// A filter that matched nothing is said inside the grid,
|
|
922
|
+
// so the toolbar that set it stays to change it.
|
|
923
|
+
noRowsLabel: contactsStatus === 'loading' ? 'Loading…' : 'No contacts match these filters',
|
|
924
|
+
columnVisibilityModel: grid.columnVisibilityModel,
|
|
925
|
+
onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
|
|
926
|
+
sortingMode: "server",
|
|
927
|
+
disableColumnSorting: true,
|
|
928
|
+
loading: contactsStatus === 'loading',
|
|
929
|
+
// Paged by the footer below, so the grid must not also slice.
|
|
930
|
+
hideFooter: true
|
|
931
|
+
})
|
|
932
|
+
}),
|
|
933
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
934
|
+
page: paged.page,
|
|
935
|
+
pageSize: paged.pageSize,
|
|
936
|
+
rowCount: contacts.length,
|
|
937
|
+
hasMore: paged.hasMore,
|
|
938
|
+
onPageChange: paged.setPage,
|
|
939
|
+
onPageSizeChange: paged.setPageSize
|
|
940
|
+
})
|
|
941
|
+
]
|
|
991
942
|
})
|
|
992
943
|
]
|
|
993
|
-
}),
|
|
994
|
-
/*#__PURE__*/ _jsx(RecentActivityFeed, {
|
|
995
|
-
hostId: hostId,
|
|
996
|
-
org: org,
|
|
997
|
-
basePath: props.basePath
|
|
998
944
|
})
|
|
999
|
-
|
|
1000
|
-
|
|
945
|
+
}),
|
|
946
|
+
/*#__PURE__*/ _jsx(RecentActivityFeed, {
|
|
947
|
+
hostId: hostId,
|
|
948
|
+
org: org,
|
|
949
|
+
basePath: props.basePath
|
|
950
|
+
})
|
|
951
|
+
]
|
|
1001
952
|
}),
|
|
1002
953
|
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1003
954
|
open: segmentName !== null,
|