@aglyn/plugins-crm 1.0.0-beta.182 → 1.0.0-beta.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/companies-section.js +97 -54
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/contacts-section.js +51 -19
  5. package/src/lib/components/contacts-section.js.map +1 -1
  6. package/src/lib/components/crm-filter-bar.d.ts +34 -32
  7. package/src/lib/components/crm-filter-bar.js +34 -235
  8. package/src/lib/components/crm-filter-bar.js.map +1 -1
  9. package/src/lib/components/crm-next-activity-column.d.ts +13 -21
  10. package/src/lib/components/crm-next-activity-column.js +21 -32
  11. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  12. package/src/lib/components/deals-section.js +65 -54
  13. package/src/lib/components/deals-section.js.map +1 -1
  14. package/src/lib/components/leads-section.js +106 -286
  15. package/src/lib/components/leads-section.js.map +1 -1
  16. package/src/lib/components/tasks-section.js +139 -34
  17. package/src/lib/components/tasks-section.js.map +1 -1
  18. package/src/lib/hooks/use-crm-grid-filter.d.ts +77 -0
  19. package/src/lib/hooks/use-crm-grid-filter.js +143 -0
  20. package/src/lib/hooks/use-crm-grid-filter.js.map +1 -0
  21. package/src/lib/model/crm-grid-filter.d.ts +64 -0
  22. package/src/lib/model/crm-grid-filter.js +144 -0
  23. package/src/lib/model/crm-grid-filter.js.map +1 -0
  24. package/src/lib/model/lead-filters.d.ts +42 -0
  25. package/src/lib/model/lead-filters.js +182 -9
  26. package/src/lib/model/lead-filters.js.map +1 -1
  27. package/src/lib/server/email-send.js +4 -2
  28. package/src/lib/server/email-send.js.map +1 -1
@@ -17,7 +17,7 @@
17
17
  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
- import { mdiAccountArrowRight, mdiAccountCancelOutline, mdiAccountConvertOutline, mdiAccountTieOutline, mdiMagnify } from "@aglyn/shared-data-mdi";
20
+ import { mdiAccountArrowRight, mdiAccountCancelOutline, mdiAccountConvertOutline, mdiAccountTieOutline } from "@aglyn/shared-data-mdi";
21
21
  import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
22
22
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
23
23
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
@@ -39,13 +39,16 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
39
39
  import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
40
40
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
41
41
  import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
42
- import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, InputAdornment, InputLabel, MenuItem, Select, Stack, TextField, Typography } from "@mui/material";
42
+ import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Select, Stack, Typography } from "@mui/material";
43
43
  import { collection, deleteField, doc, limit, orderBy, query, serverTimestamp, updateDoc, where } from "firebase/firestore";
44
44
  import { useRouter } from "next/navigation";
45
- import { useCallback, useEffect, useId, useMemo, useState } from "react";
45
+ import { useCallback, useEffect, useMemo, useState } from "react";
46
46
  import { downloadTextFile } from "../model/contacts-csv.js";
47
47
  import { crmRoutes } from "../model/crm-routes.js";
48
- import { LEAD_FILTER_LABELS, LEAD_EMAIL_FILTER_LABELS, LEAD_EMAIL_FILTERS, LEAD_FILTERS, leadMatchesCampaignFilter, leadMatchesEmailFilter, leadMatchesFilter, leadMatchesLeadSourceFilter, leadMatchesSearch, LEAD_SOURCE_FILTER_NONE } from "../model/lead-filters.js";
48
+ import { LEAD_EMAIL_FILTER_OPTIONS, LEAD_FILTER_CODECS, LEAD_LIST_FILTER_FIELDS, LEAD_LIST_FILTER_HEADERS, LEAD_SOURCE_FILTER_NONE, LEAD_STATUS_FILTER_OPTIONS, leadClausesForGrid, leadClausesToStore, leadMatchesClauses, leadMatchesSearch } from "../model/lead-filters.js";
49
+ import { crmFilterColumns } from "../model/crm-grid-filter.js";
50
+ import { useCrmGridFilter } from "../hooks/use-crm-grid-filter.js";
51
+ import CrmFilterBar from "./crm-filter-bar.js";
49
52
  import { useLeadSourcePicklist } from "../hooks/use-lead-source-picklist.js";
