@aglyn/plugins-crm 1.0.0-beta.198 → 1.0.0-beta.200

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 (105) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/companies-section.d.ts +6 -6
  3. package/src/lib/components/companies-section.js +54 -84
  4. package/src/lib/components/companies-section.js.map +1 -1
  5. package/src/lib/components/company-edit-drawer.js +17 -4
  6. package/src/lib/components/company-edit-drawer.js.map +1 -1
  7. package/src/lib/components/company-picker.js +6 -2
  8. package/src/lib/components/company-picker.js.map +1 -1
  9. package/src/lib/components/contact-detail-page.js +14 -2
  10. package/src/lib/components/contact-detail-page.js.map +1 -1
  11. package/src/lib/components/contact-list-columns.d.ts +3 -5
  12. package/src/lib/components/contact-list-columns.js +6 -8
  13. package/src/lib/components/contact-list-columns.js.map +1 -1
  14. package/src/lib/components/contacts-bulk-bar.js +10 -1
  15. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  16. package/src/lib/components/contacts-section.js +115 -163
  17. package/src/lib/components/contacts-section.js.map +1 -1
  18. package/src/lib/components/crm-next-activity-column.d.ts +4 -2
  19. package/src/lib/components/crm-next-activity-column.js +6 -3
  20. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  21. package/src/lib/components/deal-edit-drawer.js +8 -4
  22. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  23. package/src/lib/components/deals-section.d.ts +2 -2
  24. package/src/lib/components/deals-section.js +59 -84
  25. package/src/lib/components/deals-section.js.map +1 -1
  26. package/src/lib/components/lead-campaigns-card.js +11 -3
  27. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  28. package/src/lib/components/lead-properties-card.js +9 -1
  29. package/src/lib/components/lead-properties-card.js.map +1 -1
  30. package/src/lib/components/leads-bulk-bar.js +16 -3
  31. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  32. package/src/lib/components/leads-section.d.ts +2 -1
  33. package/src/lib/components/leads-section.js +110 -148
  34. package/src/lib/components/leads-section.js.map +1 -1
  35. package/src/lib/components/tasks-section.js +128 -121
  36. package/src/lib/components/tasks-section.js.map +1 -1
  37. package/src/lib/constants/company-filters.d.ts +13 -0
  38. package/src/lib/constants/company-filters.js +47 -17
  39. package/src/lib/constants/company-filters.js.map +1 -1
  40. package/src/lib/constants/contact-filters.d.ts +61 -10
  41. package/src/lib/constants/contact-filters.js +373 -141
  42. package/src/lib/constants/contact-filters.js.map +1 -1
  43. package/src/lib/constants/deal-filters.d.ts +44 -0
  44. package/src/lib/constants/deal-filters.js +78 -0
  45. package/src/lib/constants/deal-filters.js.map +1 -0
  46. package/src/lib/hooks/use-crm-list-query.d.ts +68 -0
  47. package/src/lib/hooks/use-crm-list-query.js +182 -0
  48. package/src/lib/hooks/use-crm-list-query.js.map +1 -0
  49. package/src/lib/hooks/use-crm-tasks.d.ts +23 -6
  50. package/src/lib/hooks/use-crm-tasks.js +44 -42
  51. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  52. package/src/lib/hooks/use-deals.d.ts +2 -34
  53. package/src/lib/hooks/use-deals.js +3 -62
  54. package/src/lib/hooks/use-deals.js.map +1 -1
  55. package/src/lib/model/crm-list-query.d.ts +91 -0
  56. package/src/lib/model/crm-list-query.js +182 -0
  57. package/src/lib/model/crm-list-query.js.map +1 -0
  58. package/src/lib/model/lead-filters.d.ts +49 -43
  59. package/src/lib/model/lead-filters.js +194 -88
  60. package/src/lib/model/lead-filters.js.map +1 -1
  61. package/src/lib/model/list-fields-api.d.ts +26 -0
  62. package/src/lib/model/list-fields-api.js +47 -0
  63. package/src/lib/model/list-fields-api.js.map +1 -0
  64. package/src/lib/model/list-fields.d.ts +47 -0
  65. package/src/lib/model/list-fields.js +39 -0
  66. package/src/lib/model/list-fields.js.map +1 -0
  67. package/src/lib/model/task-views.d.ts +15 -10
  68. package/src/lib/model/task-views.js +68 -9
  69. package/src/lib/model/task-views.js.map +1 -1
  70. package/src/lib/server/companies-import.js +3 -3
  71. package/src/lib/server/companies-import.js.map +1 -1
  72. package/src/lib/server/company-delete.js +3 -1
  73. package/src/lib/server/company-delete.js.map +1 -1
  74. package/src/lib/server/consent-group-participant.js +4 -0
  75. package/src/lib/server/consent-group-participant.js.map +1 -1
  76. package/src/lib/server/contact-update.js +14 -8
  77. package/src/lib/server/contact-update.js.map +1 -1
  78. package/src/lib/server/contacts-import.js +4 -2
  79. package/src/lib/server/contacts-import.js.map +1 -1
  80. package/src/lib/server/deals-import.js +4 -2
  81. package/src/lib/server/deals-import.js.map +1 -1
  82. package/src/lib/server/erase-person.js +2 -1
  83. package/src/lib/server/erase-person.js.map +1 -1
  84. package/src/lib/server/lead-create.js +4 -1
  85. package/src/lib/server/lead-create.js.map +1 -1
  86. package/src/lib/server/lead-source-values.js +9 -5
  87. package/src/lib/server/lead-source-values.js.map +1 -1
  88. package/src/lib/server/leads-import.js +4 -1
  89. package/src/lib/server/leads-import.js.map +1 -1
  90. package/src/lib/server/list-fields-routes.d.ts +29 -0
  91. package/src/lib/server/list-fields-routes.js +85 -0
  92. package/src/lib/server/list-fields-routes.js.map +1 -0
  93. package/src/lib/server/record-email-state.js +5 -2
  94. package/src/lib/server/record-email-state.js.map +1 -1
  95. package/src/lib/server/record-timeline.js +3 -2
  96. package/src/lib/server/record-timeline.js.map +1 -1
  97. package/src/lib/server/task-routes.js +6 -3
  98. package/src/lib/server/task-routes.js.map +1 -1
  99. package/src/lib/server/tasks-import.js +5 -2
  100. package/src/lib/server/tasks-import.js.map +1 -1
  101. package/src/lib/server.js +10 -1
  102. package/src/lib/server.js.map +1 -1
  103. package/src/lib/hooks/use-org-leads.d.ts +0 -50
  104. package/src/lib/hooks/use-org-leads.js +0 -104
  105. package/src/lib/hooks/use-org-leads.js.map +0 -1
