@aglyn/plugins-marketing 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.
Files changed (52) hide show
  1. package/package.json +15 -15
  2. package/src/lib/components/campaign-composer.js +14 -6
  3. package/src/lib/components/campaign-composer.js.map +1 -1
  4. package/src/lib/components/campaign-conversions-card.js +6 -0
  5. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  6. package/src/lib/components/campaign-detail-card.js +107 -37
  7. package/src/lib/components/campaign-detail-card.js.map +1 -1
  8. package/src/lib/components/campaign-members-section.d.ts +16 -4
  9. package/src/lib/components/campaign-members-section.js +171 -105
  10. package/src/lib/components/campaign-members-section.js.map +1 -1
  11. package/src/lib/components/campaign-queries.d.ts +11 -0
  12. package/src/lib/components/campaign-queries.js +13 -0
  13. package/src/lib/components/campaign-queries.js.map +1 -1
  14. package/src/lib/components/campaigns-card.d.ts +8 -6
  15. package/src/lib/components/campaigns-card.js +296 -128
  16. package/src/lib/components/campaigns-card.js.map +1 -1
  17. package/src/lib/components/email-compose-card.js +4 -3
  18. package/src/lib/components/email-compose-card.js.map +1 -1
  19. package/src/lib/components/email-detail.js +16 -5
  20. package/src/lib/components/email-detail.js.map +1 -1
  21. package/src/lib/components/email-recipients-card.js +3 -1
  22. package/src/lib/components/email-recipients-card.js.map +1 -1
  23. package/src/lib/components/emails-list-card.d.ts +11 -15
  24. package/src/lib/components/emails-list-card.js +142 -143
  25. package/src/lib/components/emails-list-card.js.map +1 -1
  26. package/src/lib/components/host-experiments-card.component.js +110 -78
  27. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  28. package/src/lib/components/org-experiments-card.js +7 -3
  29. package/src/lib/components/org-experiments-card.js.map +1 -1
  30. package/src/lib/model/campaign-list-query.d.ts +99 -0
  31. package/src/lib/model/campaign-list-query.js +373 -0
  32. package/src/lib/model/campaign-list-query.js.map +1 -0
  33. package/src/lib/model/campaign-members-query.d.ts +37 -0
  34. package/src/lib/model/campaign-members-query.js +120 -0
  35. package/src/lib/model/campaign-members-query.js.map +1 -0
  36. package/src/lib/model/experiment-list-query.d.ts +22 -0
  37. package/src/lib/model/experiment-list-query.js +85 -0
  38. package/src/lib/model/experiment-list-query.js.map +1 -0
  39. package/src/lib/server/campaign-form-flags.d.ts +35 -0
  40. package/src/lib/server/campaign-form-flags.js +52 -0
  41. package/src/lib/server/campaign-form-flags.js.map +1 -0
  42. package/src/lib/server/campaign-manage.d.ts +1 -1
  43. package/src/lib/server/campaign-manage.js +42 -20
  44. package/src/lib/server/campaign-manage.js.map +1 -1
  45. package/src/lib/server/campaign-send.d.ts +42 -1
  46. package/src/lib/server/campaign-send.js +247 -16
  47. package/src/lib/server/campaign-send.js.map +1 -1
  48. package/src/lib/server/email-events.js +13 -1
  49. package/src/lib/server/email-events.js.map +1 -1
  50. package/src/lib/components/email-send-filter.d.ts +0 -32
  51. package/src/lib/components/email-send-filter.js +0 -55
  52. package/src/lib/components/email-send-filter.js.map +0 -1
@@ -20,34 +20,32 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
20
20
  import { mdiBullhornOutline, mdiContentCopy, mdiDeleteOutline, mdiEyeOutline, mdiPaletteOutline } from "@aglyn/shared-data-mdi";
21
21
  import { AppLink, CardDisplay, MdiIcon, useConfirmationContext } from "@aglyn/shared-ui-jsx";