50
53
  import { leadsCsv } from "../model/leads-csv.js";
51
54
  import { LeadConvertDialog } from "./lead-convert-dialog.js";
@@ -160,117 +163,33 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
160
163
  leadDocs
161
164
  ]);
162
165
  /*
163
- * The `Show` filter is the saved VIEW'S (AGL-2617): a saved view of leads
164
- * holds the status beside the columns and the sort, and the select below
165
- * writes into it. Unset reads as `open`, which is what the section opened
166
- * on before views existed and the one reading a query cannot express.
166
+ * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the
167
+ * grid's own Filters panel and quick search edit it (AGL-3313): Status,
168
+ * Email, Owner, Lead source and Campaign are select columns of the grid.
169
+ * The clauses keep the shape the old dropdowns stored — see
170
+ * `leadClausesForGrid` — so no saved view of leads loses its filter.
167
171
  */ const views = useCrmSavedView({
168
172
  section: 'leads',
169
173
  hostId,
170
174
  org: props.org,
171
175
  basePath: basePath != null ? basePath : ''
172
176
  });
173
- const filter = useMemo(()=>{
174
- var _views_state_filters_find;
175
- const value = (_views_state_filters_find = views.state.filters.find((clause)=>clause.field === 'status')) == null ? void 0 : _views_state_filters_find.value;
176
- return LEAD_FILTERS.includes(value != null ? value : '') ? value : 'open';
177
- }, [
177
+ const clauses = useMemo(()=>leadClausesForGrid(views.state.filters), [
178
178
  views.state.filters
179
179
  ]);
180
- /*
181
- * The `Email` filter (AGL-3245) is the view's too, as an `emailState`
182
- * clause beside the status one. Each setter keeps the other's clause:
183
- * narrowing to bounced leads does not reopen the unqualified ones.
184
- */ const emailFilter = useMemo(()=>{
185
- var _views_state_filters_find;
186
- const value = (_views_state_filters_find = views.state.filters.find((clause)=>clause.field === 'emailState')) == null ? void 0 : _views_state_filters_find.value;
187
- return LEAD_EMAIL_FILTERS.includes(value != null ? value : '') ? value : 'any';
188
- }, [
189
- views.state.filters
190
- ]);
191
- const setFilter = useCallback((next)=>views.setFilters([
192
- ...views.state.filters.filter((clause)=>clause.field !== 'status'),
193
- ...next === 'open' ? [] : [
194
- {
195
- field: 'status',
196
- op: 'equals',
197
- value: next
198
- }
199
- ]
200
- ]), [
201
- views.setFilters,
202
- views.state.filters
203
- ]);
204
- const setEmailFilter = useCallback((next)=>views.setFilters([
205
- ...views.state.filters.filter((clause)=>clause.field !== 'emailState'),
206
- ...next === 'any' ? [] : [
207
- {
208
- field: 'emailState',
209
- op: 'equals',
210
- value: next
211
- }
212
- ]
213
- ]), [
214
- views.setFilters,
215
- views.state.filters
216
- ]);
217
- /*
218
- * The `Campaign` filter (AGL-3254) is the view's too, as a `campaignIds`
219
- * clause: the id of one of the org's campaign containers, resolved to its
220
- * name from the containers themselves — ids only in storage, so a renamed
221
- * campaign keeps its leads. Under a site the choice is the campaigns
222
- * placed on it; at the organization level, every campaign in the org.
223
- */ const campaignFilter = useMemo(()=>{
224
- var _ref;
225
- var _views_state_filters_find;
226
- return (_ref = (_views_state_filters_find = views.state.filters.find((clause)=>clause.field === 'campaignIds')) == null ? void 0 : _views_state_filters_find.value) != null ? _ref : '';
227
- }, [
228
- views.state.filters
229
- ]);
230
- const setCampaignFilter = useCallback((next)=>views.setFilters([
231
- ...views.state.filters.filter((clause)=>clause.field !== 'campaignIds'),
232
- ...next ? [
233
- {
234
- field: 'campaignIds',
235
- op: 'contains',
236
- value: next
237
- }
238
- ] : []
239
- ]), [
240
- views.setFilters,
241
- views.state.filters
242
- ]);
243
- /*
244
- * The `Lead source` filter (AGL-3298) is the view's too: an `equals`
245
- * clause naming the label records store, or an `isEmpty` one for the
246
- * leads that hold none.
247
- */ const leadSourceFilter = useMemo(()=>{
248
- var _clause_value;
249
- const clause = views.state.filters.find((entry)=>entry.field === 'leadSource');
250
- if (!clause) return '';
251
- return clause.op === 'isEmpty' ? LEAD_SOURCE_FILTER_NONE : String((_clause_value = clause.value) != null ? _clause_value : '');
252
- }, [
253
- views.state.filters
254
- ]);
255
- const setLeadSourceFilter = useCallback((next)=>views.setFilters([
256
- ...views.state.filters.filter((clause)=>clause.field !== 'leadSource'),
257
- ...next === LEAD_SOURCE_FILTER_NONE ? [
258
- {
259
- field: 'leadSource',
260
- op: 'isEmpty',
261
- value: ''
262
- }
263
- ] : next ? [
264
- {
265
- field: 'leadSource',
266
- op: 'equals',
267
- value: next
268
- }
269
- ] : []
270
- ]), [
271
- views.setFilters,
272
- views.state.filters
180
+ const setClauses = useCallback((next)=>views.setFilters(leadClausesToStore(next)), [
181
+ views.setFilters
273
182
  ]);
183
+ const gridFilter = useCrmGridFilter({
184
+ clauses,
185
+ onChange: setClauses,
186
+ selectFields: [
187
+ 'status',
188
+ 'emailState',
189
+ 'ownerUid'
190
+ ],
191
+ codecs: LEAD_FILTER_CODECS
192
+ });
274
193
  const campaigns = useCrmCampaigns({
275
194
  hostId,
276
195
  orgId
@@ -284,28 +203,62 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
284
203
  }, [
285
204
  campaigns.options
286
205
  ]);
287
- // The label's id, so the filter's combobox is named "Show" rather than
288
- // after the option it shows — see `LeadOwnerSelect`.
289
- const filterLabelId = useId();
290
- const emailFilterLabelId = useId();
291
- const campaignFilterLabelId = useId();
292
- const leadSourceFilterLabelId = useId();
293
206
  /*
294
- * The search box is the SECTION'S, not the grid's (AGL-3246). The grid's
295
- * quick filter runs over the rows the grid holds, and the grid holds one
296
- * PAGE of the window — so a lead on page three answered "no match" while
297
- * the footer below went on counting the unfiltered window. The term
298
- * narrows the whole loaded window here, beside the status filter and
299
- * before the footer's count and the page slice, over the fields a person
300
- * types to find a lead: name, email, company, title and tags.
301
- */ const [search, setSearch] = useState('');
302
- const rows = useMemo(()=>window.filter((lead)=>leadMatchesFilter(lead, filter) && leadMatchesEmailFilter(lead, emailFilter) && leadMatchesCampaignFilter(lead, campaignFilter) && leadMatchesLeadSourceFilter(lead, leadSourceFilter) && leadMatchesSearch(lead, search)), [
207
+ * The choices each select column offers. A stored clause naming a value
208
+ * no longer listed — a retired campaign, a lead source since removed —
209
+ * stays a choice, so the panel can show it and clear it.
210
+ */ const filterOptions = useMemo(()=>{
211
+ const stale = (field, known)=>clauses.filter((clause)=>clause.field === field && clause.op !== 'isEmpty').flatMap((clause)=>clause.value.split(',')).map((value)=>value.trim()).filter((value)=>value && !known.some((option)=>option.value === value)).map((value)=>({
212
+ value,
213
+ label: value
214
+ }));
215
+ const campaignOptions = campaigns.options.map((option)=>({
216
+ value: option.value,
217
+ label: option.label
218
+ }));
219
+ // Salesforce's Lead Source (AGL-3298): every value the org keeps, inactive ones marked.
220
+ const sourceOptions = [
221
+ ...leadSourceList.picklist.values.map((value)=>({
222
+ value: value.label,
223
+ label: value.active ? value.label : `${value.label} (inactive)`
224
+ })),
225
+ {
226
+ value: LEAD_SOURCE_FILTER_NONE,
227
+ label: 'No lead source'
228
+ }
229
+ ];
230
+ return {
231
+ status: LEAD_STATUS_FILTER_OPTIONS,
232
+ emailState: LEAD_EMAIL_FILTER_OPTIONS,
233
+ ownerUid: roster.options.map((option)=>({
234
+ value: option.uid,
235
+ label: Aglyn.crmMemberPickerLabel(option)
236
+ })),
237
+ campaignIds: [
238
+ ...campaignOptions,
239
+ ...stale('campaignIds', campaignOptions)
240
+ ],
241
+ leadSource: [
242
+ ...sourceOptions,
243
+ ...stale('leadSource', sourceOptions)
244
+ ]
245
+ };
246
+ }, [
247
+ clauses,
248
+ campaigns.options,
249
+ leadSourceList.picklist,
250
+ roster.options
251
+ ]);
252
+ /*
253
+ * Every clause and the search narrow the whole loaded WINDOW here, before
254
+ * the footer's count and the page slice (AGL-3246). The grid holds one
255
+ * page of it, so a filter or a quick search the grid ran itself would
256
+ * answer "no match" for a lead on page three.
257
+ */ const searchKey = gridFilter.searchWords.join(' ');
258
+ const rows = useMemo(()=>window.filter((lead)=>leadMatchesClauses(lead, clauses) && leadMatchesSearch(lead, searchKey)), [
303
259
  window,
304
- filter,
305
- emailFilter,
306
- campaignFilter,
307
- leadSourceFilter,
308
- search
260
+ clauses,
261
+ searchKey
309
262
  ]);
310
263
  const [page, setPage] = useState(0);
311
264
  const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
@@ -315,11 +268,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
315
268
  useEffect(()=>{
316
269
  setPage(0);
317
270
  }, [
318
- filter,
319
- emailFilter,
320
- campaignFilter,
321
- leadSourceFilter,
322
- search
271
+ views.state.filters,
272
+ searchKey
323
273
  ]);
324
274
  const pageRows = useMemo(()=>rows.slice(page * pageSize, (page + 1) * pageSize), [
325
275
  rows,
@@ -333,11 +283,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
333
283
  * rows no longer listed.
334
284
  */ const [selectedIds, setSelectedIds] = useState([]);
335
285
  useEffect(()=>setSelectedIds([]), [
336
- filter,
337
- emailFilter,
338
- campaignFilter,
339
- leadSourceFilter,
340
- search
286
+ views.state.filters,
287
+ searchKey
341
288
  ]);
342
289
  // How the file names the owner and, at the org level, the site.
343
290
  const csvOptions = useMemo(()=>_extends({
@@ -719,7 +666,14 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
719
666
  leadFields.active,
720
667
  leadSourceList.picklist
721
668
  ]);
722
- /* The column and sort models are the view's (AGL-2617). */ const grid = useCrmViewGrid(views, columns);
669
+ /*
670
+ * The column and sort models are the view's (AGL-2617); the filterable
671
+ * columns are the declared fields, as selects over their choices (AGL-3313).
672
+ */ const filterColumns = useMemo(()=>crmFilterColumns(columns, LEAD_LIST_FILTER_FIELDS, filterOptions, LEAD_LIST_FILTER_HEADERS), [
673
+ columns,
674
+ filterOptions
675
+ ]);
676
+ const grid = useCrmViewGrid(views, filterColumns);
723
677
  return /*#__PURE__*/ _jsxs(_Fragment, {
724
678
  children: [
725
679
  /*#__PURE__*/ _jsx(CardDisplay, {
@@ -765,157 +719,19 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
765
719
  controller: views,
766
720
  allLabel: "All leads"
767
721
  }),
768
- /*#__PURE__*/ _jsxs(FormControl, {
769
- size: "small",
770
- sx: {
771
- minWidth: 160
772
- },
773
- children: [
774
- /*#__PURE__*/ _jsx(InputLabel, {
775
- id: filterLabelId,
776
- children: 'Show'
777
- }),
778
- /*#__PURE__*/ _jsx(Select, {
779
- labelId: filterLabelId,
780
- label: "Show",
781
- value: filter,
782
- onChange: (event)=>setFilter(event.target.value),
783
- children: LEAD_FILTERS.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
784
- value: option,
785
- children: LEAD_FILTER_LABELS[option]
786
- }, option))
787
- })
788
- ]
789
- }),
790
- /*#__PURE__*/ _jsxs(FormControl, {
791
- size: "small",
792
- sx: {
793
- minWidth: 160
794
- },
795
- children: [
796
- /*#__PURE__*/ _jsx(InputLabel, {
797
- id: emailFilterLabelId,
798
- children: 'Email'
799
- }),
800
- /*#__PURE__*/ _jsx(Select, {
801
- labelId: emailFilterLabelId,
802
- label: "Email",
803
- value: emailFilter,
804
- onChange: (event)=>setEmailFilter(event.target.value),
805
- children: LEAD_EMAIL_FILTERS.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
806
- value: option,
807
- children: LEAD_EMAIL_FILTER_LABELS[option]
808
- }, option))
809
- })
810
- ]
811
- }),
812
- /*#__PURE__*/ _jsxs(FormControl, {
813
- size: "small",
814
- sx: {
815
- minWidth: 180
816
- },
817
- children: [
818
- /*#__PURE__*/ _jsx(InputLabel, {
819
- id: campaignFilterLabelId,
820
- shrink: true,
821
- children: 'Campaign'
822
- }),
823
- /*#__PURE__*/ _jsxs(Select, {
824
- labelId: campaignFilterLabelId,
825
- label: "Campaign",
826
- notched: true,
827
- value: campaignFilter,
828
- onChange: (event)=>setCampaignFilter(String(event.target.value)),
829
- displayEmpty: true,
830
- children: [
831
- /*#__PURE__*/ _jsx(MenuItem, {
832
- value: "",
833
- children: 'Any campaign'
834
- }),
835
- campaigns.options.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
836
- value: option.value,
837
- children: option.label
838
- }, option.value)),
839
- campaignFilter && !campaigns.options.some((option)=>option.value === campaignFilter) ? /*#__PURE__*/ _jsx(MenuItem, {
840
- value: campaignFilter,
841
- children: campaignFilter
842
- }) : null
843
- ]
844
- })
845
- ]
846
- }),
847
- /*#__PURE__*/ _jsxs(FormControl, {
848
- size: "small",
849
- sx: {
850
- minWidth: 180
851
- },
852
- children: [
853
- /*#__PURE__*/ _jsx(InputLabel, {
854
- id: leadSourceFilterLabelId,
855
- shrink: true,
856
- children: 'Lead source'
857
- }),
858
- /*#__PURE__*/ _jsxs(Select, {
859
- labelId: leadSourceFilterLabelId,
860
- label: "Lead source",
861
- notched: true,
862
- value: leadSourceFilter,
863
- onChange: (event)=>setLeadSourceFilter(String(event.target.value)),
864
- displayEmpty: true,
865
- children: [
866
- /*#__PURE__*/ _jsx(MenuItem, {
867
- value: "",
868
- children: 'Any lead source'
869
- }),
870
- leadSourceList.picklist.values.map((value)=>/*#__PURE__*/ _jsx(MenuItem, {
871
- value: value.label,
872
- children: value.active ? value.label : `${value.label} (inactive)`
873
- }, value.id)),
874
- /*#__PURE__*/ _jsx(MenuItem, {
875
- value: LEAD_SOURCE_FILTER_NONE,
876
- children: 'No lead source'
877
- }),
878
- leadSourceFilter && leadSourceFilter !== LEAD_SOURCE_FILTER_NONE && !Aglyn.crmPicklistValueByLabel(leadSourceList.picklist, leadSourceFilter) ? /*#__PURE__*/ _jsx(MenuItem, {
879
- value: leadSourceFilter,
880
- children: leadSourceFilter
881
- }) : null
882
- ]
883
- })
884
- ]
885
- }),
886
- /*#__PURE__*/ _jsx(TextField, {
887
- size: "small",
888
- value: search,
889
- onChange: (event)=>setSearch(event.target.value),
890
- placeholder: "Search leads",
891
- slotProps: {
892
- input: {
893
- startAdornment: /*#__PURE__*/ _jsx(InputAdornment, {
894
- position: "start",
895
- children: /*#__PURE__*/ _jsx(MdiIcon, {
896
- path: mdiMagnify.path,
897
- size: 0.8
898
- })
899
- })
900
- },
901
- htmlInput: {
902
- 'aria-label': 'Search leads',
903
- type: 'search'
904
- }
905
- },
906
- sx: {
907
- minWidth: 200
908
- }
722
+ /*#__PURE__*/ _jsx(CrmFilterBar, {
723
+ fields: LEAD_LIST_FILTER_FIELDS,
724
+ headers: LEAD_LIST_FILTER_HEADERS,
725
+ clauses: clauses,
726
+ onChange: setClauses,
727
+ options: filterOptions,
728
+ marksServed: false
909
729
  })
910
730
  ]
911
731
  }),
912
732
  status === 'success' && window.length === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
913
733
  label: 'No leads yet',
914
734
  description: 'Bookings and lead-routed forms on your site become leads on their own — or add one with New lead, or bring a list in with Import CSV.'
915
- }) : status === 'success' && rows.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
916
- variant: "body2",
917
- color: "text.secondary",
918
- children: `No ${LEAD_FILTER_LABELS[filter].toLowerCase()} leads` + (search.trim() ? ` match “${search.trim()}”` : '') + ` among the ${window.length.toLocaleString()} most recently seen.`
919
735
  }) : /*#__PURE__*/ _jsxs(_Fragment, {
920
736
  children: [
921
737
  /*#__PURE__*/ _jsx(LeadsBulkBar, {
@@ -945,9 +761,13 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
945
761
  onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
946
762
  sortModel: grid.sortModel,
947
763
  onSortModelChange: grid.onSortModelChange,
948
- // The search is the section's, above: the grid's own box
949
- // would search this page alone.
950
- quickFilter: false,
764
+ // The panel and the search are the grid's; the section
765
+ // answers both over the whole window (AGL-3313).
766
+ filterMode: "server",
767
+ filterModel: gridFilter.filterModel,
768
+ onFilterModelChange: gridFilter.onFilterModelChange,
769
+ quickFilter: true,
770
+ noRowsLabel: `No leads match among the ${window.length.toLocaleString()} most recently seen`,
951
771
  // Paged by the footer below, so the grid must not also slice.
952
772
  hideFooter: true
953
773
  })
@@ -964,7 +784,7 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
964
784
  }),
965
785
  truncated ? /*#__PURE__*/ _jsx(Alert, {
966
786
  severity: "info",
967
- children: `Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` + 'leads. The search box and the status filter narrow these ' + `${LEADS_WINDOW.toLocaleString()} only; older leads are still ` + 'listed in the Inbox and reached by campaign audiences.'
787
+ children: `Showing the ${LEADS_WINDOW.toLocaleString()} most recently seen ` + 'leads. The filters and the search narrow these ' + `${LEADS_WINDOW.toLocaleString()} only; older leads are still ` + 'listed in the Inbox and reached by campaign audiences.'
968
788
  }) : null
969
789
  ]
970
790
  })