@@ -31,21 +31,21 @@ import { useContactFieldDefinitions } from "../hooks/use-contact-field-definitio
31
31
  import { customFieldColumns } from "./contact-custom-columns.js";
32
32
  import { useCrmViewGrid } from "../hooks/use-crm-view-grid.js";
33
33
  import { CRM_LIST_SLOTS, CrmColumnOrderProvider } from "./crm-column-menu.js";
34
- import { useOrgLeads } from "../hooks/use-org-leads.js";
34
+ import { useCrmFoldsScope, useCrmListQuery } from "../hooks/use-crm-list-query.js";
35
35
  import CrmViewsControl from "./crm-views-control.js";
36
36
  import { CrmListActions, CrmListToolbar } from "./crm-list-toolbar.js";
37
37
  import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
38
- import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
39
- import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
38
+ import ListQueryNotices from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
40
39
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
41
- import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
42
- import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Select, Stack, Typography } from "@mui/material";
43
- import { collection, deleteField, doc, limit, orderBy, query, serverTimestamp, updateDoc, where } from "firebase/firestore";
40
+ import { useFirestore } from "@aglyn/tenant-feature-instance";
41
+ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Select, Stack, Typography } from "@mui/material";
42
+ import { deleteField, doc, serverTimestamp, updateDoc } from "firebase/firestore";
44
43
  import { useRouter } from "next/navigation";
45
44
  import { useCallback, useEffect, useMemo, useState } from "react";
46
45
  import { downloadTextFile } from "../model/contacts-csv.js";
47
46
  import { crmRoutes } from "../model/crm-routes.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";
47
+ import { LEAD_EMAIL_FILTER_OPTIONS, LEAD_FILTER_CODECS, LEAD_LIST_DECLARATION, LEAD_LIST_FILTER_FIELDS, LEAD_LIST_FILTER_HEADERS, LEAD_SOURCE_FILTER_NONE, LEAD_STATUS_FILTER_OPTIONS, leadClausesForGrid, LEAD_PREFIX_SEARCH, leadClauseImpliesScope, leadClausesToStore, leadQueryClause } from "../model/lead-filters.js";
48
+ import { crmAskClauses, crmQueryRefusals } from "../model/crm-list-query.js";
49
49
  import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