22
22
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
23
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
24
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
23
25
  import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
24
- import { inMemoryListField } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
25
- import { useListRowsFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-rows-filter";
26
+ import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
27
+ import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
28
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
26
29
  import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
27
30
  import { CreateArtifactDrawer } from "@aglyn/shared-ui-jsx-forms";
28
31
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
29
- import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
32
+ import { collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
33
+ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
30
34
  import { DUPLICATE_MENU_LABEL, useDuplicateResource, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
31
- import { Alert, Button, Chip, Stack, Typography } from "@mui/material";
35
+ import { Button, Chip, Stack, Typography } from "@mui/material";
32
36
  import { useRouter } from "next/navigation";
33
37
  import { useCallback, useMemo, useState } from "react";
34
38
  import { campaignPlacedOnHost, campaignSendDisplay, CAMPAIGN_SEND_CONTAINER_FIELD } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
35
39
  import { emailListTimeMs, emailSendTimeMs } from "@aglyn/shared-ui-email-campaigns/model/email-record";
36
40
  import { useCampaignManageApi, useCampaignSendApi } from "./use-campaign-send-api.js";
37
- import { EMAIL_STATE_FILTER_FIELD, EMAIL_STATE_OPTIONS, EMAIL_SUBJECT_FILTER_FIELD, emailFilterValues } from "./email-send-filter.js";
41
+ import { CAMPAIGN_SEND_STATUS_OPTIONS, SINGLE_SEND_FILTER_VALUE, campaignEmailsListQuery, campaignSendsScope, emailCampaignQueryClauses } from "../model/campaign-list-query.js";
38
42
  import { useMarketingHubPath } from "./use-marketing-hub-path.js";
39
- import { campaignContainersQuery, campaignSendsQuery } from "./campaign-queries.js";
43
+ import { campaignContainersQuery, campaignSendsCollection } from "./campaign-queries.js";
40
44
  import { orgSiteHubPath, orgSiteName, orgSiteOptions, useMarketingOrgId, useMarketingOrgMount } from "./marketing-org-mount.js";
41
- /** How many messages one read of this list covers. */ const EMAIL_CEILING = 30;
42
45
  /**
43
46
  * How many campaigns the Campaign filter and the create drawer offer — the
44
- * same read serves both.
47
+ * same read serves both. A picker's options, not a list.
45
48
  */ const CONTAINER_CEILING = 50;
46
- /**
47
- * The Campaign filter's value for an email filed under no campaign. An empty
48
- * value reads as "no value" to the filter, so a clause on it would never
49
- * apply; a sentinel is a value like any campaign id.
50
- */ const SINGLE_SEND_FILTER_VALUE = '__single__';
51
49
  /**
52
50
  * Why discard is refused, keyed by the state that refuses it.
53
51
  *
@@ -75,31 +73,22 @@ import { orgSiteHubPath, orgSiteName, orgSiteOptions, useMarketingOrgId, useMark
75
73
  * are neutral, because a finished send and a draft are not events.
76
74
  */ const STATE_COLOR = {
77
75
  sending: 'info',
78
- stopped: 'warning'
76
+ stopped: 'warning',
77
+ // Waiting on staff, not on the merchant (AGL-3356): worth finding on a scan.
78
+ held: 'warning'
79
79
  };
80
80
  /*
81
- * What the messages grid's Filters panel offers (AGL-3317). The card reads
82
- * every message under its ceiling, so the panel and the search answer over
83
- * all of them. State is what the email is DOING (`campaignSendDisplay`),
84
- * not the status it stores.
85
- */ const EMAIL_FILTER_FIELDS = [
86
- EMAIL_SUBJECT_FILTER_FIELD,
87
- EMAIL_STATE_FILTER_FIELD,
88
- // Matched on a derived key, so the row's own container field stays what
89
- // was stored for the row menu that reads it.
90
- inMemoryListField('emailCampaignId', 'select', 'campaignFilterKey'),
91
- inMemoryListField('site', 'text', 'siteName')
92
- ];
93
- const EMAIL_FILTER_HEADERS = {
81
+ * What the messages grid's Filters panel offers, each clause and the search
82
+ * on the list's Firestore query (AGL-3321, `campaignEmailsListQuery`). The
83
+ * State column is what the email is DOING (`campaignSendDisplay`), which no
84
+ * query can ask, so the panel offers the Status it STORES instead.
85
+ */ const EMAIL_FILTER_HEADERS = {
94
86
  subject: 'Subject',
95
- state: 'State',
87
+ status: 'Status',
96
88
  emailCampaignId: 'Campaign',
97
- site: 'Site'
89
+ site: 'Site',
90
+ createdAtMs: 'Created'
98
91
  };
99
- /** What the quick search reads on a message row. */ const EMAIL_SEARCH_FIELDS = [
100
- 'subjectText',
101
- 'siteName'
102
- ];
103
92
  const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
104
93
  anchor: '#opens--clicks',
105
94
  excerpt: 'Every message this site has sent or has scheduled, each with its own ' + 'report: what was delivered, who opened it, and which links they followed.'
@@ -112,23 +101,19 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
112
101
  * 14th, and to whom" — and they were previously the same list because a
113
102
  * campaign document WAS a single send.
114
103
  *
115
- * ## Ordered in the browser, deliberately
116
- *
117
- * No SEND date is on every message: a sent one carries `sentAt` and a
118
- * scheduled one carries `sendAtMs`, written by two different branches of the
119
- * send path, and a draft carries neither — so `orderBy` on either would not
120
- * mis-sort this list, it would DROP half of it. `collectionCeiling` reads a
121
- * bounded window in document-id order and probes one past the ceiling, so the
122
- * rows are sorted here and the reader is told when there are more.
104
+ * ## Asked of Firestore, a page at a time
123
105
  *
124
- * Every writer stamps a `createdAtMs`, which is the field this list could be
125
- * ordered on in Firestore. Moving it there is a query change with the same
126
- * hazard as the two above — `orderBy` drops a document missing the field —
127
- * so it needs the corpus proven to carry one, not just the writers.
106
+ * No SEND date is on every message — a sent one carries `sentAt`, a
107
+ * scheduled one `sendAtMs`, a draft neither — so the list is ordered on
108
+ * `createdAtMs`, the one date every writer stamps when it mints a record
109
+ * (and `backfill-campaign-list-fields.mjs` stamped on the ones before).
110
+ * Newest first, paged by the query itself.
128
111
  *
129
- * The page is therefore a SLICE of a window this card already holds, not a
130
- * query: paging an id-ordered walk and re-sorting each page by date would run
131
- * in one order within a page and another across them.
112
+ * The Filters panel and the search box are the grid's; every clause and the
113
+ * search word go onto that ONE query (AGL-3321, `campaignEmailsListQuery`),
114
+ * so each page is a page of matches — an email that matches on page four is
115
+ * found — and a combination one query cannot hold is refused by name above
116
+ * the table, never answered over the rows that happen to be loaded.
132
117
  *
133
118
  * ## Under a site, and over the organization
134
119
  *
@@ -147,32 +132,34 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
147
132
  const siteMarketingHub = useMarketingHubPath();
148
133
  const marketingHub = orgMount ? orgMount.basePath : siteMarketingHub;
149
134
  const router = useRouter();
150
- const { data: emailDocs } = useFirestoreCollection(()=>{
151
- const sends = campaignSendsQuery(firestore, orgId, hostId);
152
- return sends ? collectionCeiling(sends, EMAIL_CEILING) : null;
153
- }, [
154
- firestore,
155
- orgId,
156
- hostId
157
- ], {
135
+ /*
136
+ * The Filters panel and the search, bound to the grid. Held here rather
137
+ * than in a saved view: this list keeps no views.
138
+ */ const declaration = campaignEmailsListQuery(!hostId);
139
+ const gridFilter = useListGridFilter({
140
+ selectFields: [
141
+ 'status',
142
+ 'emailCampaignId',
143
+ 'site'
144
+ ]
145
+ });
146
+ const emailPage = useListQuery({
147
+ collection: campaignSendsCollection(firestore, orgId),
148
+ declaration,
149
+ request: {
150
+ clauses: emailCampaignQueryClauses(gridFilter.clauses),
151
+ search: gridFilter.searchWords,
152
+ base: campaignSendsScope(hostId)
153
+ },
154
+ deps: [
155
+ firestore,
156
+ orgId,
157
+ hostId
158
+ ],
158
159
  idField: '$id'
159
160
  });
160
- const { rows: readEmails, truncated } = ceilingedWindow(emailDocs, EMAIL_CEILING);
161
- /*
162
- * Newest first on the time each message SITS at — its send time where it
163
- * has one, its creation where it does not.
164
- *
165
- * A draft has neither `sentAt` nor `sendAtMs`, so sorting on the send time
166
- * alone gave every draft the key 0 and filed the email a merchant is in the
167
- * middle of writing at the very bottom of the list, behind whatever paging
168
- * it has. `emailListTimeMs` is the same ordering with that one gap closed;
169
- * a SENT message still orders by when it went out, never by when it was
170
- * drafted.
171
- */ const emails = useMemo(()=>[
172
- ...readEmails
173
- ].sort((a, b)=>emailListTimeMs(b) - emailListTimeMs(a)), [
174
- readEmails
175
- ]);
161
+ const emails = emailPage.rows;
162
+ const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word)=>word.trim());
176
163
  /*
177
164
  * A message's own pages, beneath the Emails page this list is on — the
178
165
  * site's or the organization's. A template is a site's design, so over the
@@ -183,11 +170,11 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
183
170
  /** The site a row is sent as, which every action on it must name. */ const rowHostId = useCallback((email)=>hostId != null ? hostId : (email == null ? void 0 : email.hostId) ? String(email.hostId) : null, [
184
171
  hostId
185
172
  ]);
186
- const hostOfEmail = useMemo(()=>new Map(readEmails.map((email)=>[
173
+ const hostOfEmail = useMemo(()=>new Map(emails.map((email)=>[
187
174
  String(email.$id),
188
175
  (email == null ? void 0 : email.hostId) ? String(email.hostId) : null
189
176
  ])), [
190
- readEmails
177
+ emails
191
178
  ]);
192
179
  const { confirm } = useConfirmationContext();
193
180
  const { enqueueSnackbar } = useSnackbar();
@@ -406,18 +393,8 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
406
393
  })).sort((a, b)=>a.label.localeCompare(b.label)), [
407
394
  campaignDocs
408
395
  ]);
409
- const filterRows = useMemo(()=>emails.map((email)=>_extends({}, email, emailFilterValues(email), {
410
- campaignFilterKey: email.emailCampaignId ? String(email.emailCampaignId) : SINGLE_SEND_FILTER_VALUE,
411
- siteName: orgMount && (email == null ? void 0 : email.hostId) ? orgSiteName(orgMount, email.hostId) : ''
412
- })), [
413
- emails,
414
- orgMount
415
- ]);
416
- const filterFields = useMemo(()=>orgMount ? EMAIL_FILTER_FIELDS : EMAIL_FILTER_FIELDS.filter((field)=>field.column !== 'site'), [
417
- orgMount
418
- ]);
419
- const filterOptions = useMemo(()=>({
420
- state: EMAIL_STATE_OPTIONS,
396
+ const filterOptions = useMemo(()=>_extends({
397
+ status: CAMPAIGN_SEND_STATUS_OPTIONS,
421
398
  emailCampaignId: [
422
399
  {
423
400
  value: SINGLE_SEND_FILTER_VALUE,
@@ -425,16 +402,13 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
425
402
  },
426
403
  ...campaignOptions
427
404
  ]
405
+ }, hostId ? {} : {
406
+ site: orgSites
428
407
  }), [
429
- campaignOptions
408
+ campaignOptions,
409
+ hostId,
410
+ orgSites
430
411
  ]);
431
- const listFilter = useListRowsFilter({
432
- rows: filterRows,
433
- fields: filterFields,
434
- options: filterOptions,
435
- headers: EMAIL_FILTER_HEADERS,
436
- search: EMAIL_SEARCH_FIELDS
437
- });
438
412
  /*==========================================
439
413
  * CREATE, THEN GO TO THE EMAIL'S OWN PAGE.
440
414
  *
@@ -520,9 +494,9 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
520
494
  router
521
495
  ]);
522
496
  /*
523
- * One row per message, in the order above; the whole window is in hand, so
524
- * the grid pages it. The subject is a link AND the row opens the report: a
525
- * click handler cannot be middle-clicked or opened in a new tab.
497
+ * One row per message, in the query's order. The subject is a link AND the
498
+ * row opens the report: a click handler cannot be middle-clicked or opened
499
+ * in a new tab.
526
500
  */ const columns = [
527
501
  {
528
502
  field: 'subject',
@@ -573,8 +547,6 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
573
547
  field: 'when',
574
548
  headerName: 'When',
575
549
  width: 190,
576
- // Sorted on the time the list is ordered by, so a draft sorts by its
577
- // creation rather than as the oldest thing on the page.
578
550
  valueGetter: (_value, row)=>emailListTimeMs(row),
579
551
  renderCell: ({ row })=>{
580
552
  const at = emailSendTimeMs(row);
@@ -615,6 +587,8 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
615
587
  width: 72
616
588
  })
617
589
  ];
590
+ const gridColumns = listFilterGridColumns(columns, declaration.fields, filterOptions, EMAIL_FILTER_HEADERS);
591
+ /** Nothing stored at all — not a filter that matched nothing. */ const none = emailPage.status !== 'loading' && emails.length === 0 && !filtering && emailPage.page === 0;
618
592
  return /*#__PURE__*/ _jsxs(CardDisplay, {
619
593
  header: 'Messages',
620
594
  help: emailsDocsHelp,
@@ -630,52 +604,77 @@ const emailsDocsHelp = pluginDocsHelp('emailCampaigns', {
630
604
  contentGutterY: true,
631
605
  children: [
632
606
  duplicate.dialog,
633
- /*#__PURE__*/ _jsxs(Stack, {
607
+ /*#__PURE__*/ _jsx(Stack, {
634
608
  spacing: 2,
635
- children: [
636
- emails.length === 0 ? /*#__PURE__*/ _jsxs(Stack, {
637
- spacing: 2,
638
- sx: {
639
- alignItems: 'flex-start'
640
- },
641
- children: [
642
- /*#__PURE__*/ _jsx(Typography, {
643
- variant: "body2",
644
- color: "text.secondary",
645
- children: 'Nothing has been sent or scheduled yet. Write one here, or ' + 'from a campaign, and it appears in this list with its own ' + 'report.'
609
+ children: none ? /*#__PURE__*/ _jsxs(Stack, {
610
+ spacing: 2,
611
+ sx: {
612
+ alignItems: 'flex-start'
613
+ },
614
+ children: [
615
+ /*#__PURE__*/ _jsx(Typography, {
616
+ variant: "body2",
617
+ color: "text.secondary",
618
+ children: 'Nothing has been sent or scheduled yet. Write one here, or ' + 'from a campaign, and it appears in this list with its own ' + 'report.'
619
+ }),
620
+ /*#__PURE__*/ _jsx(Button, {
621
+ variant: "contained",
622
+ onClick: ()=>setCreateOpen(true),
623
+ children: 'New email'
624
+ })
625
+ ]
626
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
627
+ children: [
628
+ /*#__PURE__*/ _jsx(ListFilterChips, {
629
+ fields: declaration.fields,
630
+ headers: EMAIL_FILTER_HEADERS,
631
+ clauses: gridFilter.clauses,
632
+ onChange: gridFilter.setClauses,
633
+ options: filterOptions
634
+ }),
635
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
636
+ refused: listQueryRefusals(emailPage.plan.refused, {
637
+ fields: declaration.fields,
638
+ headers: EMAIL_FILTER_HEADERS,
639
+ options: filterOptions
646
640
  }),
647
- /*#__PURE__*/ _jsx(Button, {
648
- variant: "contained",
649
- onClick: ()=>setCreateOpen(true),
650
- children: 'New email'
651
- })
652
- ]
653
- }) : /*#__PURE__*/ _jsxs(_Fragment, {
654
- children: [
655
- /*#__PURE__*/ _jsx(ListFilterChips, _extends({}, listFilter.chipsProps)),
656
- /*#__PURE__*/ _jsx(ListTable, _extends({
657
- "aria-label": "Messages",
658
- rows: listFilter.rows,
659
- columns: listFilter.filterColumns(columns),
660
- rowHeight: TABLE_ROW_HEIGHT,
661
- onOpen: (_id, row)=>router.push(emailHref(row))
662
- }, listFilter.gridProps, {
663
- initialState: {
664
- columns: {
665
- columnVisibilityModel: {
666
- emailCampaignId: false
667
- }
668
- }
669
- },
670
- noRowsLabel: "No messages match these filters"
671
- }))
672
- ]
673
- }),
674
- truncated ? /*#__PURE__*/ _jsx(Alert, {
675
- severity: "info",
676
- children: `Showing ${EMAIL_CEILING} messages. ${hostId ? 'This site' : 'This organization'} has sent or ` + 'scheduled more than that, and the rest are not in this list.'
677
- }) : null
678
- ]
641
+ notices: emailPage.plan.notices
642
+ }),
643
+ /*#__PURE__*/ _jsx(ListTable, {
644
+ "aria-label": "Messages",
645
+ rows: emails,
646
+ columns: gridColumns,
647
+ rowHeight: TABLE_ROW_HEIGHT,
648
+ onOpen: (_id, row)=>router.push(emailHref(row)),
649
+ loading: emailPage.status === 'loading',
650
+ /*
651
+ * The panel and the search are the grid's; every clause and
652
+ * the search word are on the list's query (AGL-3321), and the
653
+ * grid neither filters nor sorts the page it is handed: the
654
+ * query's one order, newest first, is the list's.
655
+ */ filterMode: "server",
656
+ filterModel: gridFilter.filterModel,
657
+ onFilterModelChange: gridFilter.onFilterModelChange,
658
+ quickFilter: true,
659
+ disableColumnSorting: true,
660
+ hideFooter: true,
661
+ initialState: {
662
+ columns: {
663
+ columnVisibilityModel: hiddenFilterVisibility(declaration.fields, columns.map((column)=>column.field))
664
+ }
665
+ },
666
+ noRowsLabel: "No messages match these filters"
667
+ }),
668
+ /*#__PURE__*/ _jsx(ListPagination, {
669
+ page: emailPage.page,
670
+ pageSize: emailPage.pageSize,
671
+ rowCount: emails.length,
672
+ hasMore: emailPage.hasMore,
673
+ onPageChange: emailPage.setPage,
674
+ onPageSizeChange: emailPage.setPageSize
675
+ })
676
+ ]
677
+ })
679
678
  }),
680
679
  /*#__PURE__*/ _jsx(CreateArtifactDrawer, {
681
680
  open: createOpen,