@aglyn/plugins-outreach 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.
- package/package.json +14 -14
- package/src/lib/components/do-not-contact-domains.js +60 -42
- package/src/lib/components/do-not-contact-domains.js.map +1 -1
- package/src/lib/components/enrollments-table.d.ts +4 -2
- package/src/lib/components/enrollments-table.js +70 -89
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/sequence-detail.js +3 -3
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequences-section.d.ts +9 -0
- package/src/lib/components/sequences-section.js +96 -82
- package/src/lib/components/sequences-section.js.map +1 -1
- package/src/lib/components/use-outreach-data.d.ts +30 -25
- package/src/lib/components/use-outreach-data.js +93 -104
- package/src/lib/components/use-outreach-data.js.map +1 -1
- package/src/lib/enrollment/enrollment-search.d.ts +32 -0
- package/src/lib/enrollment/enrollment-search.js +40 -0
- package/src/lib/enrollment/enrollment-search.js.map +1 -0
- package/src/lib/model/do-not-contact-domain-list-query.d.ts +32 -0
- package/src/lib/model/do-not-contact-domain-list-query.js +107 -0
- package/src/lib/model/do-not-contact-domain-list-query.js.map +1 -0
- package/src/lib/model/enrollment-list-query.d.ts +22 -0
- package/src/lib/model/enrollment-list-query.js +117 -0
- package/src/lib/model/enrollment-list-query.js.map +1 -0
- package/src/lib/model/outreach.types.d.ts +12 -0
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sequence-list-query.d.ts +22 -0
- package/src/lib/model/sequence-list-query.js +99 -0
- package/src/lib/model/sequence-list-query.js.map +1 -0
- package/src/lib/routes/enroll-routes.js +8 -0
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +6 -1
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/storage/do-not-contact-store.js +6 -1
- package/src/lib/storage/do-not-contact-store.js.map +1 -1
|
@@ -23,17 +23,19 @@ import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.com
|
|
|
23
23
|
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
24
24
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
25
25
|
import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
26
|
+
import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
|
|
27
|
+
import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
|
|
28
|
+
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
28
29
|
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
30
|
+
import { Button, Card, CardContent, Chip, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
|
|
31
|
+
import { useMemo } from "react";
|
|
31
32
|
import { OUTREACH_SEQUENCE_STATUSES } from "../model/outreach.types.js";
|
|
33
|
+
import { OUTREACH_SEQUENCE_LIST_QUERY } from "../model/sequence-list-query.js";
|
|
32
34
|
import { OUTREACH_SEQUENCE_STATUS_LABELS, OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
|
|
33
35
|
import { OutreachSequenceDetail } from "./sequence-detail.js";
|
|
34
36
|
import { OutreachSequenceEditor } from "./sequence-editor.js";
|
|
35
37
|
import { useOutreachApi } from "./use-outreach-api.js";
|
|
36
|
-
import { useOutreachSequenceCounts,
|
|
38
|
+
import { useOutreachSequenceCounts, useOutreachSequenceList } from "./use-outreach-data.js";
|
|
37
39
|
import { useOutreachMailboxes } from "./use-outreach-mailboxes.js";
|
|
38
40
|
import { useOutreachComplianceSettings } from "./use-outreach-settings.js";
|
|
39
41
|
/** The new-sequence page's segment below the section. */ export const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new';
|
|
@@ -121,85 +123,76 @@ const COUNT_COLUMNS = [
|
|
|
121
123
|
]
|
|
122
124
|
];
|
|
123
125
|
/*
|
|
124
|
-
* What the sequences grid's Filters panel offers (AGL-3317)
|
|
125
|
-
*
|
|
126
|
-
|
|
127
|
-
*/ const SEQUENCE_FILTER_FIELDS = [
|
|
128
|
-
inMemoryListField('name', 'text'),
|
|
129
|
-
inMemoryListField('mailbox', 'text'),
|
|
130
|
-
inMemoryListField('status', 'select')
|
|
131
|
-
];
|
|
126
|
+
* What the sequences grid's Filters panel offers (AGL-3317), each field on
|
|
127
|
+
* the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).
|
|
128
|
+
*/ const SEQUENCE_FILTER_FIELDS = OUTREACH_SEQUENCE_LIST_QUERY.fields;
|
|
132
129
|
const SEQUENCE_FILTER_HEADERS = {
|
|
133
130
|
name: 'Name',
|
|
134
131
|
mailbox: 'Mailbox',
|
|
135
|
-
status: 'Status'
|
|
136
|
-
|
|
137
|
-
const SEQUENCE_FILTER_OPTIONS = {
|
|
138
|
-
status: OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
139
|
-
value: status,
|
|
140
|
-
label: OUTREACH_SEQUENCE_STATUS_LABELS[status]
|
|
141
|
-
}))
|
|
132
|
+
status: 'Status',
|
|
133
|
+
created: 'Created'
|
|
142
134
|
};
|
|
143
|
-
/**
|
|
135
|
+
/** `created` is no column of its own: a filter-only column, never shown. */ const SEQUENCE_HIDDEN_COLUMNS = hiddenFilterVisibility(SEQUENCE_FILTER_FIELDS, [
|
|
144
136
|
'name',
|
|
145
|
-
'mailbox'
|
|
146
|
-
|
|
137
|
+
'mailbox',
|
|
138
|
+
'status'
|
|
139
|
+
]);
|
|
140
|
+
const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
141
|
+
value: status,
|
|
142
|
+
label: OUTREACH_SEQUENCE_STATUS_LABELS[status]
|
|
143
|
+
}));
|
|
144
|
+
/** How a mailbox reads in the list: the address it sends as. */ const mailboxName = (mailbox)=>mailbox.sendAs || mailbox.email;
|
|
147
145
|
/**
|
|
148
146
|
* Every sequence: its mailbox, its status, and how many people it enrolled
|
|
149
147
|
* and where they stand — a table on wider screens, cards on a phone.
|
|
148
|
+
*
|
|
149
|
+
* ## Asked of Firestore, a page at a time
|
|
150
|
+
*
|
|
151
|
+
* The Filters panel and the search box are the grid's; the clauses and the
|
|
152
|
+
* words go onto ONE query over the organization's sequences, newest first,
|
|
153
|
+
* and each page is a page of that query's matches (AGL-3321). A clause the
|
|
154
|
+
* query cannot hold beside the others is not applied, and the notice above
|
|
155
|
+
* the list says which and why — never answered over the sequences that
|
|
156
|
+
* happen to be loaded.
|
|
150
157
|
*/ export function OutreachSequenceList(props) {
|
|
151
158
|
const { orgId, sectionPath } = props;
|
|
152
159
|
const theme = useTheme();
|
|
153
160
|
const narrow = useMediaQuery(theme.breakpoints.down('md'));
|
|
154
161
|
const navigate = useOutreachNavigate();
|
|
155
|
-
const sequences = useOutreachSequences(orgId);
|
|
156
|
-
const [page, setPage] = useState(0);
|
|
157
|
-
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
158
162
|
const gridFilter = useListGridFilter({
|
|
159
163
|
selectFields: [
|
|
160
|
-
'status'
|
|
164
|
+
'status',
|
|
165
|
+
'mailbox'
|
|
161
166
|
]
|
|
162
167
|
});
|
|
168
|
+
const sequences = useOutreachSequenceList(orgId, {
|
|
169
|
+
clauses: gridFilter.clauses,
|
|
170
|
+
search: gridFilter.searchWords
|
|
171
|
+
});
|
|
172
|
+
const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word)=>word.trim());
|
|
173
|
+
const filterOptions = useMemo(()=>({
|
|
174
|
+
status: SEQUENCE_STATUS_OPTIONS,
|
|
175
|
+
mailbox: props.mailboxes.map((mailbox)=>({
|
|
176
|
+
value: mailbox.id,
|
|
177
|
+
label: mailboxName(mailbox)
|
|
178
|
+
}))
|
|
179
|
+
}), [
|
|
180
|
+
props.mailboxes
|
|
181
|
+
]);
|
|
163
182
|
const mailboxLabel = (sequence)=>{
|
|
164
183
|
const mailbox = props.mailboxes.find((entry)=>entry.id === sequence.mailboxId);
|
|
165
|
-
return mailbox ? mailbox
|
|
184
|
+
return mailbox ? mailboxName(mailbox) : sequence.mailboxId ? 'Mailbox removed' : 'No mailbox yet';
|
|
166
185
|
};
|
|
167
|
-
|
|
168
|
-
* Filtered BEFORE the page is cut: the list read every sequence (under
|
|
169
|
-
* `OUTREACH_SEQUENCES_LIMIT`), so a clause or a search word answers over
|
|
170
|
-
* all of them, and the footer counts the matches.
|
|
171
|
-
*/ const searchKey = gridFilter.searchWords.join(' ');
|
|
172
|
-
const matching = useMemo(()=>filterListRows(sequences.data.map((sequence)=>({
|
|
173
|
-
sequence,
|
|
174
|
-
name: sequence.name || 'Untitled',
|
|
175
|
-
mailbox: mailboxLabel(sequence),
|
|
176
|
-
status: sequence.status
|
|
177
|
-
})), SEQUENCE_FILTER_FIELDS, gridFilter.clauses, {
|
|
178
|
-
paths: SEQUENCE_SEARCH_FIELDS,
|
|
179
|
-
words: gridFilter.searchWords
|
|
180
|
-
}).map((row)=>row.sequence), // `searchKey` stands for the words, which are a new array each render.
|
|
181
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
182
|
-
[
|
|
183
|
-
sequences.data,
|
|
184
|
-
props.mailboxes,
|
|
185
|
-
gridFilter.clauses,
|
|
186
|
-
searchKey
|
|
187
|
-
]);
|
|
188
|
-
// A narrowed list starts again at its first page.
|
|
189
|
-
useEffect(()=>setPage(0), [
|
|
190
|
-
gridFilter.clauses,
|
|
191
|
-
searchKey
|
|
192
|
-
]);
|
|
193
|
-
const pageRows = matching.slice(page * pageSize, (page + 1) * pageSize);
|
|
186
|
+
const pageRows = sequences.rows;
|
|
194
187
|
// Counted for the page on screen only: five aggregations a sequence.
|
|
195
188
|
const counts = useOutreachSequenceCounts(orgId, pageRows.map((sequence)=>sequence.id));
|
|
196
189
|
const footer = /*#__PURE__*/ _jsx(ListPagination, {
|
|
197
|
-
page: page,
|
|
198
|
-
pageSize: pageSize,
|
|
190
|
+
page: sequences.page,
|
|
191
|
+
pageSize: sequences.pageSize,
|
|
199
192
|
rowCount: pageRows.length,
|
|
200
|
-
|
|
201
|
-
onPageChange: setPage,
|
|
202
|
-
onPageSizeChange: setPageSize
|
|
193
|
+
hasMore: sequences.hasMore,
|
|
194
|
+
onPageChange: sequences.setPage,
|
|
195
|
+
onPageSizeChange: sequences.setPageSize
|
|
203
196
|
});
|
|
204
197
|
const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`;
|
|
205
198
|
const newButton = /*#__PURE__*/ _jsx(Button, {
|
|
@@ -216,30 +209,38 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
216
209
|
children: "New sequence"
|
|
217
210
|
});
|
|
218
211
|
const count = (sequence, key)=>counts[sequence.id] ? String(counts[sequence.id][key]) : '—';
|
|
219
|
-
/*
|
|
220
|
-
* The read stops at `OUTREACH_SEQUENCES_LIMIT`, newest first, and probes one
|
|
221
|
-
* past it, so this shows only when older sequences really exist.
|
|
222
|
-
*/ const truncatedNotice = sequences.truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
223
|
-
severity: "info",
|
|
224
|
-
children: `Showing the ${sequences.data.length.toLocaleString()} newest sequences. ` + 'The filters and the search narrow these only; older sequences are ' + 'not listed here.'
|
|
225
|
-
}) : null;
|
|
226
212
|
const chips = /*#__PURE__*/ _jsx(ListFilterChips, {
|
|
227
213
|
fields: SEQUENCE_FILTER_FIELDS,
|
|
228
214
|
headers: SEQUENCE_FILTER_HEADERS,
|
|
229
215
|
clauses: gridFilter.clauses,
|
|
230
216
|
onChange: gridFilter.setClauses,
|
|
231
|
-
options:
|
|
217
|
+
options: filterOptions
|
|
218
|
+
});
|
|
219
|
+
const notices = /*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
220
|
+
refused: listQueryRefusals(sequences.plan.refused, {
|
|
221
|
+
fields: SEQUENCE_FILTER_FIELDS,
|
|
222
|
+
headers: SEQUENCE_FILTER_HEADERS,
|
|
223
|
+
options: filterOptions
|
|
224
|
+
}),
|
|
225
|
+
notices: sequences.plan.notices
|
|
232
226
|
});
|
|
227
|
+
/** Nothing stored at all — not a filter that matched nothing. */ const none = sequences.status === 'ready' && !pageRows.length && !filtering && sequences.page === 0;
|
|
233
228
|
let body;
|
|
234
|
-
if (sequences.status === 'loading') body = /*#__PURE__*/ _jsx(OutreachLoading, {
|
|
229
|
+
if (sequences.status === 'loading' && !filtering) body = /*#__PURE__*/ _jsx(OutreachLoading, {
|
|
235
230
|
label: "Loading sequences…"
|
|
236
231
|
});
|
|
237
232
|
else if (sequences.status === 'error' || sequences.status === 'refused') {
|
|
238
|
-
body = /*#__PURE__*/
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
body = /*#__PURE__*/ _jsxs(Stack, {
|
|
234
|
+
spacing: 1,
|
|
235
|
+
children: [
|
|
236
|
+
chips,
|
|
237
|
+
/*#__PURE__*/ _jsx(OutreachLoadProblem, {
|
|
238
|
+
status: sequences.status,
|
|
239
|
+
what: "sequences"
|
|
240
|
+
})
|
|
241
|
+
]
|
|
241
242
|
});
|
|
242
|
-
} else if (
|
|
243
|
+
} else if (none) {
|
|
243
244
|
body = /*#__PURE__*/ _jsx(Card, {
|
|
244
245
|
variant: "outlined",
|
|
245
246
|
children: /*#__PURE__*/ _jsx(EmptyStateComponent, {
|
|
@@ -253,7 +254,8 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
253
254
|
spacing: 1,
|
|
254
255
|
children: [
|
|
255
256
|
chips,
|
|
256
|
-
|
|
257
|
+
notices,
|
|
258
|
+
pageRows.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
257
259
|
spacing: 1,
|
|
258
260
|
component: "ul",
|
|
259
261
|
sx: {
|
|
@@ -308,9 +310,12 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
308
310
|
})
|
|
309
311
|
})
|
|
310
312
|
}, sequence.id))
|
|
313
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
314
|
+
variant: "body2",
|
|
315
|
+
color: "text.secondary",
|
|
316
|
+
children: sequences.status === 'loading' ? 'Loading sequences…' : 'No sequences match these filters'
|
|
311
317
|
}),
|
|
312
|
-
footer
|
|
313
|
-
truncatedNotice
|
|
318
|
+
footer
|
|
314
319
|
]
|
|
315
320
|
});
|
|
316
321
|
} else {
|
|
@@ -342,14 +347,20 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
342
347
|
align: 'right',
|
|
343
348
|
headerAlign: 'right'
|
|
344
349
|
}))
|
|
345
|
-
], SEQUENCE_FILTER_FIELDS,
|
|
350
|
+
], SEQUENCE_FILTER_FIELDS, filterOptions, SEQUENCE_FILTER_HEADERS);
|
|
346
351
|
body = /*#__PURE__*/ _jsxs(Stack, {
|
|
347
352
|
spacing: 1,
|
|
348
353
|
children: [
|
|
349
354
|
chips,
|
|
355
|
+
notices,
|
|
350
356
|
/*#__PURE__*/ _jsx(ListTable, {
|
|
351
357
|
"aria-label": "Sequences",
|
|
352
358
|
columns: columns,
|
|
359
|
+
initialState: {
|
|
360
|
+
columns: {
|
|
361
|
+
columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS
|
|
362
|
+
}
|
|
363
|
+
},
|
|
353
364
|
rows: pageRows.map((sequence)=>_extends({
|
|
354
365
|
$id: sequence.id,
|
|
355
366
|
name: sequence.name || 'Untitled',
|
|
@@ -360,18 +371,21 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
360
371
|
count(sequence, key)
|
|
361
372
|
])))),
|
|
362
373
|
onOpen: (id)=>navigate(`${sectionPath}/${id}`),
|
|
374
|
+
loading: sequences.status === 'loading',
|
|
363
375
|
/*
|
|
364
|
-
* The panel and the search are the grid's;
|
|
365
|
-
*
|
|
376
|
+
* The panel and the search are the grid's; every clause and the
|
|
377
|
+
* search word are on the list's query (AGL-3321), and the grid
|
|
378
|
+
* neither filters nor sorts the page it is handed: the query's
|
|
379
|
+
* one order, newest first, is the list's.
|
|
366
380
|
*/ filterMode: "server",
|
|
367
381
|
filterModel: gridFilter.filterModel,
|
|
368
382
|
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
369
383
|
quickFilter: true,
|
|
384
|
+
disableColumnSorting: true,
|
|
370
385
|
noRowsLabel: "No sequences match these filters",
|
|
371
386
|
hideFooter: true
|
|
372
387
|
}),
|
|
373
|
-
footer
|
|
374
|
-
truncatedNotice
|
|
388
|
+
footer
|
|
375
389
|
]
|
|
376
390
|
});
|
|
377
391
|
}
|
|
@@ -381,7 +395,7 @@ const SEQUENCE_FILTER_OPTIONS = {
|
|
|
381
395
|
anchor: '#sequences'
|
|
382
396
|
}),
|
|
383
397
|
HeaderProps: {
|
|
384
|
-
action: sequences.status === '
|
|
398
|
+
action: none || sequences.status === 'loading' ? undefined : newButton
|
|
385
399
|
},
|
|
386
400
|
contentGutterX: true,
|
|
387
401
|
contentGutterY: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n filterListRows,\n inMemoryListField,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Alert,\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequences,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /** The path below the section: `[]`, `['new']`, `[id]` or `[id, 'enrollments']`. */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317). The list holds\n * every sequence it read, so each clause and the quick search are answered\n * over that whole set before it is paged, never over the page on screen.\n */\nconst SEQUENCE_FILTER_FIELDS = [\n inMemoryListField('name', 'text'),\n inMemoryListField('mailbox', 'text'),\n inMemoryListField('status', 'select'),\n]\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n}\nconst SEQUENCE_FILTER_OPTIONS = {\n status: OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n })),\n}\n/** What the quick search reads on a sequence row. */\nconst SEQUENCE_SEARCH_FIELDS = ['name', 'mailbox'] as const\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const sequences = useOutreachSequences(orgId)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const gridFilter = useListGridFilter({ selectFields: ['status'] })\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailbox.sendAs || mailbox.email\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n /*\n * Filtered BEFORE the page is cut: the list read every sequence (under\n * `OUTREACH_SEQUENCES_LIMIT`), so a clause or a search word answers over\n * all of them, and the footer counts the matches.\n */\n const searchKey = gridFilter.searchWords.join(' ')\n const matching = useMemo(\n () =>\n filterListRows(\n sequences.data.map((sequence) => ({\n sequence,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n })),\n SEQUENCE_FILTER_FIELDS,\n gridFilter.clauses,\n { paths: SEQUENCE_SEARCH_FIELDS, words: gridFilter.searchWords },\n ).map((row) => row.sequence),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [sequences.data, props.mailboxes, gridFilter.clauses, searchKey],\n )\n // A narrowed list starts again at its first page.\n useEffect(() => setPage(0), [gridFilter.clauses, searchKey])\n const pageRows = matching.slice(page * pageSize, (page + 1) * pageSize)\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={matching.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n\n /*\n * The read stops at `OUTREACH_SEQUENCES_LIMIT`, newest first, and probes one\n * past it, so this shows only when older sequences really exist.\n */\n const truncatedNotice = sequences.truncated ? (\n <Alert severity=\"info\">\n {`Showing the ${sequences.data.length.toLocaleString()} newest sequences. ` +\n 'The filters and the search narrow these only; older sequences are ' +\n 'not listed here.'}\n </Alert>\n ) : null\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={SEQUENCE_FILTER_OPTIONS}\n />\n )\n let body\n if (sequences.status === 'loading')\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n } else if (!sequences.data.length) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n {footer}\n {truncatedNotice}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, SEQUENCE_FILTER_OPTIONS, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n rows={pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n /*\n * The panel and the search are the grid's; the list answers them\n * over every sequence it read (AGL-3317).\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n {truncatedNotice}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action:\n sequences.status === 'ready' && sequences.data.length\n ? newButton\n : undefined,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","filterListRows","inMemoryListField","listFilterGridColumns","TABLE_PAGE_SIZE_DEFAULT","useListGridFilter","Alert","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequences","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","org","sequenceId","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","SEQUENCE_FILTER_FIELDS","SEQUENCE_FILTER_HEADERS","name","mailbox","status","SEQUENCE_FILTER_OPTIONS","map","value","SEQUENCE_SEARCH_FIELDS","theme","narrow","breakpoints","down","sequences","page","setPage","pageSize","setPageSize","gridFilter","selectFields","mailboxLabel","find","entry","mailboxId","sendAs","email","searchKey","searchWords","join","matching","data","clauses","paths","words","row","pageRows","slice","counts","footer","rowCount","length","count","onPageChange","onPageSizeChange","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","key","String","truncatedNotice","truncated","severity","toLocaleString","chips","fields","headers","onChange","setClauses","options","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","align","headerAlign","rows","$id","Object","fromEntries","onOpen","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OUTREACH_SEQUENCE_LIST_QUERY } from '../model/sequence-list-query'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequenceList,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /** The path below the section: `[]`, `['new']`, `[id]` or `[id, 'enrollments']`. */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317), each field on\n * the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).\n */\nconst SEQUENCE_FILTER_FIELDS = OUTREACH_SEQUENCE_LIST_QUERY.fields\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n created: 'Created',\n}\n/** `created` is no column of its own: a filter-only column, never shown. */\nconst SEQUENCE_HIDDEN_COLUMNS = hiddenFilterVisibility(SEQUENCE_FILTER_FIELDS, [\n 'name',\n 'mailbox',\n 'status',\n])\nconst SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n}))\n\n/** How a mailbox reads in the list: the address it sends as. */\nconst mailboxName = (mailbox: OutreachMailbox) => mailbox.sendAs || mailbox.email\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n *\n * ## Asked of Firestore, a page at a time\n *\n * The Filters panel and the search box are the grid's; the clauses and the\n * words go onto ONE query over the organization's sequences, newest first,\n * and each page is a page of that query's matches (AGL-3321). A clause the\n * query cannot hold beside the others is not applied, and the notice above\n * the list says which and why — never answered over the sequences that\n * happen to be loaded.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const gridFilter = useListGridFilter({ selectFields: ['status', 'mailbox'] })\n const sequences = useOutreachSequenceList(orgId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const filterOptions = useMemo(\n () => ({\n status: SEQUENCE_STATUS_OPTIONS,\n mailbox: props.mailboxes.map((mailbox) => ({\n value: mailbox.id,\n label: mailboxName(mailbox),\n })),\n }),\n [props.mailboxes],\n )\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailboxName(mailbox)\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n const pageRows = sequences.rows\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={sequences.page}\n pageSize={sequences.pageSize}\n rowCount={pageRows.length}\n hasMore={sequences.hasMore}\n onPageChange={sequences.setPage}\n onPageSizeChange={sequences.setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n const notices = (\n <ListQueryNotices\n refused={listQueryRefusals(sequences.plan.refused, {\n fields: SEQUENCE_FILTER_FIELDS,\n headers: SEQUENCE_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={sequences.plan.notices}\n />\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n sequences.status === 'ready' &&\n !pageRows.length &&\n !filtering &&\n sequences.page === 0\n let body\n if (sequences.status === 'loading' && !filtering)\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = (\n <Stack spacing={1}>\n {chips}\n <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n </Stack>\n )\n } else if (none) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n {pageRows.length ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sequences.status === 'loading'\n ? 'Loading sequences…'\n : 'No sequences match these filters'}\n </Typography>\n )}\n {footer}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, filterOptions, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n initialState={{ columns: { columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS } }}\n rows={pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n loading={sequences.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the list's query (AGL-3321), and the grid\n * neither filters nor sorts the page it is handed: the query's\n * one order, newest first, is the list's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action: none || sequences.status === 'loading' ? undefined : newButton,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","ListQueryNotices","listQueryRefusals","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useMemo","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_LIST_QUERY","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequenceList","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","org","sequenceId","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","SEQUENCE_FILTER_FIELDS","fields","SEQUENCE_FILTER_HEADERS","name","mailbox","status","created","SEQUENCE_HIDDEN_COLUMNS","SEQUENCE_STATUS_OPTIONS","map","value","mailboxName","sendAs","email","theme","narrow","breakpoints","down","gridFilter","selectFields","sequences","clauses","search","searchWords","filtering","length","some","word","trim","filterOptions","mailboxLabel","find","entry","mailboxId","pageRows","rows","counts","footer","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","count","key","String","chips","headers","onChange","setClauses","options","notices","refused","plan","none","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","row","align","headerAlign","initialState","columnVisibilityModel","$id","Object","fromEntries","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,QAAoC,eAAc;AACzE,SAASC,OAAO,QAAQ,yBAAwB;AAChD,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,OAAOC,yBAAyB,wDAAuD;AACvF,OAAOC,qBAAqB,8DAA6D;AACzF,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SACEC,SAAS,QAEJ,uDAAsD;AAC7D,OAAOC,oBACLC,iBAAiB,QACZ,+DAA8D;AACrE,SAASC,sBAAsB,QAAQ,yCAAwC;AAC/E,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,SACEC,MAAM,EACNC,IAAI,EACJC,WAAW,EACXC,IAAI,EACJC,KAAK,EACLC,UAAU,EACVC,aAAa,EACbC,QAAQ,QACH,gBAAe;AACtB,SAASC,OAAO,QAAQ,QAAO;AAC/B,SACEC,0BAA0B,QAGrB,6BAAyB;AAChC,SAASC,4BAA4B,QAAQ,kCAA8B;AAC3E,SACEC,+BAA+B,EAC/BC,YAAY,EACZC,eAAe,EACfC,mBAAmB,EACnBC,0BAA0B,EAC1BC,mBAAmB,QACd,mBAAe;AACtB,SACEC,sBAAsB,QAEjB,uBAAmB;AAC1B,SAASC,sBAAsB,QAAQ,uBAAmB;AAC1D,SAASC,cAAc,QAAQ,wBAAoB;AACnD,SAEEC,yBAAyB,EACzBC,uBAAuB,QAClB,yBAAqB;AAC5B,SAASC,oBAAoB,QAAQ,8BAA0B;AAC/D,SAASC,6BAA6B,QAAQ,6BAAyB;AAiBvE,uDAAuD,GACvD,OAAO,MAAMC,gCAAgC,MAAK;AAElD;;;;CAIC,GACD,OAAO,SAASC,yBAAyBC,KAAoC;IAC3E,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAAGH;IACxC,MAAMI,MAAMX,eAAeQ;IAC3B,MAAMI,WAAWR,8BAA8BO,KAAKH;IACpD,MAAMK,YAAYV,qBAAqBK;IACvC,MAAMM,WAAWjB;IAEjB,IAAI,CAACW,OAAO,qBAAO,KAACd;QAAgBqB,OAAM;;IAC1C,MAAM,CAACC,OAAOC,OAAO,GAAGP;IACxB,IAAIM,UAAUX,+BAA+B;QAC3C,qBACE,MAACpB;YAAMiC,SAAS;;8BACd,KAACzB;oBAAa0B,MAAMV;8BAAa;;8BACjC,KAACvB;oBAAWkC,SAAQ;oBAAKC,WAAU;8BAAK;;8BAGxC,KAACtB;oBACCS,OAAOA;oBACPc,UAAUf,MAAMe,QAAQ;oBACxBC,UAAU;oBACVX,UAAUA;oBACVC,WAAWA;oBACXW,eAAejB,MAAMiB,aAAa;oBAClCC,SAAS,CAACF,WAAaT,SAAS,GAAGL,YAAY,CAAC,EAAEc,SAASG,EAAE,EAAE;oBAC/DC,UAAU,IAAMb,SAASL;;;;IAIjC;IACA,IAAIO,OAAO;QACT,qBACE,KAAClB;YACCU,OAAOA;YACPc,UAAUf,MAAMe,QAAQ;YACxBM,KAAKrB,MAAMqB,GAAG;YACdnB,aAAaA;YACboB,YAAYb;YACZc,KACGb,WAAW,gBACR,gBACA;YAENL,UAAUA;YACVC,WAAWA;YACXW,eAAejB,MAAMiB,aAAa;YAClCO,gBAAgBxB,MAAMwB,cAAc;;IAG1C;IACA,qBACE,KAACC;QACCxB,OAAOA;QACPC,aAAaA;QACbI,WAAWA,UAAUA,SAAS;;AAGpC;AACAP,yBAAyB2B,WAAW,GAAG;AAEvC,MAAMC,gBAAuE;IAC3E;QAAC;QAAY;KAAW;IACxB;QAAC;QAAU;KAAS;IACpB;QAAC;QAAW;KAAU;IACtB;QAAC;QAAW;KAAU;IACtB;QAAC;QAAY;KAAY;CAC1B;AAED;;;CAGC,GACD,MAAMC,yBAAyB5C,6BAA6B6C,MAAM;AAClE,MAAMC,0BAA4D;IAChEC,MAAM;IACNC,SAAS;IACTC,QAAQ;IACRC,SAAS;AACX;AACA,0EAA0E,GAC1E,MAAMC,0BAA0BhE,uBAAuByD,wBAAwB;IAC7E;IACA;IACA;CACD;AACD,MAAMQ,0BAA0BrD,2BAA2BsD,GAAG,CAAC,CAACJ,SAAY,CAAA;QAC1EK,OAAOL;QACPzB,OAAOvB,+BAA+B,CAACgD,OAAO;IAChD,CAAA;AAEA,8DAA8D,GAC9D,MAAMM,cAAc,CAACP,UAA6BA,QAAQQ,MAAM,IAAIR,QAAQS,KAAK;AAEjF;;;;;;;;;;;;CAYC,GACD,OAAO,SAAShB,qBAAqBzB,KAIpC;IACC,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAE,GAAGF;IAC/B,MAAM0C,QAAQ7D;IACd,MAAM8D,SAAS/D,cAAc8D,MAAME,WAAW,CAACC,IAAI,CAAC;IACpD,MAAMtC,WAAWjB;IACjB,MAAMwD,aAAazE,kBAAkB;QAAE0E,cAAc;YAAC;YAAU;SAAU;IAAC;IAC3E,MAAMC,YAAYrD,wBAAwBM,OAAO;QAC/CgD,SAASH,WAAWG,OAAO;QAC3BC,QAAQJ,WAAWK,WAAW;IAChC;IACA,MAAMC,YACJN,WAAWG,OAAO,CAACI,MAAM,GAAG,KAAKP,WAAWK,WAAW,CAACG,IAAI,CAAC,CAACC,OAASA,KAAKC,IAAI;IAClF,MAAMC,gBAAgB3E,QACpB,IAAO,CAAA;YACLmD,QAAQG;YACRJ,SAAShC,MAAMM,SAAS,CAAC+B,GAAG,CAAC,CAACL,UAAa,CAAA;oBACzCM,OAAON,QAAQb,EAAE;oBACjBX,OAAO+B,YAAYP;gBACrB,CAAA;QACF,CAAA,GACA;QAAChC,MAAMM,SAAS;KAAC;IAEnB,MAAMoD,eAAe,CAAC1C;QACpB,MAAMgB,UAAUhC,MAAMM,SAAS,CAACqD,IAAI,CAClC,CAACC,QAAUA,MAAMzC,EAAE,KAAKH,SAAS6C,SAAS;QAE5C,OAAO7B,UACHO,YAAYP,WACZhB,SAAS6C,SAAS,GAChB,oBACA;IACR;IACA,MAAMC,WAAWd,UAAUe,IAAI;IAC/B,qEAAqE;IACrE,MAAMC,SAAStE,0BACbO,OACA6D,SAASzB,GAAG,CAAC,CAACrB,WAAaA,SAASG,EAAE;IAExC,MAAM8C,uBACJ,KAAClG;QACCmG,MAAMlB,UAAUkB,IAAI;QACpBC,UAAUnB,UAAUmB,QAAQ;QAC5BC,UAAUN,SAAST,MAAM;QACzBgB,SAASrB,UAAUqB,OAAO;QAC1BC,cAActB,UAAUuB,OAAO;QAC/BC,kBAAkBxB,UAAUyB,WAAW;;IAG3C,MAAMC,UAAU,GAAGxE,YAAY,CAAC,EAAEJ,+BAA+B;IACjE,MAAM6E,0BACJ,KAACrG;QACCuC,SAAQ;QACR+D,yBAAW,KAAChH;YAAQiH,MAAMnH,QAAQmH,IAAI;YAAEC,UAAS;;QACjDlE,MAAM8D;QACNK,SAAS,CAACC;YACRA,MAAMC,cAAc;YACpB1E,SAASmE;QACX;kBACD;;IAIH,MAAMQ,QAAQ,CACZlE,UACAmE,MACInB,MAAM,CAAChD,SAASG,EAAE,CAAC,GAAGiE,OAAOpB,MAAM,CAAChD,SAASG,EAAE,CAAC,CAACgE,IAAI,IAAI;IAE/D,MAAME,sBACJ,KAACvH;QACC+D,QAAQD;QACR0D,SAASxD;QACTmB,SAASH,WAAWG,OAAO;QAC3BsC,UAAUzC,WAAW0C,UAAU;QAC/BC,SAAShC;;IAGb,MAAMiC,wBACJ,KAACzH;QACC0H,SAASzH,kBAAkB8E,UAAU4C,IAAI,CAACD,OAAO,EAAE;YACjD9D,QAAQD;YACR0D,SAASxD;YACT2D,SAAShC;QACX;QACAiC,SAAS1C,UAAU4C,IAAI,CAACF,OAAO;;IAGnC,+DAA+D,GAC/D,MAAMG,OACJ7C,UAAUf,MAAM,KAAK,WACrB,CAAC6B,SAAST,MAAM,IAChB,CAACD,aACDJ,UAAUkB,IAAI,KAAK;IACrB,IAAI4B;IACJ,IAAI9C,UAAUf,MAAM,KAAK,aAAa,CAACmB,WACrC0C,qBAAO,KAAC3G;QAAgBqB,OAAM;;SAC3B,IAAIwC,UAAUf,MAAM,KAAK,WAAWe,UAAUf,MAAM,KAAK,WAAW;QACvE6D,qBACE,MAACpH;YAAMiC,SAAS;;gBACb0E;8BACD,KAACjG;oBAAoB6C,QAAQe,UAAUf,MAAM;oBAAE8D,MAAK;;;;IAG1D,OAAO,IAAIF,MAAM;QACfC,qBACE,KAACvH;YAAKsC,SAAQ;sBACZ,cAAA,KAAChD;gBACC2C,OAAM;gBACNwF,aAAY;gBACZC,QAAQtB;;;IAIhB,OAAO,IAAIhC,QAAQ;QACjBmD,qBACE,MAACpH;YAAMiC,SAAS;;gBACb0E;gBACAK;gBACA5B,SAAST,MAAM,iBACd,KAAC3E;oBACCiC,SAAS;oBACTG,WAAU;oBACVoF,IAAI;wBAAEC,WAAW;wBAAQC,GAAG;wBAAGC,GAAG;oBAAE;oBACpCC,cAAW;8BAEVxC,SAASzB,GAAG,CAAC,CAACrB,yBACb,KAACzC;4BAAuBsC,SAAQ;4BAAWC,WAAU;sCACnD,cAAA,KAACtC;0CACC,cAAA,MAACE;oCAAMiC,SAAS;;sDACd,MAACjC;4CACC6H,WAAU;4CACV5F,SAAS;4CACTuF,IAAI;gDACFM,YAAY;gDACZC,gBAAgB;4CAClB;;8DAEA,KAACvH;oDAAa0B,MAAM,GAAGV,YAAY,CAAC,EAAEc,SAASG,EAAE,EAAE;8DAChDH,SAASe,IAAI,IAAI;;8DAEpB,KAAC1C;oDAA2B4C,QAAQjB,SAASiB,MAAM;;;;sDAErD,KAACtD;4CAAWkC,SAAQ;4CAAQ6F,OAAM;sDAC/BhD,aAAa1C;;sDAEhB,KAACtC;4CACC6H,WAAU;4CACV5F,SAAS;4CACTuF,IAAI;gDAAES,UAAU;gDAAQC,QAAQ;4CAAI;sDAEnCjF,cAAcU,GAAG,CAAC,CAAC,CAAC8C,KAAK3E,MAAM,iBAC9B,KAAC/B;oDAECoI,MAAK;oDACLhG,SAAQ;oDACRL,OAAO,GAAGA,MAAM,CAAC,EAAE0E,MAAMlE,UAAUmE,MAAM;mDAHpCA;;;;;2BA1BNnE,SAASG,EAAE;mCAuC1B,KAACxC;oBAAWkC,SAAQ;oBAAQ6F,OAAM;8BAC/B1D,UAAUf,MAAM,KAAK,YAClB,uBACA;;gBAGPgC;;;IAGP,OAAO;QACL,MAAM6C,UAAkD1I,sBAAsB;YAC5E;gBAAE2I,OAAO;gBAAQC,YAAY;gBAAQC,MAAM;gBAAGC,UAAU;YAAI;YAC5D;gBAAEH,OAAO;gBAAWC,YAAY;gBAAWC,MAAM;gBAAGC,UAAU;YAAI;YAClE;gBACEH,OAAO;gBACPC,YAAY;gBACZG,OAAO;gBACPC,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,KAAChI;wBAA2B4C,QAAQoF,IAAIpF,MAAM;;YAElD;eACGN,cAAcU,GAAG,CAAC,CAAC,CAAC8C,KAAK3E,MAAM,GAAM,CAAA;oBACtCuG,OAAO5B;oBACP6B,YAAYxG;oBACZ2G,OAAO;oBACPG,OAAO;oBACPC,aAAa;gBACf,CAAA;SACD,EAAE3F,wBAAwB6B,eAAe3B;QAC1CgE,qBACE,MAACpH;YAAMiC,SAAS;;gBACb0E;gBACAK;8BACD,KAAC1H;oBACCsI,cAAW;oBACXQ,SAASA;oBACTU,cAAc;wBAAEV,SAAS;4BAAEW,uBAAuBtF;wBAAwB;oBAAE;oBAC5E4B,MAAMD,SAASzB,GAAG,CAAC,CAACrB,WAAc;4BAChC0G,KAAK1G,SAASG,EAAE;4BAChBY,MAAMf,SAASe,IAAI,IAAI;4BACvBC,SAAS0B,aAAa1C;4BACtBiB,QAAQjB,SAASiB,MAAM;2BACpB0F,OAAOC,WAAW,CACnBjG,cAAcU,GAAG,CAAC,CAAC,CAAC8C,IAAI,GAAK;gCAACA;gCAAKD,MAAMlE,UAAUmE;6BAAK;oBAG5D0C,QAAQ,CAAC1G,KAAOZ,SAAS,GAAGL,YAAY,CAAC,EAAEiB,IAAI;oBAC/C2G,SAAS9E,UAAUf,MAAM,KAAK;oBAC9B;;;;;WAKC,GACD8F,YAAW;oBACXC,aAAalF,WAAWkF,WAAW;oBACnCC,qBAAqBnF,WAAWmF,mBAAmB;oBACnDC,WAAW;oBACXC,oBAAoB;oBACpBC,aAAY;oBACZC,UAAU;;gBAEXpE;;;IAGP;IAEA,qBACE,KAACtG;QACC2K,QAAO;QACPC,MAAM9K,eAAe,aAAa;YAAE+K,QAAQ;QAAa;QACzDC,aAAa;YACXxC,QAAQJ,QAAQ7C,UAAUf,MAAM,KAAK,YAAYyG,YAAY/D;QAC/D;QACAgE,cAAc;QACdC,cAAc;kBAEb9C;;AAGP;AACArE,qBAAqBC,WAAW,GAAG;AAEnC,eAAe3B,yBAAwB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CampaignLinkRollup } from '@aglyn/shared-ui-email-campaigns/model';
|
|
2
|
+
import type { ListQueryPlan, ListQueryRequest } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
2
3
|
import { type OutreachDoNotContactDomainEntry, type OutreachEnrollment, type OutreachSequence } from '../model/outreach.types';
|
|
3
4
|
/**
|
|
4
5
|
* The Outreach documents the console reads LIVE (AGL-2980): sequences,
|
|
@@ -18,15 +19,27 @@ export interface OutreachLoad<T> {
|
|
|
18
19
|
status: OutreachLoadStatus;
|
|
19
20
|
data: T;
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/**
|
|
23
|
+
* One page of a list whose every filter and search word is on its Firestore
|
|
24
|
+
* query (AGL-3321): the rows of the page on screen, whether another page
|
|
25
|
+
* exists, the pager, and the plan — what the query holds and what it could
|
|
26
|
+
* not take, which the list says above itself.
|
|
27
|
+
*/
|
|
28
|
+
export interface OutreachListPage<T> {
|
|
29
|
+
status: OutreachLoadStatus;
|
|
30
|
+
rows: T[];
|
|
31
|
+
hasMore: boolean;
|
|
32
|
+
page: number;
|
|
33
|
+
setPage(page: number): void;
|
|
34
|
+
pageSize: number;
|
|
35
|
+
setPageSize(pageSize: number): void;
|
|
36
|
+
plan: ListQueryPlan;
|
|
27
37
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The organization's sequences, newest first, one page at a time — each
|
|
40
|
+
* clause and the search on the query (`OUTREACH_SEQUENCE_LIST_QUERY`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function useOutreachSequenceList(orgId: string | null, request: ListQueryRequest): OutreachListPage<OutreachSequence>;
|
|
30
43
|
/** One sequence, or `null` once it is known not to exist. */
|
|
31
44
|
export declare function useOutreachSequence(orgId: string | null, sequenceId: string | null): OutreachLoad<OutreachSequence | null>;
|
|
32
45
|
/**
|
|
@@ -41,26 +54,18 @@ export type OutreachSequenceLinksLoad = OutreachLoad<CampaignLinkRollup | null>;
|
|
|
41
54
|
export declare function useOutreachSequenceLinks(orgId: string | null, sequenceId: string | null): OutreachSequenceLinksLoad;
|
|
42
55
|
/**
|
|
43
56
|
* The domains on the organization's do-not-contact list (AGL-3244),
|
|
44
|
-
* alphabetically
|
|
45
|
-
*
|
|
46
|
-
* gateway block.
|
|
57
|
+
* alphabetically — the document id is the domain — one page at a time, live:
|
|
58
|
+
* an add or a remove through the route shows up without a reload, and so
|
|
59
|
+
* does a domain the sending runtime files after a gateway block. Each clause
|
|
60
|
+
* and the search are on the query (`OUTREACH_DO_NOT_CONTACT_DOMAIN_LIST_QUERY`).
|
|
47
61
|
*/
|
|
48
|
-
export declare function
|
|
49
|
-
/** How many enrollments one page of the table reads; "Show more" reads another. */
|
|
50
|
-
export declare const OUTREACH_ENROLLMENTS_PAGE = 200;
|
|
51
|
-
export interface OutreachEnrollmentsLoad extends OutreachLoad<OutreachEnrollment[]> {
|
|
52
|
-
/** Whether a further page may exist. */
|
|
53
|
-
hasMore: boolean;
|
|
54
|
-
showMore(): void;
|
|
55
|
-
}
|
|
62
|
+
export declare function useOutreachDoNotContactDomainList(orgId: string | null, request: ListQueryRequest): OutreachListPage<OutreachDoNotContactDomainEntry>;
|
|
56
63
|
/**
|
|
57
|
-
* A sequence's enrollments, the most recently enrolled first
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* needs no composite index — and sorted by enrollment time once read, so a
|
|
61
|
-
* table of up to a page reads in the order people were enrolled.
|
|
64
|
+
* A sequence's enrollments, the most recently enrolled first, one page at a
|
|
65
|
+
* time — the sequence is the query's base (`sequenceId ==`), and each clause
|
|
66
|
+
* and the search are on it beside that (`OUTREACH_ENROLLMENT_LIST_QUERY`).
|
|
62
67
|
*/
|
|
63
|
-
export declare function
|
|
68
|
+
export declare function useOutreachEnrollmentList(orgId: string | null, sequenceId: string | null, request: Omit<ListQueryRequest, 'base'>): OutreachListPage<OutreachEnrollment>;
|
|
64
69
|
/** The counts the sequence list shows beside each sequence. */
|
|
65
70
|
export interface OutreachSequenceCounts {
|
|
66
71
|
enrolled: number;
|