50
50
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
51
51
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
@@ -64,28 +64,6 @@ import LeadSurfacesNote from "./lead-surfaces-note.js";
64
64
  import { LeadUnqualifyDialog } from "./lead-unqualify-dialog.js";
65
65
  import LeadsBulkBar from "./leads-bulk-bar.js";
66
66
  import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
67
- /**
68
- * How many leads the section reads: the newest by last seen, plus one probe
69
- * row so "there are more" is a fact rather than a guess at the boundary.
70
- *
71
- * A CEILING and a client-side filter rather than a paged status query, and
72
- * the reason is the lead documents themselves. Every lead the capture door
73
- * writes carries NO `status` — the field exists only once somebody in the
74
- * CRM has touched the lead — and Firestore cannot select documents by a
75
- * field's absence: `where('status','in',[…])`, `!=` and `not-in` all skip a
76
- * document without the field. A server-side "open leads" query would
77
- * therefore hide every lead nobody has worked yet, which is the entire
78
- * population the section exists to show on the day it ships. So the query
79
- * is the one order every lead can satisfy (`lastSeenAtMs`, stamped on every
80
- * capture), and the status filter narrows the loaded window — said out loud
81
- * beneath the table whenever the window is not the whole collection.
82
- *
83
- * The window is then PAGED in memory under the shared footer, the way the
84
- * workspace pickers page a slice of a window they cannot re-key: the rows
85
- * are already in the snapshot, so turning a page costs nothing, and the
86
- * footer's count is exact because it counts the filtered window rather than
87
- * a collection nobody has measured.
88
- */ const LEADS_WINDOW = 200;
89
67
  /**
90
68
  * `/crm/leads` — the people a site has met but not yet qualified (AGL-2608).
91
69
  *
@@ -95,7 +73,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
95
73
  * it is real. Reads `orgs/{orgId}/leads` narrowed by `visibleTo` to the sites
96
74
  * this viewer may see (AGL-3275) — the same collection and the same clause at
97
75
  * both levels, which is what lets ONE listener serve a section that used to
98
- * open one per site.
76
+ * open one per site — and pages it by a query that carries every filter and
77
+ * the search word (AGL-3321; see `leadQueryClause`).
99
78
  *
100
79
  * Under a site the clause names that site; at the ORGANIZATION level an
101
80
  * org-wide member reads without one, since the rules short-circuit on
@@ -122,46 +101,19 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
122
101
  const routes = crmRoutes(basePath != null ? basePath : '');
123
102
  /*
124
103
  * Under a site: the ORG collection, narrowed to what this site may see
125
- * (AGL-3275). It read `hosts/{hostId}/leads` until the silo moved, and
126
- * leaving it there would have shown a site its pre-migration rows and
127
- * nothing captured since — then nothing at all, once AGL-3276 emptied the
128
- * path it was reading.
129
- */ const site = useFirestoreCollection(()=>hostId && orgId ? query(collection(firestore, 'orgs', orgId, 'leads'), where('visibleTo', 'array-contains-any', scopeTokensForHost(hostId)), orderBy('lastSeenAtMs', 'desc'), limit(LEADS_WINDOW + 1)) : null, [
130
- firestore,
131
- hostId,
132
- orgId
133
- ], {
134
- idField: '$id'
135
- });
136
- // At the organization level: every site's window, merged.
137
- const orgHostIds = useMemo(()=>{
138
- var _ref;
139
- return hostId ? [] : ((_ref = mount == null ? void 0 : mount.hosts) != null ? _ref : []).map((host)=>host.id);
140
- }, [
141
- hostId,
142
- mount == null ? void 0 : mount.hosts
143
- ]);
144
- /*
145
- * ONE LISTENER (AGL-3275), where this used to fan out across the org's
146
- * sites and merge. At the organization level an org-wide member reads with
147
- * no scope clause, which is what `visibleTo: null` asks for.
148
- */ const orgLeads = useOrgLeads({
149
- orgId,
150
- visibleTo: null,
151
- windowSize: LEADS_WINDOW
152
- });
153
- const leadDocs = useMemo(()=>hostId ? site.data.map((row)=>_extends({}, row, {
154
- leadId: row.$id
155
- })) : orgLeads.data, [
156
- hostId,
157
- site.data,
158
- orgLeads.data
104
+ * (AGL-3275) by `visibleTo array-contains-any` over the site's own tokens.
105
+ * At the ORGANIZATION level an org-wide member reads with no scope clause,
106
+ * which is what `null` asks for.
107
+ */ const scopeTokens = useMemo(()=>hostId ? scopeTokensForHost(hostId) : null, [
108
+ hostId
159
109
  ]);
160
- const status = hostId ? site.status : (mount == null ? void 0 : mount.hostsReady) && !orgHostIds.length ? 'success' : orgLeads.status;
161
- const truncated = hostId ? leadDocs.length > LEADS_WINDOW : orgLeads.truncated;
162
- const window = useMemo(()=>leadDocs.slice(0, LEADS_WINDOW), [
163
- leadDocs
110
+ const dataRoot = useMemo(()=>orgId ? [
111
+ 'orgs',
112
+ orgId
113
+ ] : null, [
114
+ orgId
164
115
  ]);
116
+ const foldsScope = useCrmFoldsScope(orgId, scopeTokens);
165
117
  /*
166
118
  * What the list is narrowed by is the saved VIEW'S (AGL-2617), and the
167
119
  * grid's own Filters panel and quick search edit it (AGL-3313): Status,
@@ -250,32 +202,46 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
250
202
  roster.options
251
203
  ]);
252
204
  /*
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)), [
259
- window,
205
+ * Every clause and the search word on ONE query (AGL-3321): each stored
206
+ * clause asked through the field its writer keeps (`leadQueryClause`),
207
+ * paged by that query, and what one query cannot hold refused by name
208
+ * above the list — never matched over the rows a page happened to load.
209
+ */ const asked = useMemo(()=>crmAskClauses(clauses, (clause)=>leadQueryClause(clause, {
210
+ scopeTokens,
211
+ foldsScope
212
+ })), [
260
213
  clauses,
261
- searchKey
214
+ scopeTokens,
215
+ foldsScope
262
216
  ]);
263
- const [page, setPage] = useState(0);
264
- const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
265
- // A new filter or search term starts on page one: page three of the open
266
- // leads is not a page of the unqualified ones, and an out-of-range page
267
- // renders empty.
268
- useEffect(()=>{
269
- setPage(0);
270
- }, [
271
- views.state.filters,
272
- searchKey
217
+ const searchKey = gridFilter.searchWords.join(' ');
218
+ const paged = useCrmListQuery({
219
+ scope: dataRoot,
220
+ collection: 'leads',
221
+ visibleTo: scopeTokens,
222
+ foldsScope,
223
+ declaration: LEAD_LIST_DECLARATION,
224
+ clauses: asked.clauses,
225
+ search: gridFilter.searchWords,
226
+ impliesScope: leadClauseImpliesScope,
227
+ prefixSearch: LEAD_PREFIX_SEARCH
228
+ });
229
+ const status = paged.status;
230
+ const rows = useMemo(()=>paged.rows.map((row)=>_extends({}, row, {
231
+ leadId: row.$id
232
+ })), [
233
+ paged.rows
273
234
  ]);
274
- const pageRows = useMemo(()=>rows.slice(page * pageSize, (page + 1) * pageSize), [
275
- rows,
276
- page,
277
- pageSize
235
+ const refused = useMemo(()=>crmQueryRefusals(paged.plan, asked, {
236
+ fields: LEAD_LIST_FILTER_FIELDS,
237
+ headers: LEAD_LIST_FILTER_HEADERS,
238
+ options: filterOptions
239
+ }), [
240
+ paged.plan,
241
+ asked,
242
+ filterOptions
278
243
  ]);
244
+ /** The list as it opens — Open leads, nothing typed — whose emptiness is news. */ const opening = !searchKey.trim() && clauses.length === 1 && clauses[0].field === 'status' && clauses[0].value === 'open';
279
245
  /*
280
246
  * The ticked rows, for the bulk bar (AGL-2662). Cleared when the filter or
281
247
  * the search term changes: a selection made on the open leads is not a
@@ -284,7 +250,8 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
284
250
  */ const [selectedIds, setSelectedIds] = useState([]);
285
251
  useEffect(()=>setSelectedIds([]), [
286
252
  views.state.filters,
287
- searchKey
253
+ searchKey,
254
+ paged.page
288
255
  ]);
289
256
  // How the file names the owner and, at the org level, the site.
290
257
  const csvOptions = useMemo(()=>_extends({
@@ -296,8 +263,7 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
296
263
  hostId,
297
264
  mount
298
265
  ]);
299
- // The listed window — every row the filter and the search admit, not just
300
- // the page.
266
+ // The page on screen; Export all on the bulk bar takes the whole list.
301
267
  const handleExport = useCallback(()=>{
302
268
  downloadTextFile('leads.csv', 'text/csv', leadsCsv(rows, csvOptions));
303
269
  }, [
@@ -729,63 +695,59 @@ import OrgLeadSurfacesNote from "./org-lead-surfaces-note.js";
729
695
  })
730
696
  ]
731
697
  }),
732
- status === 'success' && window.length === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
733
- label: 'No leads yet',
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.'
735
- }) : /*#__PURE__*/ _jsxs(_Fragment, {
736
- children: [
737
- /*#__PURE__*/ _jsx(LeadsBulkBar, {
738
- rows: rows,
698
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
699
+ refused: refused,
700
+ notices: paged.plan.notices
701
+ }),
702
+ /*#__PURE__*/ _jsx(LeadsBulkBar, {
703
+ rows: rows,
704
+ selected: selectedIds,
705
+ onSelectedChange: setSelectedIds,
706
+ roster: roster,
707
+ csv: csvOptions,
708
+ orgId: orgId,
709
+ hostId: hostId,
710
+ org: org
711
+ }),
712
+ /*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
713
+ value: grid.columnOrder,
714
+ children: /*#__PURE__*/ _jsx(ListTable, {
715
+ rows: rows,
716
+ columns: grid.columns,
717
+ slots: CRM_LIST_SLOTS,
718
+ selectable: {
739
719
  selected: selectedIds,
740
- onSelectedChange: setSelectedIds,
741
- roster: roster,
742
- csv: csvOptions,
743
- orgId: orgId,
744
- hostId: hostId,
745
- org: org
746
- }),
747
- /*#__PURE__*/ _jsx(CrmColumnOrderProvider, {
748
- value: grid.columnOrder,
749
- children: /*#__PURE__*/ _jsx(ListTable, {
750
- rows: pageRows,
751
- columns: grid.columns,
752
- slots: CRM_LIST_SLOTS,
753
- selectable: {
754
- selected: selectedIds,
755
- onChange: setSelectedIds
756
- },
757
- loading: status === 'loading',
758
- onOpen: (_id, row)=>router.push(routes.lead(row.leadId)),
759
- // Columns and sort are the view's, controlled (AGL-2617).
760
- columnVisibilityModel: grid.columnVisibilityModel,
761
- onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
762
- sortModel: grid.sortModel,
763
- onSortModelChange: grid.onSortModelChange,
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`,
771
- // Paged by the footer below, so the grid must not also slice.
772
- hideFooter: true
773
- })
774
- }),
775
- /*#__PURE__*/ _jsx(ListPagination, {
776
- page: page,
777
- pageSize: pageSize,
778
- rowCount: pageRows.length,
779
- count: rows.length,
780
- onPageChange: setPage,
781
- onPageSizeChange: setPageSize
782
- })
783
- ]
720
+ onChange: setSelectedIds
721
+ },
722
+ loading: status === 'loading',
723
+ onOpen: (_id, row)=>router.push(routes.lead(row.leadId)),
724
+ // Columns are the view's, controlled (AGL-2617). The query
725
+ // orders the list — newest seen first — so the grid sorts
726
+ // nothing itself: a sort over one page would reorder that page.
727
+ columnVisibilityModel: grid.columnVisibilityModel,
728
+ onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
729
+ sortingMode: "server",
730
+ disableColumnSorting: true,
731
+ // The panel and the search are the grid's; the query answers
732
+ // both (AGL-3321).
733
+ filterMode: "server",
734
+ filterModel: gridFilter.filterModel,
735
+ onFilterModelChange: gridFilter.onFilterModelChange,
736
+ quickFilter: true,
737
+ noRowsLabel: opening ? 'No open leads' : 'No leads match these filters',
738
+ noRowsDescription: opening ? '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.' : undefined,
739
+ // Paged by the footer below, so the grid must not also slice.
740
+ hideFooter: true
741
+ })
784
742
  }),
785
- truncated ? /*#__PURE__*/ _jsx(Alert, {
786
- severity: "info",
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.'
788
- }) : null
743
+ /*#__PURE__*/ _jsx(ListPagination, {
744
+ page: paged.page,
745
+ pageSize: paged.pageSize,
746
+ rowCount: rows.length,
747
+ hasMore: paged.hasMore,
748
+ onPageChange: paged.setPage,
749
+ onPageSizeChange: paged.setPageSize
750
+ })
789
751
  ]
790
752
  })
791
753
  }),