@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.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 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 ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\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 { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n Chip,\n} from '@mui/material'\nimport { type ReactNode, useEffect, useMemo, useState } from 'react'\nimport { OutreachCurateStepDialog, outreachNextEmailStepIndex } from './curate-step-dialog'\nimport { readOutreachEngagement } from '../model/enrollment-engagement'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_ENROLLMENT_STATUSES,\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_ENROLLMENT_STATUS_LABELS,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /**\n * Whether this sequence counts link clicks (AGL-3239). The Clicks column\n * is shown only when it does: a column of dashes for a sequence that\n * measures nothing reads as \"nobody clicked\", which is a different claim\n * and a false one.\n */\n trackClicks?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** The row's actions: the four the action route takes, and curating the next step (AGL-3324). */\ntype RowAction = OutreachEnrollmentAction | 'curate'\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n): RowAction[] {\n const actions: RowAction[] = []\n // Rewrite the next email for this one person, while one is still to go.\n if (outreachNextEmailStepIndex(enrollment, steps) !== null) actions.push('curate')\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<RowAction, string> = {\n curate: 'Curate next step',\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** Whether the step the enrollment is on goes out as this person's own copy (AGL-3324). */\nexport function outreachStepIsCurated(\n enrollment: Pick<OutreachEnrollment, 'stepIndex' | 'stepOverrides'>,\n): boolean {\n return Boolean(enrollment.stepOverrides?.[String(enrollment.stepIndex)])\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/*\n * What the enrollments grid's Filters panel offers (AGL-3317). Status and\n * whether the person is a lead are picked; the name, the address and the\n * stop reason are typed. `target` and `email` are no column of their own,\n * so they are hidden columns the panel can still reach.\n */\nconst ENROLLMENT_FILTER_FIELDS = [\n inMemoryListField('status', 'select'),\n inMemoryListField('target', 'select'),\n inMemoryListField('contactName', 'text'),\n inMemoryListField('email', 'text'),\n inMemoryListField('stopReason', 'text'),\n]\nconst ENROLLMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n target: 'Enrolled as',\n contactName: 'Name',\n email: 'Email',\n stopReason: 'Stop reason',\n}\nconst ENROLLMENT_FILTER_OPTIONS = {\n status: OUTREACH_ENROLLMENT_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_ENROLLMENT_STATUS_LABELS[status],\n })),\n target: [\n { value: 'contact', label: 'Contact' },\n { value: 'lead', label: 'Lead' },\n ],\n}\n/** What the quick search reads on an enrollment row. */\nconst ENROLLMENT_SEARCH_FIELDS = ['contactName', 'email', 'step', 'stopReason'] as const\n/** The filter-only columns never show. */\nconst ENROLLMENT_HIDDEN_COLUMNS = hiddenFilterVisibility(ENROLLMENT_FILTER_FIELDS, [\n 'status',\n 'stopReason',\n])\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n trackClicks: boolean,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n {\n field: 'step',\n headerName: 'Current step',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => (\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', height: '100%', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.step}\n </Typography>\n {outreachStepIsCurated(row.enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n ),\n },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { enrollment: OutreachEnrollment } }) => {\n const engagement = readOutreachEngagement(row.enrollment.engagement)\n if (!engagement.clicks) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\">\n {engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`}\n </Typography>\n {engagement.lastClickUrl ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {engagement.lastClickUrl}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n ]\n : []),\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const trackClicks = props.trackClicks === true\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [curating, setCurating] = useState<OutreachEnrollment | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const gridFilter = useListGridFilter({ selectFields: ['status', 'target'] })\n const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0\n /*\n * The rows the grid is handed, every clause and the search answered over\n * the enrollments read so far — a window that grows as the reader pages\n * past it, so a filter reaches further the further they go.\n */\n const searchKey = gridFilter.searchWords.join(' ')\n const loadedData = enrollments.data\n const matching = useMemo(\n () =>\n filterListRows(\n loadedData.map((enrollment) => ({\n enrollment,\n status: enrollment.status,\n target: enrollment.target === 'lead' ? 'lead' : 'contact',\n contactName: enrollment.contactName,\n email: enrollment.email,\n step: outreachCurrentStepLabel(enrollment, steps),\n stopReason: outreachStopLabel(enrollment),\n })),\n ENROLLMENT_FILTER_FIELDS,\n gridFilter.clauses,\n { paths: ENROLLMENT_SEARCH_FIELDS, words: gridFilter.searchWords },\n ).map((row) => row.enrollment),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [loadedData, steps, gridFilter.clauses, searchKey],\n )\n // A narrowed list starts again at its first page.\n useEffect(() => setPage(0), [gridFilter.clauses, searchKey])\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: RowAction,\n ) => {\n if (action === 'curate') {\n setCurating(enrollment)\n } else if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment, steps).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = matching\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n <ListFilterChips\n fields={ENROLLMENT_FILTER_FIELDS}\n headers={ENROLLMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={ENROLLMENT_FILTER_OPTIONS}\n />\n {filtering && enrollments.hasMore ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Filtering the ${enrollments.data.length} enrollments read so far — the next page reads more.`}\n </Typography>\n ) : null}\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n {outreachStepIsCurated(enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {trackClicks && readOutreachEngagement(enrollment.engagement).clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicked ${readOutreachEngagement(enrollment.engagement).clicks} ${\n readOutreachEngagement(enrollment.engagement).clicks === 1 ? 'time' : 'times'\n }`}\n </Typography>\n ) : null}\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={listFilterGridColumns(\n columns(rowActions, trackClicks),\n ENROLLMENT_FILTER_FIELDS,\n ENROLLMENT_FILTER_OPTIONS,\n ENROLLMENT_FILTER_HEADERS,\n )}\n initialState={{ columns: { columnVisibilityModel: ENROLLMENT_HIDDEN_COLUMNS } }}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n target: enrollment.target === 'lead' ? 'lead' : 'contact',\n contactName: enrollment.contactName,\n email: enrollment.email,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n /*\n * The panel and the search are the grid's; the table answers them\n * over the enrollments it read (AGL-3317).\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No enrollments match these filters\"\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <OutreachCurateStepDialog\n enrollment={curating}\n steps={steps}\n api={api}\n onClose={() => setCurating(null)}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListFilterChips","ListPagination","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","filterListRows","inMemoryListField","listFilterGridColumns","TABLE_PAGE_SIZE_DEFAULT","useListGridFilter","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useEffect","useMemo","useState","OutreachCurateStepDialog","outreachNextEmailStepIndex","readOutreachEngagement","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_ENROLLMENT_STATUS_LABELS","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","curate","pause","resume","stop","do_not_contact","outreachStepIsCurated","Boolean","stepOverrides","String","DONE_LABELS","CONFIRM","title","body","label","ENROLLMENT_FILTER_FIELDS","ENROLLMENT_FILTER_HEADERS","target","contactName","email","ENROLLMENT_FILTER_OPTIONS","map","value","ENROLLMENT_SEARCH_FIELDS","ENROLLMENT_HIDDEN_COLUMNS","columns","rowActions","trackClicks","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","size","color","width","engagement","clicks","lastClickUrl","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","curating","setCurating","page","setPage","pageSize","setPageSize","gridFilter","selectFields","filtering","clauses","searchWords","searchKey","join","loadedData","data","matching","paths","words","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","description","enrollAction","items","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","fields","headers","onChange","setClauses","options","component","listStyle","p","m","aria-label","flexGrow","initialState","columnVisibilityModel","rows","$id","lastActivity","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","onClose","open","fullWidth","maxWidth","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.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 EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.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 { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n Chip,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport { OutreachCurateStepDialog, outreachNextEmailStepIndex } from './curate-step-dialog'\nimport { readOutreachEngagement } from '../model/enrollment-engagement'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport { OUTREACH_ENROLLMENT_LIST_QUERY } from '../model/enrollment-list-query'\nimport {\n OUTREACH_ENROLLMENT_STATUSES,\n OUTREACH_STOP_REASONS,\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_ENROLLMENT_STATUS_LABELS,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport { useOutreachEnrollmentList } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n /** The organization, whose enrollments the table asks for. */\n orgId: string\n /** The sequence whose people these are: every page is narrowed to it. */\n sequenceId: string\n steps: readonly OutreachSequenceStep[]\n /**\n * Whether this sequence counts link clicks (AGL-3239). The Clicks column\n * is shown only when it does: a column of dashes for a sequence that\n * measures nothing reads as \"nobody clicked\", which is a different claim\n * and a false one.\n */\n trackClicks?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** The row's actions: the four the action route takes, and curating the next step (AGL-3324). */\ntype RowAction = OutreachEnrollmentAction | 'curate'\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n): RowAction[] {\n const actions: RowAction[] = []\n // Rewrite the next email for this one person, while one is still to go.\n if (outreachNextEmailStepIndex(enrollment, steps) !== null) actions.push('curate')\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<RowAction, string> = {\n curate: 'Curate next step',\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** Whether the step the enrollment is on goes out as this person's own copy (AGL-3324). */\nexport function outreachStepIsCurated(\n enrollment: Pick<OutreachEnrollment, 'stepIndex' | 'stepOverrides'>,\n): boolean {\n return Boolean(enrollment.stepOverrides?.[String(enrollment.stepIndex)])\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/*\n * What the enrollments grid's Filters panel offers (AGL-3317), each field on\n * the table's Firestore query (AGL-3321, `OUTREACH_ENROLLMENT_LIST_QUERY`).\n * Status, whether the person is a lead and the stop reason are picked; the\n * address is typed whole. `target` and `email` are no column of their own,\n * so they are hidden columns the panel can still reach; the search box\n * reads the person's name and address.\n */\nconst ENROLLMENT_FILTER_FIELDS = OUTREACH_ENROLLMENT_LIST_QUERY.fields\nconst ENROLLMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n target: 'Enrolled as',\n stopReason: 'Stop reason',\n email: 'Email',\n enrolled: 'Enrolled',\n}\nconst ENROLLMENT_FILTER_OPTIONS = {\n status: OUTREACH_ENROLLMENT_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_ENROLLMENT_STATUS_LABELS[status],\n })),\n target: [\n { value: 'contact', label: 'Contact' },\n { value: 'lead', label: 'Lead' },\n ],\n stopReason: OUTREACH_STOP_REASONS.map((reason) => ({\n value: reason,\n label: OUTREACH_STOP_REASON_LABELS[reason],\n })),\n}\n/** The filter-only columns never show. */\nconst ENROLLMENT_HIDDEN_COLUMNS = hiddenFilterVisibility(ENROLLMENT_FILTER_FIELDS, [\n 'status',\n 'stopReason',\n])\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n trackClicks: boolean,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n {\n field: 'step',\n headerName: 'Current step',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => (\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', height: '100%', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.step}\n </Typography>\n {outreachStepIsCurated(row.enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n ),\n },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { enrollment: OutreachEnrollment } }) => {\n const engagement = readOutreachEngagement(row.enrollment.engagement)\n if (!engagement.clicks) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\">\n {engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`}\n </Typography>\n {engagement.lastClickUrl ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {engagement.lastClickUrl}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n ]\n : []),\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { steps, timeZone, api } = props\n const trackClicks = props.trackClicks === true\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [curating, setCurating] = useState<OutreachEnrollment | null>(null)\n const gridFilter = useListGridFilter({ selectFields: ['status', 'target', 'stopReason'] })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n /*\n * One page of the sequence's enrollments, newest enrolled first, with\n * every clause and the search word on the query (AGL-3321): a filter\n * reaches the enrollment on page forty as surely as the one on page one,\n * and nothing is matched over the rows that happen to be loaded.\n */\n const enrollments = useOutreachEnrollmentList(props.orgId, props.sequenceId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: RowAction,\n ) => {\n if (action === 'curate') {\n setCurating(enrollment)\n } else if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n const chips = (\n <ListFilterChips\n fields={ENROLLMENT_FILTER_FIELDS}\n headers={ENROLLMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={ENROLLMENT_FILTER_OPTIONS}\n />\n )\n if (enrollments.status === 'loading' && !filtering)\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <Stack spacing={1.5}>\n {chips}\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n </Stack>\n )\n }\n if (\n enrollments.status === 'ready' &&\n !enrollments.rows.length &&\n !filtering &&\n enrollments.page === 0\n ) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment, steps).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const pageRows = enrollments.rows\n\n return (\n <Stack spacing={1.5}>\n {chips}\n <ListQueryNotices\n refused={listQueryRefusals(enrollments.plan.refused, {\n fields: ENROLLMENT_FILTER_FIELDS,\n headers: ENROLLMENT_FILTER_HEADERS,\n options: ENROLLMENT_FILTER_OPTIONS,\n })}\n notices={enrollments.plan.notices}\n />\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n {outreachStepIsCurated(enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {trackClicks && readOutreachEngagement(enrollment.engagement).clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicked ${readOutreachEngagement(enrollment.engagement).clicks} ${\n readOutreachEngagement(enrollment.engagement).clicks === 1 ? 'time' : 'times'\n }`}\n </Typography>\n ) : null}\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={listFilterGridColumns(\n columns(rowActions, trackClicks),\n ENROLLMENT_FILTER_FIELDS,\n ENROLLMENT_FILTER_OPTIONS,\n ENROLLMENT_FILTER_HEADERS,\n )}\n initialState={{ columns: { columnVisibilityModel: ENROLLMENT_HIDDEN_COLUMNS } }}\n loading={enrollments.status === 'loading'}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n target: enrollment.target === 'lead' ? 'lead' : 'contact',\n contactName: enrollment.contactName,\n email: enrollment.email,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the table's query (AGL-3321). The grid\n * neither filters nor sorts the page it is handed: the query's\n * one order, newest enrolled first, is the table's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No enrollments match these filters\"\n hideFooter\n />\n )}\n <ListPagination\n page={enrollments.page}\n pageSize={enrollments.pageSize}\n rowCount={pageRows.length}\n hasMore={enrollments.hasMore}\n onPageChange={enrollments.setPage}\n onPageSizeChange={enrollments.setPageSize}\n />\n\n <OutreachCurateStepDialog\n enrollment={curating}\n steps={steps}\n api={api}\n onClose={() => setCurating(null)}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","OutreachCurateStepDialog","outreachNextEmailStepIndex","readOutreachEngagement","OUTREACH_ENROLLMENT_LIST_QUERY","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_STOP_REASONS","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_ENROLLMENT_STATUS_LABELS","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","useOutreachEnrollmentList","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","curate","pause","resume","stop","do_not_contact","outreachStepIsCurated","Boolean","stepOverrides","String","DONE_LABELS","CONFIRM","title","body","label","ENROLLMENT_FILTER_FIELDS","fields","ENROLLMENT_FILTER_HEADERS","target","email","enrolled","ENROLLMENT_FILTER_OPTIONS","map","value","ENROLLMENT_HIDDEN_COLUMNS","columns","rowActions","trackClicks","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","size","color","width","engagement","clicks","lastClickUrl","OutreachEnrollmentsTable","props","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","curating","setCurating","gridFilter","selectFields","filtering","clauses","searchWords","some","word","trim","enrollments","orgId","sequenceId","search","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","chips","headers","onChange","setClauses","options","what","rows","page","description","enrollAction","items","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","pageRows","refused","plan","notices","component","listStyle","p","m","aria-label","flexGrow","initialState","columnVisibilityModel","loading","$id","lastActivity","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","onClose","open","fullWidth","maxWidth","event","slotProps","htmlInput","maxLength","undefined","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,OAAOA,yBAAyB,wDAAuD;AACvF,OAAOC,qBAAqB,8DAA6D;AACzF,OAAOC,oBACLC,iBAAiB,QACZ,+DAA8D;AACrE,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SACEC,cAAc,EACdC,SAAS,EACTC,iBAAiB,QAEZ,uDAAsD;AAC7D,SAASC,sBAAsB,QAAQ,yCAAwC;AAC/E,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,MAAM,EACNC,IAAI,EACJC,WAAW,EACXC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,iBAAiB,EACjBC,WAAW,EACXC,KAAK,EACLC,SAAS,EACTC,UAAU,EACVC,aAAa,EACbC,QAAQ,EACRC,IAAI,QACC,gBAAe;AACtB,SAAyBC,QAAQ,QAAQ,QAAO;AAChD,SAASC,wBAAwB,EAAEC,0BAA0B,QAAQ,0BAAsB;AAC3F,SAASC,sBAAsB,QAAQ,oCAAgC;AAEvE,SAASC,8BAA8B,QAAQ,oCAAgC;AAC/E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,yBAAyB,QAGpB,6BAAyB;AAChC,SACEC,kBAAkB,EAClBC,iCAAiC,EACjCC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,eAAe,EACfC,mBAAmB,QACd,mBAAe;AAEtB,SAASC,yBAAyB,QAAQ,yBAAqB;AAsB/D,wDAAwD,GACxD,OAAO,SAASC,yBACdC,UAA4D,EAC5DC,KAAsC;IAEtC,IAAID,WAAWE,MAAM,KAAK,cAAcF,WAAWG,SAAS,IAAIF,MAAMG,MAAM,EAC1E,OAAO;IACT,MAAMC,OAAOJ,KAAK,CAACD,WAAWG,SAAS,CAAC;IACxC,MAAMG,OACJD,CAAAA,wBAAAA,KAAMC,IAAI,MAAK,UACX,UACAD,CAAAA,wBAAAA,KAAMC,IAAI,MAAK,SACbf,yBAAyB,CAACc,KAAKE,QAAQ,CAAC,GACxC;IACR,OAAO,CAAC,KAAK,EAAEP,WAAWG,SAAS,GAAG,EAAE,IAAI,EAAEF,MAAMG,MAAM,CAAC,GAAG,EAAEE,MAAM;AACxE;AAEA,yEAAyE,GACzE,OAAO,SAASE,kBACdR,UAAiE;QAGlDN;IADf,IAAI,CAACM,WAAWS,UAAU,EAAE,OAAO;IACnC,MAAMC,UAAShB,qDAAAA,2BAA2B,CAACM,WAAWS,UAAU,CAAC,YAAlDf,qDAAsD;IACrE,OAAOM,WAAWW,UAAU,GAAG,GAAGD,OAAO,GAAG,EAAEV,WAAWW,UAAU,EAAE,GAAGD;AAC1E;AAEA,4EAA4E,GAC5E,SAASE,eAAeZ,UAA8B;QAElDA,0BACAA,yBACAA;IAHF,OAAOa,KAAKC,GAAG,EACbd,2BAAAA,WAAWe,YAAY,YAAvBf,2BAA2B,IAC3BA,0BAAAA,WAAWgB,WAAW,YAAtBhB,0BAA0B,IAC1BA,0BAAAA,WAAWiB,WAAW,YAAtBjB,0BAA0B;AAE9B;AAKA,kEAAkE,GAClE,SAASkB,WACPlB,UAA8B,EAC9BC,KAAsC;IAEtC,MAAMkB,UAAuB,EAAE;IAC/B,wEAAwE;IACxE,IAAIjC,2BAA2Bc,YAAYC,WAAW,MAAMkB,QAAQC,IAAI,CAAC;IACzE,IAAIpB,WAAWE,MAAM,KAAK,UAAUiB,QAAQC,IAAI,CAAC,SAAS;IAC1D,IAAIpB,WAAWE,MAAM,KAAK,UAAUiB,QAAQC,IAAI,CAAC,UAAU;IAC3D,IAAIpB,WAAWE,MAAM,KAAK,aAAaiB,QAAQC,IAAI,CAAC;IACpD,OAAOD;AACT;AAEA,MAAME,gBAA2C;IAC/CC,QAAQ;IACRC,OAAO;IACPC,QAAQ;IACRC,MAAM;IACNC,gBAAgB;AAClB;AAEA,yFAAyF,GACzF,OAAO,SAASC,sBACd3B,UAAmE;QAEpDA;IAAf,OAAO4B,SAAQ5B,4BAAAA,WAAW6B,aAAa,qBAAxB7B,yBAA0B,CAAC8B,OAAO9B,WAAWG,SAAS,EAAE;AACzE;AAEA,iDAAiD,GACjD,MAAM4B,cAGF;IACFR,OAAO;IACPC,QAAQ;IACRC,MAAM;AACR;AAEA,2DAA2D,GAC3D,MAAMO,UAKF;IACFP,MAAM;QACJQ,OAAO;QACPC,MAAM;QACNC,OAAO;IACT;IACAT,gBAAgB;QACdO,OAAO;QACPC,MACE,qGACA;QACFC,OAAO;IACT;AACF;AAEA;;;;;;;CAOC,GACD,MAAMC,2BAA2BhD,+BAA+BiD,MAAM;AACtE,MAAMC,4BAA8D;IAClEpC,QAAQ;IACRqC,QAAQ;IACR9B,YAAY;IACZ+B,OAAO;IACPC,UAAU;AACZ;AACA,MAAMC,4BAA4B;IAChCxC,QAAQb,6BAA6BsD,GAAG,CAAC,CAACzC,SAAY,CAAA;YACpD0C,OAAO1C;YACPiC,OAAO1C,iCAAiC,CAACS,OAAO;QAClD,CAAA;IACAqC,QAAQ;QACN;YAAEK,OAAO;YAAWT,OAAO;QAAU;QACrC;YAAES,OAAO;YAAQT,OAAO;QAAO;KAChC;IACD1B,YAAYnB,sBAAsBqD,GAAG,CAAC,CAACjC,SAAY,CAAA;YACjDkC,OAAOlC;YACPyB,OAAOzC,2BAA2B,CAACgB,OAAO;QAC5C,CAAA;AACF;AACA,wCAAwC,GACxC,MAAMmC,4BAA4B/E,uBAAuBsE,0BAA0B;IACjF;IACA;CACD;AAED,yFAAyF,GACzF,SAASU,QACPC,UAAyD,EACzDC,WAAoB;IAEpB,OAAO;QACL;YACEC,OAAO;YACPC,YAAY;YACZC,MAAM;YACNC,UAAU;YACVC,UAAU;YACVC,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,MAAC7E;oBACC8E,SAAS;oBACTC,IAAI;wBAAEC,gBAAgB;wBAAUC,QAAQ;wBAAQP,UAAU;oBAAE;;sCAE5D,MAAC1E;4BAAMkF,WAAU;4BAAMJ,SAAS;4BAAMC,IAAI;gCAAEI,YAAY;gCAAUT,UAAU;4BAAE;;8CAC5E,KAACxE;oCAAWkF,SAAQ;oCAAQC,MAAM;8CAC/BR,IAAIvD,UAAU,CAACgE,WAAW,IAAIT,IAAIvD,UAAU,CAACwC,KAAK;;gCAEpDe,IAAIvD,UAAU,CAACuC,MAAM,KAAK,uBACzB,KAACxD;oCAAKkF,MAAK;oCAAQH,SAAQ;oCAAW3B,OAAM;qCAC1C;;;wBAELoB,IAAIvD,UAAU,CAACgE,WAAW,iBACzB,KAACpF;4BAAWkF,SAAQ;4BAAUI,OAAM;4BAAiBH,MAAM;sCACxDR,IAAIvD,UAAU,CAACwC,KAAK;6BAErB;;;QAGV;QACA;YACES,OAAO;YACPC,YAAY;YACZiB,OAAO;YACPb,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,KAAC5D;oBAA6BO,QAAQqD,IAAIrD,MAAM;;QAEpD;QACA;YACE+C,OAAO;YACPC,YAAY;YACZC,MAAM;YACNC,UAAU;YACVE,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,MAAC7E;oBAAMkF,WAAU;oBAAMJ,SAAS;oBAAMC,IAAI;wBAAEI,YAAY;wBAAUF,QAAQ;wBAAQP,UAAU;oBAAE;;sCAC5F,KAACxE;4BAAWkF,SAAQ;4BAAQC,MAAM;sCAC/BR,IAAIlD,IAAI;;wBAEVsB,sBAAsB4B,IAAIvD,UAAU,kBACnC,KAACjB;4BAAKkF,MAAK;4BAAQH,SAAQ;4BAAWI,OAAM;4BAAU/B,OAAM;6BAC1D;;;QAGV;QACA;YAAEc,OAAO;YAAYC,YAAY;YAAaiB,OAAO;YAAKd,UAAU;QAAM;QAC1E;YACEJ,OAAO;YACPC,YAAY;YACZiB,OAAO;YACPd,UAAU;QACZ;WACIL,cACA;YACE;gBACEC,OAAO;gBACPC,YAAY;gBACZiB,OAAO;gBACPd,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAA+C;oBAC/D,MAAMa,aAAajF,uBAAuBoE,IAAIvD,UAAU,CAACoE,UAAU;oBACnE,IAAI,CAACA,WAAWC,MAAM,EAAE;wBACtB,qBACE,KAACzF;4BAAWkF,SAAQ;4BAAQI,OAAM;sCAAiB;;oBAIvD;oBACA,qBACE,MAACxF;wBAAM8E,SAAS;wBAAGC,IAAI;4BAAEC,gBAAgB;4BAAUC,QAAQ;wBAAO;;0CAChE,KAAC/E;gCAAWkF,SAAQ;0CACjBM,WAAWC,MAAM,KAAK,IAAI,YAAY,GAAGD,WAAWC,MAAM,CAAC,OAAO,CAAC;;4BAErED,WAAWE,YAAY,iBACtB,KAAC1F;gCAAWkF,SAAQ;gCAAUI,OAAM;gCAAiBH,MAAM;0CACxDK,WAAWE,YAAY;iCAExB;;;gBAGV;YACF;SACD,GACD,EAAE;QACN;YAAErB,OAAO;YAAcC,YAAY;YAAeC,MAAM;YAAGC,UAAU;QAAI;QACzEvF,kBAAkB,CAAC0F,MAAQR,WAAWQ,IAAIvD,UAAU,GAAG;YAAEmE,OAAO;QAAG;KACpE;AACH;AAEA;;;;;CAKC,GACD,OAAO,SAASI,yBAAyBC,KAAoC;QA0QnDxC,4BAIPA,6BAsBFA;IAnSf,MAAM,EAAE/B,KAAK,EAAEwE,QAAQ,EAAEC,GAAG,EAAE,GAAGF;IACjC,MAAMxB,cAAcwB,MAAMxB,WAAW,KAAK;IAC1C,MAAM2B,QAAQ7F;IACd,MAAM8F,SAAS/F,cAAc8F,MAAME,WAAW,CAACC,IAAI,CAAC;IACpD,MAAM,EAAEC,eAAe,EAAE,GAAG9G;IAC5B,MAAM,CAAC+G,YAAYC,cAAc,GAAGjG,SAG1B;IACV,MAAM,CAACkG,QAAQC,UAAU,GAAGnG,SAAS;IACrC,MAAM,CAACoG,MAAMC,QAAQ,GAAGrG,SAAwB;IAChD,MAAM,CAACsG,UAAUC,YAAY,GAAGvG,SAAoC;IACpE,MAAMwG,aAAaxH,kBAAkB;QAAEyH,cAAc;YAAC;YAAU;YAAU;SAAa;IAAC;IACxF,MAAMC,YACJF,WAAWG,OAAO,CAACvF,MAAM,GAAG,KAAKoF,WAAWI,WAAW,CAACC,IAAI,CAAC,CAACC,OAASA,KAAKC,IAAI;IAClF;;;;;GAKC,GACD,MAAMC,cAAclG,0BAA0B0E,MAAMyB,KAAK,EAAEzB,MAAM0B,UAAU,EAAE;QAC3EP,SAASH,WAAWG,OAAO;QAC3BQ,QAAQX,WAAWI,WAAW;IAChC;IAEA,MAAMQ,MAAM,OACVpG,YACAqG,QACAC;QAEAjB,QAAQrF,WAAWuG,EAAE;QACrB,IAAI;YACF,MAAMC,SAAS,MAAM9B,IAAI+B,eAAe,CAACzG,WAAWuG,EAAE,EAAEF,QAAQC;YAChE,MAAMI,SAASF,OAAOG,aAAa;YACnC5B,gBACEsB,WAAW,mBACP,CAAC,qBAAqB,EAAEK,SAAS,CAAC,iBAAiB,EAAEA,OAAO,OAAO,EAAEA,WAAW,IAAI,aAAa,aAAa,GAAG,GAAG,CAAC,CAAC,GACtHF,OAAOI,OAAO,GACZ7E,WAAW,CAACsE,OAAO,GACnB,sBACN;gBAAEvC,SAAS;YAAU;QAEzB,EAAE,OAAO+C,OAAO;YACd9B,gBAAgB,AAAC8B,MAAgBC,OAAO,EAAE;gBACxChD,SAAS;gBACTiD,gBAAgB;YAClB;QACF,SAAU;YACR1B,QAAQ;QACV;IACF;IAEA,MAAM2B,SAAS,CACbhH,YACAqG;QAEA,IAAIA,WAAW,UAAU;YACvBd,YAAYvF;QACd,OAAO,IAAIgC,OAAO,CAACqE,OAAO,EAAE;YAC1BlB,UAAU;YACVF,cAAc;gBAAEjF;gBAAYqG;YAAO;QACrC,OAAO;YACL,KAAKD,IAAIpG,YAAYqG;QACvB;IACF;IAEA,MAAMY,sBACJ,KAAC1J;QACC8E,QAAQD;QACR8E,SAAS5E;QACTqD,SAASH,WAAWG,OAAO;QAC3BwB,UAAU3B,WAAW4B,UAAU;QAC/BC,SAAS3E;;IAGb,IAAIsD,YAAY9F,MAAM,KAAK,aAAa,CAACwF,WACvC,qBAAO,KAAC9F;QAAgBuC,OAAM;;IAChC,IAAI6D,YAAY9F,MAAM,KAAK,WAAW8F,YAAY9F,MAAM,KAAK,WAAW;QACtE,qBACE,MAACxB;YAAM8E,SAAS;;gBACbyD;8BACD,KAACpH;oBAAoBK,QAAQ8F,YAAY9F,MAAM;oBAAEoH,MAAK;;;;IAG5D;IACA,IACEtB,YAAY9F,MAAM,KAAK,WACvB,CAAC8F,YAAYuB,IAAI,CAACnH,MAAM,IACxB,CAACsF,aACDM,YAAYwB,IAAI,KAAK,GACrB;QACA,qBACE,KAACrJ;YAAK2F,SAAQ;sBACZ,cAAA,KAACxG;gBACC6E,OAAM;gBACNsF,aAAY;gBACZpB,QAAQ7B,MAAMkD,YAAY;;;IAIlC;IAEA,MAAM3E,aAAa,CAAC/C,2BAClB,KAACrC;YACCwE,OAAOnC,WAAWgE,WAAW,IAAIhE,WAAWwC,KAAK;YACjDmF,OAAOzG,WAAWlB,YAAYC,OAAO0C,GAAG,CAAC,CAAC0D,SAAY,CAAA;oBACpDuB,KAAKvB;oBACLlE,OAAOd,aAAa,CAACgF,OAAO;oBAC5BwB,SAAS,IAAMb,OAAOhH,YAAYqG;oBAClCyB,aAAazB,WAAW,UAAUA,WAAW;oBAC7C0B,UAAU3C,SAASpF,WAAWuG,EAAE;gBAClC,CAAA;;IAGJ,MAAMyB,WAAW,CAAChI,aAChBA,WAAWE,MAAM,KAAK,YAAYF,WAAWE,MAAM,KAAK,WACpDV,mBAAmBQ,WAAWiI,WAAW,EAAExD,YAC3C;IACN,MAAMyD,SAAS,CAAClI,2BACd,MAACtB;YAAM8E,SAAS;;8BACd,MAAC9E;oBAAMkF,WAAU;oBAAMJ,SAAS;oBAAMC,IAAI;wBAAEI,YAAY;oBAAS;;sCAC/D,KAACjF;4BAAWkF,SAAQ;sCACjB9D,WAAWgE,WAAW,IAAIhE,WAAWwC,KAAK;;wBAE5CxC,WAAWuC,MAAM,KAAK,uBACrB,KAACxD;4BAAKkF,MAAK;4BAAQH,SAAQ;4BAAW3B,OAAM;6BAC1C;;;gBAELnC,WAAWgE,WAAW,iBACrB,KAACpF;oBAAWkF,SAAQ;oBAAUI,OAAM;8BACjClE,WAAWwC,KAAK;qBAEjB;;;IAIR,MAAM2F,WAAWnC,YAAYuB,IAAI;IAEjC,qBACE,MAAC7I;QAAM8E,SAAS;;YACbyD;0BACD,KAACzJ;gBACC4K,SAAS3K,kBAAkBuI,YAAYqC,IAAI,CAACD,OAAO,EAAE;oBACnD/F,QAAQD;oBACR8E,SAAS5E;oBACT+E,SAAS3E;gBACX;gBACA4F,SAAStC,YAAYqC,IAAI,CAACC,OAAO;;YAElC1D,uBACC,KAAClG;gBACC8E,SAAS;gBACT+E,WAAU;gBACV9E,IAAI;oBAAE+E,WAAW;oBAAQC,GAAG;oBAAGC,GAAG;gBAAE;gBACpCC,cAAW;0BAEVR,SAASxF,GAAG,CAAC,CAAC3C,2BACb,KAAC7B;wBAAyB2F,SAAQ;wBAAWyE,WAAU;kCACrD,cAAA,KAACnK;sCACC,cAAA,MAACM;gCAAM8E,SAAS;;kDACd,MAAC9E;wCACCkF,WAAU;wCACVJ,SAAS;wCACTC,IAAI;4CAAEI,YAAY;wCAAa;;0DAE/B,KAACnF;gDAAM+E,IAAI;oDAAEmF,UAAU;oDAAGxF,UAAU;gDAAE;0DACnC8E,OAAOlI;;0DAEV,KAACL;gDAA6BO,QAAQF,WAAWE,MAAM;;4CACtD6C,WAAW/C;;;kDAEd,MAACtB;wCAAMkF,WAAU;wCAAMJ,SAAS;wCAAMC,IAAI;4CAAEI,YAAY;wCAAS;;0DAC/D,KAACjF;gDAAWkF,SAAQ;gDAAQI,OAAM;0DAC/BnE,yBAAyBC,YAAYC;;4CAEvC0B,sBAAsB3B,4BACrB,KAACjB;gDAAKkF,MAAK;gDAAQH,SAAQ;gDAAWI,OAAM;gDAAU/B,OAAM;iDAC1D;;;kDAEN,KAACvD;wCAAWkF,SAAQ;wCAAQI,OAAM;kDAC/B,CAAC,WAAW,EAAE8D,SAAShI,YAAY,kBAAkB,EAAER,mBAAmBoB,eAAeZ,aAAayE,WAAW;;oCAEnHzB,eAAe7D,uBAAuBa,WAAWoE,UAAU,EAAEC,MAAM,iBAClE,KAACzF;wCAAWkF,SAAQ;wCAAQI,OAAM;kDAC/B,CAAC,QAAQ,EAAE/E,uBAAuBa,WAAWoE,UAAU,EAAEC,MAAM,CAAC,CAAC,EAChElF,uBAAuBa,WAAWoE,UAAU,EAAEC,MAAM,KAAK,IAAI,SAAS,SACtE;yCAEF;oCACH7D,kBAAkBR,4BACjB,KAACpB;wCAAWkF,SAAQ;wCAAQI,OAAM;kDAC/B1D,kBAAkBR;yCAEnB;;;;uBApCCA,WAAWuG,EAAE;+BA2C5B,KAAC3I;gBACC+K,cAAW;gBACX7F,SAAS/E,sBACP+E,QAAQC,YAAYC,cACpBZ,0BACAM,2BACAJ;gBAEFuG,cAAc;oBAAE/F,SAAS;wBAAEgG,uBAAuBjG;oBAA0B;gBAAE;gBAC9EkG,SAAS/C,YAAY9F,MAAM,KAAK;gBAChCqH,MAAMY,SAASxF,GAAG,CAAC,CAAC3C,aAAgB,CAAA;wBAClCgJ,KAAKhJ,WAAWuG,EAAE;wBAClBvG;wBACAE,QAAQF,WAAWE,MAAM;wBACzBqC,QAAQvC,WAAWuC,MAAM,KAAK,SAAS,SAAS;wBAChDyB,aAAahE,WAAWgE,WAAW;wBACnCxB,OAAOxC,WAAWwC,KAAK;wBACvBnC,MAAMN,yBAAyBC,YAAYC;wBAC3C+H,UAAUA,SAAShI;wBACnBiJ,cAAczJ,mBACZoB,eAAeZ,aACfyE;wBAEFhE,YAAYD,kBAAkBR,eAAe;oBAC/C,CAAA;gBACA;;;;;WAKC,GACDkJ,YAAW;gBACXC,aAAa3D,WAAW2D,WAAW;gBACnCC,qBAAqB5D,WAAW4D,mBAAmB;gBACnDC,WAAW;gBACXC,oBAAoB;gBACpBC,aAAY;gBACZC,UAAU;;0BAGd,KAAC9L;gBACC8J,MAAMxB,YAAYwB,IAAI;gBACtBiC,UAAUzD,YAAYyD,QAAQ;gBAC9BC,UAAUvB,SAAS/H,MAAM;gBACzBuJ,SAAS3D,YAAY2D,OAAO;gBAC5BC,cAAc5D,YAAY6D,OAAO;gBACjCC,kBAAkB9D,YAAY+D,WAAW;;0BAG3C,KAAC9K;gBACCe,YAAYsF;gBACZrF,OAAOA;gBACPyE,KAAKA;gBACLsF,SAAS,IAAMzE,YAAY;;0BAG7B,KAAClH;gBACC4L,MAAMrI,QAAQoD;gBACdgF,SAAS,IAAM/E,cAAc;gBAC7BiF,SAAS;gBACTC,UAAS;0BAERnF,2BACC;;sCACE,KAACvG;uCAAauD,6BAAAA,OAAO,CAACgD,WAAWqB,MAAM,CAAC,qBAA1BrE,2BAA4BC,KAAK;;sCAC/C,KAAC1D;sCACC,cAAA,MAACG;gCAAM8E,SAAS;;kDACd,KAAChF;mDACEwD,8BAAAA,OAAO,CAACgD,WAAWqB,MAAM,CAAC,qBAA1BrE,4BAA4BE,IAAI;;kDAEnC,KAACvD;wCACCwD,OAAM;wCACNS,OAAOsC;wCACPiC,UAAU,CAACiD,QAAUjF,UAAUiF,MAAM7H,MAAM,CAACK,KAAK;wCACjDsH,SAAS;wCACTG,WAAW;4CAAEC,WAAW;gDAAEC,WAAW;4CAAI;wCAAE;;;;;sCAIjD,MAACjM;;8CACC,KAACJ;oCAAO2J,SAAS,IAAM5C,cAAc;8CAAO;;8CAC5C,KAAC/G;oCACCgG,OAAM;oCACNJ,SAAQ;oCACR+D,SAAS;wCACP,MAAM,EAAE7H,UAAU,EAAEqG,MAAM,EAAE,GAAGrB;wCAC/BC,cAAc;wCACd,KAAKmB,IAAIpG,YAAYqG,QAAQnB,OAAOa,IAAI,MAAMyE;oCAChD;+CAECxI,8BAAAA,OAAO,CAACgD,WAAWqB,MAAM,CAAC,qBAA1BrE,4BAA4BG,KAAK;;;;;qBAItC;;;;AAIZ;AACAoC,yBAAyBkG,WAAW,GAAG;AAEvC,eAAelG,yBAAwB"}
|
|
@@ -31,7 +31,7 @@ import { OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceSta
|
|
|
31
31
|
import { OutreachSequenceEditor } from "./sequence-editor.js";
|
|
32
32
|
import { OutreachSequenceReportCard } from "./sequence-report-card.js";
|
|
33
33
|
import { OutreachRouteError, useOutreachApi } from "./use-outreach-api.js";
|
|
34
|
-
import {
|
|
34
|
+
import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-data.js";
|
|
35
35
|
/**
|
|
36
36
|
* What stops a sequence being activated, read before anyone clicks: its
|
|
37
37
|
* mailbox not sending, and a footer the organization cannot write yet —
|
|
@@ -98,7 +98,6 @@ import { useOutreachEnrollments, useOutreachSequence, useOutreachSequenceLinks }
|
|
|
98
98
|
const { enqueueSnackbar } = useSnackbar();
|
|
99
99
|
const { data: user } = useUser();
|
|
100
100
|
const loaded = useOutreachSequence(orgId, sequenceId);
|
|
101
|
-
const enrollments = useOutreachEnrollments(props.tab === 'enrollments' ? orgId : null, sequenceId);
|
|
102
101
|
// One document however large the sequence, and read on both tabs, because
|
|
103
102
|
// the report card sits above them (AGL-3239).
|
|
104
103
|
const links = useOutreachSequenceLinks(orgId, sequenceId);
|
|
@@ -386,7 +385,8 @@ import { useOutreachEnrollments, useOutreachSequence, useOutreachSequenceLinks }
|
|
|
386
385
|
mailboxesPath: props.mailboxesPath,
|
|
387
386
|
onSaved: ()=>undefined
|
|
388
387
|
}) : /*#__PURE__*/ _jsx(OutreachEnrollmentsTable, {
|
|
389
|
-
|
|
388
|
+
orgId: orgId,
|
|
389
|
+
sequenceId: sequence.id,
|
|
390
390
|
steps: sequence.steps,
|
|
391
391
|
trackClicks: sequence.settings.trackClicks,
|
|
392
392
|
timeZone: (_ref1 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref1 : null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n const enrollments = useOutreachEnrollments(\n props.tab === 'enrollments' ? orgId : null,\n sequenceId,\n )\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n enrollments={enrollments}\n steps={sequence.steps}\n trackClicks={sequence.settings.trackClicks}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","useState","validateOutreachOrgSettings","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","enrollments","tab","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","detailPath","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","filter","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","map","li","data-testid","blocker","timeZone","timezone","value","onChange","_event","next","aria-label","orgMount","org","onSaved","undefined","trackClicks","enrollAction","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n orgId={orgId}\n sequenceId={sequence.id}\n steps={sequence.steps}\n trackClicks={sequence.settings.trackClicks}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","useState","validateOutreachOrgSettings","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","detailPath","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","filter","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","map","li","data-testid","blocker","timeZone","timezone","value","tab","onChange","_event","next","aria-label","orgMount","org","onSaved","undefined","trackClicks","enrollAction","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
@@ -28,6 +28,15 @@ export declare namespace OutreachSequencesSection {
|
|
|
28
28
|
/**
|
|
29
29
|
* Every sequence: its mailbox, its status, and how many people it enrolled
|
|
30
30
|
* and where they stand — a table on wider screens, cards on a phone.
|
|
31
|
+
*
|
|
32
|
+
* ## Asked of Firestore, a page at a time
|
|
33
|
+
*
|
|
34
|
+
* The Filters panel and the search box are the grid's; the clauses and the
|
|
35
|
+
* words go onto ONE query over the organization's sequences, newest first,
|
|
36
|
+
* and each page is a page of that query's matches (AGL-3321). A clause the
|
|
37
|
+
* query cannot hold beside the others is not applied, and the notice above
|
|
38
|
+
* the list says which and why — never answered over the sequences that
|
|
39
|
+
* happen to be loaded.
|
|
31
40
|
*/
|
|
32
41
|
export declare function OutreachSequenceList(props: {
|
|
33
42
|
orgId: string;
|