@aglyn/plugins-outreach 1.0.0-beta.200 → 1.0.0-beta.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/components/enrollment-actions.d.ts +33 -0
- package/src/lib/components/enrollment-actions.js +174 -0
- package/src/lib/components/enrollment-actions.js.map +1 -0
- package/src/lib/components/enrollment-detail.d.ts +21 -0
- package/src/lib/components/enrollment-detail.js +870 -0
- package/src/lib/components/enrollment-detail.js.map +1 -0
- package/src/lib/components/enrollment-filter-params.d.ts +58 -0
- package/src/lib/components/enrollment-filter-params.js +98 -0
- package/src/lib/components/enrollment-filter-params.js.map +1 -0
- package/src/lib/components/enrollments-table.d.ts +21 -0
- package/src/lib/components/enrollments-table.js +249 -210
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/outreach-console-page.js +10 -0
- package/src/lib/components/outreach-console-page.js.map +1 -1
- package/src/lib/components/sequence-detail.d.ts +2 -0
- package/src/lib/components/sequence-detail.js +70 -8
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-report-card.d.ts +17 -0
- package/src/lib/components/sequence-report-card.js +63 -15
- package/src/lib/components/sequence-report-card.js.map +1 -1
- package/src/lib/components/sequences-section.d.ts +4 -1
- package/src/lib/components/sequences-section.js +12 -1
- package/src/lib/components/sequences-section.js.map +1 -1
- package/src/lib/components/use-outreach-api.d.ts +3 -1
- package/src/lib/components/use-outreach-api.js +6 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/components/use-outreach-enrollment.d.ts +18 -0
- package/src/lib/components/use-outreach-enrollment.js +133 -0
- package/src/lib/components/use-outreach-enrollment.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +2 -0
- package/src/lib/constants/api-routes.js +1 -0
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/gates.d.ts +4 -3
- package/src/lib/engine/gates.js.map +1 -1
- package/src/lib/engine/gateway-block.d.ts +4 -8
- package/src/lib/engine/gateway-block.js +6 -26
- package/src/lib/engine/gateway-block.js.map +1 -1
- package/src/lib/engine/mail-gateway.d.ts +18 -73
- package/src/lib/engine/mail-gateway.js +32 -206
- package/src/lib/engine/mail-gateway.js.map +1 -1
- package/src/lib/model/enrollment-engagement.d.ts +26 -2
- package/src/lib/model/enrollment-engagement.js +40 -3
- package/src/lib/model/enrollment-engagement.js.map +1 -1
- package/src/lib/model/enrollment-history.d.ts +40 -0
- package/src/lib/model/enrollment-history.js +95 -0
- package/src/lib/model/enrollment-history.js.map +1 -0
- package/src/lib/model/enrollment-list-query.d.ts +6 -0
- package/src/lib/model/enrollment-list-query.js +35 -1
- package/src/lib/model/enrollment-list-query.js.map +1 -1
- package/src/lib/model/enrollment-timeline.d.ts +78 -0
- package/src/lib/model/enrollment-timeline.js +430 -0
- package/src/lib/model/enrollment-timeline.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +24 -0
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +78 -29
- package/src/lib/model/outreach.types.js +33 -8
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +26 -3
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/enrollment-routes.d.ts +10 -0
- package/src/lib/routes/enrollment-routes.js +82 -1
- package/src/lib/routes/enrollment-routes.js.map +1 -1
- package/src/lib/routes/register-routes.js +14 -4
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/routes/route-deps.d.ts +3 -1
- package/src/lib/routes/route-deps.js.map +1 -1
- package/src/lib/runtime/click-events.d.ts +10 -5
- package/src/lib/runtime/click-events.js +45 -6
- package/src/lib/runtime/click-events.js.map +1 -1
- package/src/lib/runtime/gateway-ledger.d.ts +6 -0
- package/src/lib/runtime/gateway-ledger.js +9 -3
- package/src/lib/runtime/gateway-ledger.js.map +1 -1
- package/src/lib/runtime/host-suspension.d.ts +30 -0
- package/src/lib/runtime/host-suspension.js +35 -0
- package/src/lib/runtime/host-suspension.js.map +1 -0
- package/src/lib/runtime/person-erasure.d.ts +4 -1
- package/src/lib/runtime/person-erasure.js +14 -3
- package/src/lib/runtime/person-erasure.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +34 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/runtime-deps.d.ts +27 -1
- package/src/lib/runtime/runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +46 -6
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +4 -0
- package/src/lib/runtime/sync-job.js.map +1 -1
- package/src/lib/storage/domain-intel-store.d.ts +61 -43
- package/src/lib/storage/domain-intel-store.js +138 -226
- package/src/lib/storage/domain-intel-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 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":"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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, type ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n listFilterGridColumns,\n type ListFilterClause,\n type ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Card,\n CardContent,\n Chip,\n Link,\n Stack,\n Tooltip,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { type MouseEvent, type ReactNode, useMemo } from 'react'\nimport {\n outreachClickCountLabel,\n outreachClickSummary,\n type OutreachClickSummary,\n} from '../model/enrollment-engagement'\nimport {\n OUTREACH_ENROLLMENT_CLICK_FIELDS,\n OUTREACH_ENROLLMENT_LIST_QUERY,\n} 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 { useOutreachEnrollmentActions } from './enrollment-actions'\nimport { OUTREACH_CLICKED_FILTER_VALUE, OUTREACH_UNCLICKED_FILTER_VALUE } from './enrollment-filter-params'\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 * Opens one person's page (AGL-3332): a row's click, Enter on a focused\n * row, or the name on a phone's card. The row's menu never opens it.\n */\n onOpen?: (enrollment: OutreachEnrollment) => void\n /** The person's page, for the name on a phone's card, which is a real link. */\n hrefFor?: (enrollment: OutreachEnrollment) => string\n /**\n * The clauses the table is narrowed by, when the page holds them — so the\n * Results card's \"Clicked\" and \"Links followed\" can set one (AGL-3332).\n * Omitted, the table holds its own.\n */\n clauses?: readonly ListFilterClause[]\n onClausesChange?: (clauses: ListFilterClause[]) => void\n /** Destinations the sequence's links went to, for the \"Link followed\" filter's choices. */\n linkOptions?: readonly ListFilterOption[]\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/** 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/*\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 *\n * A sequence that counts clicks adds two more (AGL-3332), on the same query:\n * whether the person clicked, and a destination they followed — matched\n * whole against the destinations stored on the enrollment, never by a piece\n * of its address, so \"followed /pricing\" does not also list the people who\n * followed \"/pricing/enterprise\".\n */\nconst BASE_FILTER_FIELDS = OUTREACH_ENROLLMENT_LIST_QUERY.fields.filter(\n (field) => !(OUTREACH_ENROLLMENT_CLICK_FIELDS as readonly string[]).includes(field.column),\n)\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 clicked: 'Clicked',\n link: 'Link followed',\n}\nconst BASE_FILTER_OPTIONS: Record<string, readonly ListFilterOption[]> = {\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}\nconst CLICKED_OPTIONS: readonly ListFilterOption[] = [\n { value: OUTREACH_CLICKED_FILTER_VALUE, label: 'Yes' },\n { value: OUTREACH_UNCLICKED_FILTER_VALUE, label: 'No' },\n]\n/** Columns a reader can show from the column chooser, off until they do (AGL-3332). */\nconst CLICK_DETAIL_COLUMNS = ['linksFollowed', 'lastClick', 'scannerClicks'] as const\n\n/** The table's grammar, with the click fields when the sequence counts clicks. */\nfunction filterFieldsFor(trackClicks: boolean): readonly ListFilterField[] {\n return trackClicks ? OUTREACH_ENROLLMENT_LIST_QUERY.fields : BASE_FILTER_FIELDS\n}\n\n/** What the grid's cells and its export read off one enrollment. */\nfunction filterRow(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n clicks: OutreachClickSummary,\n) {\n return {\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 clicked: clicks.clicks > 0 ? OUTREACH_CLICKED_FILTER_VALUE : OUTREACH_UNCLICKED_FILTER_VALUE,\n link: clicks.followed,\n }\n}\n\n/** The stop reason: its short label, with every word of it on hover and on the person's page. */\nfunction StopReasonCell(props: { enrollment: OutreachEnrollment }) {\n const { enrollment } = props\n if (!enrollment.stopReason) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n const label = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n const text = (\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: 'normal',\n lineHeight: 1.3,\n ...(enrollment.stopDetail\n ? { textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }\n : {}),\n }}\n >\n {label}\n </Typography>\n )\n return enrollment.stopDetail ? (\n <Tooltip title={outreachStopLabel(enrollment)} describeChild>\n {text}\n </Tooltip>\n ) : (\n text\n )\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.2,\n minWidth: 190,\n sortable: false,\n // What the export writes for a column that is drawn rather than read.\n valueGetter: (_value: unknown, row: { enrollment: OutreachEnrollment }) =>\n row.enrollment.contactName\n ? `${row.enrollment.contactName} <${row.enrollment.email}>`\n : row.enrollment.email,\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: 110,\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: 170, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 170,\n sortable: false,\n },\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n description: 'A person’s clicks, and how many different links they followed. Scanners are not counted.',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: { clicks: string } }) => (\n <Typography variant=\"body2\" color={row.clicks === '—' ? 'text.secondary' : undefined}>\n {row.clicks}\n </Typography>\n ),\n },\n {\n field: 'linksFollowed',\n headerName: 'Links followed',\n flex: 1,\n minWidth: 220,\n sortable: false,\n },\n { field: 'lastClick', headerName: 'Last click', width: 170, sortable: false },\n {\n field: 'scannerClicks',\n headerName: 'Scanner clicks',\n description: 'Clicks a security scanner made, which are not counted as the person’s.',\n width: 130,\n sortable: false,\n },\n ]\n : []),\n {\n field: 'stopReason',\n headerName: 'Stop reason',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => <StopReasonCell enrollment={row.enrollment} />,\n },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/** A plain click on a link, which the page handles itself; any other is the browser's. */\nconst plainClick = (event: MouseEvent) =>\n !event.defaultPrevented &&\n event.button === 0 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey\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 *\n * Each row opens the person's own page (AGL-3332), which holds everything\n * else known about them: so a cell says one thing, whole, and the detail\n * behind it is a click away rather than cut off at the column's edge.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { steps, timeZone, api, onOpen, hrefFor } = props\n const trackClicks = props.trackClicks === true\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const actions = useOutreachEnrollmentActions({ api, steps })\n const fields = useMemo(() => filterFieldsFor(trackClicks), [trackClicks])\n const gridFilter = useListGridFilter({\n selectFields: ['status', 'target', 'stopReason', 'clicked', 'link'],\n ...(props.clauses !== undefined\n ? { clauses: props.clauses, onChange: props.onClausesChange }\n : {}),\n })\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. The\n * click filters (AGL-3332) are on it too, answered by the `clicked` flag\n * and the destinations the click route stores on each enrollment.\n */\n const enrollments = useOutreachEnrollmentList(props.orgId, props.sequenceId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n const loadedData = enrollments.rows\n const summaries = useMemo(\n () =>\n new Map(\n loadedData.map((enrollment) => [enrollment.id, outreachClickSummary(enrollment.engagement)]),\n ),\n [loadedData],\n )\n const clicksOf = (enrollment: OutreachEnrollment) =>\n summaries.get(enrollment.id) ?? outreachClickSummary(enrollment.engagement)\n\n /*\n * The destinations the \"Link followed\" filter offers: the sequence's own\n * rollup, and any a loaded person followed that it does not list — the\n * rollup is capped, and a filter that could not name a destination a\n * person followed would hide them from it.\n */\n const linkOptions = useMemo(() => {\n const options = new Map<string, string>()\n for (const option of props.linkOptions ?? []) options.set(option.value, option.label)\n for (const summary of summaries.values()) {\n for (const url of summary.followed) if (!options.has(url)) options.set(url, url)\n }\n return [...options].map(([value, label]) => ({ value, label }))\n }, [props.linkOptions, summaries])\n const filterOptions = useMemo(\n () =>\n trackClicks\n ? { ...BASE_FILTER_OPTIONS, clicked: CLICKED_OPTIONS, link: linkOptions }\n : BASE_FILTER_OPTIONS,\n [trackClicks, linkOptions],\n )\n const hiddenColumns = useMemo(\n () => ({\n ...hiddenFilterVisibility(fields, ['status', 'stopReason']),\n ...(trackClicks\n ? Object.fromEntries(CLICK_DETAIL_COLUMNS.map((column) => [column, false]))\n : {}),\n }),\n [fields, trackClicks],\n )\n /*\n * A person who followed this destination before each click was recorded\n * on its own, and another link after it, is known by their LAST link only\n * — so a \"Link followed\" filter says it may miss them rather than\n * implying the list is whole. Which people those are is exactly what the\n * query cannot see, so the caveat stands whenever the filter does.\n */\n const linkFilterMayMiss =\n trackClicks && gridFilter.clauses.some((clause) => clause.field === 'link')\n\n const chips = (\n <ListFilterChips\n fields={fields}\n headers={ENROLLMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\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={actions.menuItems(enrollment)}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const personName = (enrollment: OutreachEnrollment) => {\n const name = enrollment.contactName || enrollment.email\n if (!hrefFor) return <Typography variant=\"body2\">{name}</Typography>\n return (\n <Link\n href={hrefFor(enrollment)}\n variant=\"body2\"\n underline=\"hover\"\n onClick={(event) => {\n if (!onOpen || !plainClick(event)) return\n event.preventDefault()\n onOpen(enrollment)\n }}\n >\n {name}\n </Link>\n )\n }\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n {personName(enrollment)}\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,\n headers: ENROLLMENT_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={enrollments.plan.notices}\n />\n {linkFilterMayMiss ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n Clicks made before each one was recorded on its own kept only the person’s last link, so\n someone who followed this link and then another back then is not listed here.\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 const clicks = clicksOf(enrollment)\n return (\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 && clicks.clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicks: ${outreachClickCountLabel(clicks)}`}\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 })}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={listFilterGridColumns(\n columns(rowActions, trackClicks),\n fields,\n filterOptions,\n ENROLLMENT_FILTER_HEADERS,\n )}\n initialState={{ columns: { columnVisibilityModel: hiddenColumns } }}\n loading={enrollments.status === 'loading'}\n rows={pageRows.map((enrollment) => {\n const clicks = clicksOf(enrollment)\n return {\n $id: enrollment.id,\n ...filterRow(enrollment, steps, clicks),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n // What the export writes; the cells draw these their own way.\n clicks: outreachClickCountLabel(clicks),\n linksFollowed: clicks.followed.join(' ') || '—',\n lastClick: clicks.clicks\n ? formatOutreachTime(enrollment.engagement?.lastClickAtMs, timeZone)\n : '—',\n scannerClicks: clicks.machineClicks ? String(clicks.machineClicks) : '—',\n stopReason: outreachStopLabel(enrollment) || '—',\n }\n })}\n onOpen={\n onOpen\n ? (_id, row: { enrollment: OutreachEnrollment }) => onOpen(row.enrollment)\n : undefined\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 {actions.dialogs}\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","Card","CardContent","Chip","Link","Stack","Tooltip","Typography","useMediaQuery","useTheme","useMemo","outreachClickCountLabel","outreachClickSummary","OUTREACH_ENROLLMENT_CLICK_FIELDS","OUTREACH_ENROLLMENT_LIST_QUERY","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_STOP_REASONS","OUTREACH_TASK_KIND_LABELS","useOutreachEnrollmentActions","OUTREACH_CLICKED_FILTER_VALUE","OUTREACH_UNCLICKED_FILTER_VALUE","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","outreachStepIsCurated","Boolean","stepOverrides","String","BASE_FILTER_FIELDS","fields","filter","field","includes","column","ENROLLMENT_FILTER_HEADERS","target","email","enrolled","clicked","link","BASE_FILTER_OPTIONS","map","value","label","CLICKED_OPTIONS","CLICK_DETAIL_COLUMNS","filterFieldsFor","trackClicks","filterRow","clicks","contactName","followed","StopReasonCell","props","variant","color","text","sx","whiteSpace","lineHeight","textDecoration","textUnderlineOffset","cursor","title","describeChild","columns","rowActions","headerName","flex","minWidth","sortable","valueGetter","_value","row","renderCell","spacing","justifyContent","height","direction","alignItems","noWrap","size","width","description","undefined","plainClick","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","OutreachEnrollmentsTable","timeZone","api","onOpen","hrefFor","theme","narrow","breakpoints","down","actions","gridFilter","selectFields","clauses","onChange","onClausesChange","filtering","searchWords","some","word","trim","enrollments","orgId","sequenceId","search","loadedData","rows","summaries","Map","id","engagement","clicksOf","get","linkOptions","options","option","set","summary","values","url","has","filterOptions","hiddenColumns","Object","fromEntries","linkFilterMayMiss","clause","chips","headers","setClauses","what","page","action","enrollAction","items","menuItems","nextSend","nextDueAtMs","personName","name","href","underline","onClick","preventDefault","person","pageRows","refused","plan","notices","component","listStyle","p","m","aria-label","flexGrow","initialState","columnVisibilityModel","loading","$id","lastActivity","linksFollowed","join","lastClick","lastClickAtMs","scannerClicks","machineClicks","_id","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","dialogs","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,QAA8B,yCAAwC;AACrG,SACEC,qBAAqB,QAGhB,8CAA6C;AACpD,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,SACEC,IAAI,EACJC,WAAW,EACXC,IAAI,EACJC,IAAI,EACJC,KAAK,EACLC,OAAO,EACPC,UAAU,EACVC,aAAa,EACbC,QAAQ,QACH,gBAAe;AACtB,SAA0CC,OAAO,QAAQ,QAAO;AAChE,SACEC,uBAAuB,EACvBC,oBAAoB,QAEf,oCAAgC;AACvC,SACEC,gCAAgC,EAChCC,8BAA8B,QACzB,oCAAgC;AACvC,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,yBAAyB,QAGpB,6BAAyB;AAChC,SAASC,4BAA4B,QAAQ,0BAAsB;AACnE,SAASC,6BAA6B,EAAEC,+BAA+B,QAAQ,gCAA4B;AAC3G,SACEC,kBAAkB,EAClBC,iCAAiC,EACjCC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,eAAe,EACfC,mBAAmB,QACd,mBAAe;AAEtB,SAASC,yBAAyB,QAAQ,yBAAqB;AAsC/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,SACblB,yBAAyB,CAACiB,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;AAEA,yFAAyF,GACzF,OAAO,SAASkB,sBACdlB,UAAmE;QAEpDA;IAAf,OAAOmB,SAAQnB,4BAAAA,WAAWoB,aAAa,qBAAxBpB,yBAA0B,CAACqB,OAAOrB,WAAWG,SAAS,EAAE;AACzE;AAEA;;;;;;;;;;;;;CAaC,GACD,MAAMmB,qBAAqBrC,+BAA+BsC,MAAM,CAACC,MAAM,CACrE,CAACC,QAAU,CAAC,AAACzC,iCAAuD0C,QAAQ,CAACD,MAAME,MAAM;AAE3F,MAAMC,4BAA8D;IAClE1B,QAAQ;IACR2B,QAAQ;IACRpB,YAAY;IACZqB,OAAO;IACPC,UAAU;IACVC,SAAS;IACTC,MAAM;AACR;AACA,MAAMC,sBAAmE;IACvEhC,QAAQhB,6BAA6BiD,GAAG,CAAC,CAACjC,SAAY,CAAA;YACpDkC,OAAOlC;YACPmC,OAAO5C,iCAAiC,CAACS,OAAO;QAClD,CAAA;IACA2B,QAAQ;QACN;YAAEO,OAAO;YAAWC,OAAO;QAAU;QACrC;YAAED,OAAO;YAAQC,OAAO;QAAO;KAChC;IACD5B,YAAYtB,sBAAsBgD,GAAG,CAAC,CAACzB,SAAY,CAAA;YACjD0B,OAAO1B;YACP2B,OAAO3C,2BAA2B,CAACgB,OAAO;QAC5C,CAAA;AACF;AACA,MAAM4B,kBAA+C;IACnD;QAAEF,OAAO9C;QAA+B+C,OAAO;IAAM;IACrD;QAAED,OAAO7C;QAAiC8C,OAAO;IAAK;CACvD;AACD,qFAAqF,GACrF,MAAME,uBAAuB;IAAC;IAAiB;IAAa;CAAgB;AAE5E,gFAAgF,GAChF,SAASC,gBAAgBC,WAAoB;IAC3C,OAAOA,cAAcxD,+BAA+BsC,MAAM,GAAGD;AAC/D;AAEA,kEAAkE,GAClE,SAASoB,UACP1C,UAA8B,EAC9BC,KAAsC,EACtC0C,MAA4B;IAE5B,OAAO;QACL3C;QACAE,QAAQF,WAAWE,MAAM;QACzB2B,QAAQ7B,WAAW6B,MAAM,KAAK,SAAS,SAAS;QAChDe,aAAa5C,WAAW4C,WAAW;QACnCd,OAAO9B,WAAW8B,KAAK;QACvBzB,MAAMN,yBAAyBC,YAAYC;QAC3CQ,YAAYD,kBAAkBR;QAC9BgC,SAASW,OAAOA,MAAM,GAAG,IAAIrD,gCAAgCC;QAC7D0C,MAAMU,OAAOE,QAAQ;IACvB;AACF;AAEA,+FAA+F,GAC/F,SAASC,eAAeC,KAAyC;QASjDrD;IARd,MAAM,EAAEM,UAAU,EAAE,GAAG+C;IACvB,IAAI,CAAC/C,WAAWS,UAAU,EAAE;QAC1B,qBACE,KAAC/B;YAAWsE,SAAQ;YAAQC,OAAM;sBAAiB;;IAIvD;IACA,MAAMZ,SAAQ3C,qDAAAA,2BAA2B,CAACM,WAAWS,UAAU,CAAC,YAAlDf,qDAAsD;IACpE,MAAMwD,qBACJ,KAACxE;QACCsE,SAAQ;QACRG,IAAI;YACFC,YAAY;YACZC,YAAY;WACRrD,WAAWW,UAAU,GACrB;YAAE2C,gBAAgB;YAAoBC,qBAAqB;YAAGC,QAAQ;QAAO,IAC7E,CAAC;kBAGNnB;;IAGL,OAAOrC,WAAWW,UAAU,iBAC1B,KAAClC;QAAQgF,OAAOjD,kBAAkBR;QAAa0D,aAAa;kBACzDR;SAGHA;AAEJ;AAEA,yFAAyF,GACzF,SAASS,QACPC,UAAyD,EACzDnB,WAAoB;IAEpB,OAAO;QACL;YACEhB,OAAO;YACPoC,YAAY;YACZC,MAAM;YACNC,UAAU;YACVC,UAAU;YACV,sEAAsE;YACtEC,aAAa,CAACC,QAAiBC,MAC7BA,IAAInE,UAAU,CAAC4C,WAAW,GACtB,GAAGuB,IAAInE,UAAU,CAAC4C,WAAW,CAAC,EAAE,EAAEuB,IAAInE,UAAU,CAAC8B,KAAK,CAAC,CAAC,CAAC,GACzDqC,IAAInE,UAAU,CAAC8B,KAAK;YAC1BsC,YAAY,CAAC,EAAED,GAAG,EAAE,iBAClB,MAAC3F;oBACC6F,SAAS;oBACTlB,IAAI;wBAAEmB,gBAAgB;wBAAUC,QAAQ;wBAAQR,UAAU;oBAAE;;sCAE5D,MAACvF;4BAAMgG,WAAU;4BAAMH,SAAS;4BAAMlB,IAAI;gCAAEsB,YAAY;gCAAUV,UAAU;4BAAE;;8CAC5E,KAACrF;oCAAWsE,SAAQ;oCAAQ0B,MAAM;8CAC/BP,IAAInE,UAAU,CAAC4C,WAAW,IAAIuB,IAAInE,UAAU,CAAC8B,KAAK;;gCAEpDqC,IAAInE,UAAU,CAAC6B,MAAM,KAAK,uBACzB,KAACvD;oCAAKqG,MAAK;oCAAQ3B,SAAQ;oCAAWX,OAAM;qCAC1C;;;wBAEL8B,IAAInE,UAAU,CAAC4C,WAAW,iBACzB,KAAClE;4BAAWsE,SAAQ;4BAAUC,OAAM;4BAAiByB,MAAM;sCACxDP,IAAInE,UAAU,CAAC8B,KAAK;6BAErB;;;QAGV;QACA;YACEL,OAAO;YACPoC,YAAY;YACZe,OAAO;YACPR,YAAY,CAAC,EAAED,GAAG,EAAE,iBAClB,KAACxE;oBAA6BO,QAAQiE,IAAIjE,MAAM;;QAEpD;QACA;YACEuB,OAAO;YACPoC,YAAY;YACZC,MAAM;YACNC,UAAU;YACVK,YAAY,CAAC,EAAED,GAAG,EAAE,iBAClB,MAAC3F;oBAAMgG,WAAU;oBAAMH,SAAS;oBAAMlB,IAAI;wBAAEsB,YAAY;wBAAUF,QAAQ;wBAAQR,UAAU;oBAAE;;sCAC5F,KAACrF;4BAAWsE,SAAQ;4BAAQ0B,MAAM;sCAC/BP,IAAI9D,IAAI;;wBAEVa,sBAAsBiD,IAAInE,UAAU,kBACnC,KAAC1B;4BAAKqG,MAAK;4BAAQ3B,SAAQ;4BAAWC,OAAM;4BAAUZ,OAAM;6BAC1D;;;QAGV;QACA;YAAEZ,OAAO;YAAYoC,YAAY;YAAae,OAAO;YAAKZ,UAAU;QAAM;QAC1E;YACEvC,OAAO;YACPoC,YAAY;YACZe,OAAO;YACPZ,UAAU;QACZ;WACIvB,cACA;YACE;gBACEhB,OAAO;gBACPoC,YAAY;gBACZgB,aAAa;gBACbD,OAAO;gBACPZ,UAAU;gBACVI,YAAY,CAAC,EAAED,GAAG,EAA+B,iBAC/C,KAACzF;wBAAWsE,SAAQ;wBAAQC,OAAOkB,IAAIxB,MAAM,KAAK,MAAM,mBAAmBmC;kCACxEX,IAAIxB,MAAM;;YAGjB;YACA;gBACElB,OAAO;gBACPoC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,UAAU;YACZ;YACA;gBAAEvC,OAAO;gBAAaoC,YAAY;gBAAce,OAAO;gBAAKZ,UAAU;YAAM;YAC5E;gBACEvC,OAAO;gBACPoC,YAAY;gBACZgB,aAAa;gBACbD,OAAO;gBACPZ,UAAU;YACZ;SACD,GACD,EAAE;QACN;YACEvC,OAAO;YACPoC,YAAY;YACZC,MAAM;YACNC,UAAU;YACVK,YAAY,CAAC,EAAED,GAAG,EAAE,iBAAK,KAACrB;oBAAe9C,YAAYmE,IAAInE,UAAU;;QACrE;QACAhC,kBAAkB,CAACmG,MAAQP,WAAWO,IAAInE,UAAU,GAAG;YAAE4E,OAAO;QAAG;KACpE;AACH;AAEA,wFAAwF,GACxF,MAAMG,aAAa,CAACC,QAClB,CAACA,MAAMC,gBAAgB,IACvBD,MAAME,MAAM,KAAK,KACjB,CAACF,MAAMG,OAAO,IACd,CAACH,MAAMI,OAAO,IACd,CAACJ,MAAMK,QAAQ,IACf,CAACL,MAAMM,MAAM;AAEf;;;;;;;;;CASC,GACD,OAAO,SAASC,yBAAyBxC,KAAoC;IAC3E,MAAM,EAAE9C,KAAK,EAAEuF,QAAQ,EAAEC,GAAG,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAG5C;IAClD,MAAMN,cAAcM,MAAMN,WAAW,KAAK;IAC1C,MAAMmD,QAAQhH;IACd,MAAMiH,SAASlH,cAAciH,MAAME,WAAW,CAACC,IAAI,CAAC;IACpD,MAAMC,UAAU3G,6BAA6B;QAAEoG;QAAKxF;IAAM;IAC1D,MAAMsB,SAAS1C,QAAQ,IAAM2D,gBAAgBC,cAAc;QAACA;KAAY;IACxE,MAAMwD,aAAa9H,kBAAkB;QACnC+H,cAAc;YAAC;YAAU;YAAU;YAAc;YAAW;SAAO;OAC/DnD,MAAMoD,OAAO,KAAKrB,YAClB;QAAEqB,SAASpD,MAAMoD,OAAO;QAAEC,UAAUrD,MAAMsD,eAAe;IAAC,IAC1D,CAAC;IAEP,MAAMC,YACJL,WAAWE,OAAO,CAAC/F,MAAM,GAAG,KAAK6F,WAAWM,WAAW,CAACC,IAAI,CAAC,CAACC,OAASA,KAAKC,IAAI;IAClF;;;;;;;GAOC,GACD,MAAMC,cAAc7G,0BAA0BiD,MAAM6D,KAAK,EAAE7D,MAAM8D,UAAU,EAAE;QAC3EV,SAASF,WAAWE,OAAO;QAC3BW,QAAQb,WAAWM,WAAW;IAChC;IACA,MAAMQ,aAAaJ,YAAYK,IAAI;IACnC,MAAMC,YAAYpI,QAChB,IACE,IAAIqI,IACFH,WAAW5E,GAAG,CAAC,CAACnC,aAAe;gBAACA,WAAWmH,EAAE;gBAAEpI,qBAAqBiB,WAAWoH,UAAU;aAAE,IAE/F;QAACL;KAAW;IAEd,MAAMM,WAAW,CAACrH;YAChBiH;gBAAAA,iBAAAA,UAAUK,GAAG,CAACtH,WAAWmH,EAAE,aAA3BF,iBAAgClI,qBAAqBiB,WAAWoH,UAAU;;IAE5E;;;;;GAKC,GACD,MAAMG,cAAc1I,QAAQ;YAELkE;QADrB,MAAMyE,UAAU,IAAIN;QACpB,KAAK,MAAMO,WAAU1E,qBAAAA,MAAMwE,WAAW,YAAjBxE,qBAAqB,EAAE,CAAEyE,QAAQE,GAAG,CAACD,OAAOrF,KAAK,EAAEqF,OAAOpF,KAAK;QACpF,KAAK,MAAMsF,WAAWV,UAAUW,MAAM,GAAI;YACxC,KAAK,MAAMC,OAAOF,QAAQ9E,QAAQ,CAAE,IAAI,CAAC2E,QAAQM,GAAG,CAACD,MAAML,QAAQE,GAAG,CAACG,KAAKA;QAC9E;QACA,OAAO;eAAIL;SAAQ,CAACrF,GAAG,CAAC,CAAC,CAACC,OAAOC,MAAM,GAAM,CAAA;gBAAED;gBAAOC;YAAM,CAAA;IAC9D,GAAG;QAACU,MAAMwE,WAAW;QAAEN;KAAU;IACjC,MAAMc,gBAAgBlJ,QACpB,IACE4D,cACI,aAAKP;YAAqBF,SAASM;YAAiBL,MAAMsF;aAC1DrF,qBACN;QAACO;QAAa8E;KAAY;IAE5B,MAAMS,gBAAgBnJ,QACpB,IAAO,aACFZ,uBAAuBsD,QAAQ;YAAC;YAAU;SAAa,GACtDkB,cACAwF,OAAOC,WAAW,CAAC3F,qBAAqBJ,GAAG,CAAC,CAACR,SAAW;gBAACA;gBAAQ;aAAM,KACvE,CAAC,IAEP;QAACJ;QAAQkB;KAAY;IAEvB;;;;;;GAMC,GACD,MAAM0F,oBACJ1F,eAAewD,WAAWE,OAAO,CAACK,IAAI,CAAC,CAAC4B,SAAWA,OAAO3G,KAAK,KAAK;IAEtE,MAAM4G,sBACJ,KAAC3K;QACC6D,QAAQA;QACR+G,SAAS1G;QACTuE,SAASF,WAAWE,OAAO;QAC3BC,UAAUH,WAAWsC,UAAU;QAC/Bf,SAASO;;IAGb,IAAIpB,YAAYzG,MAAM,KAAK,aAAa,CAACoG,WACvC,qBAAO,KAAC1G;QAAgByC,OAAM;;IAChC,IAAIsE,YAAYzG,MAAM,KAAK,WAAWyG,YAAYzG,MAAM,KAAK,WAAW;QACtE,qBACE,MAAC1B;YAAM6F,SAAS;;gBACbgE;8BACD,KAACxI;oBAAoBK,QAAQyG,YAAYzG,MAAM;oBAAEsI,MAAK;;;;IAG5D;IACA,IACE7B,YAAYzG,MAAM,KAAK,WACvB,CAACyG,YAAYK,IAAI,CAAC5G,MAAM,IACxB,CAACkG,aACDK,YAAY8B,IAAI,KAAK,GACrB;QACA,qBACE,KAACrK;YAAK4E,SAAQ;sBACZ,cAAA,KAACvF;gBACC4E,OAAM;gBACNwC,aAAY;gBACZ6D,QAAQ3F,MAAM4F,YAAY;;;IAIlC;IAEA,MAAM/E,aAAa,CAAC5D,2BAClB,KAAClC;YACCuE,OAAOrC,WAAW4C,WAAW,IAAI5C,WAAW8B,KAAK;YACjD8G,OAAO5C,QAAQ6C,SAAS,CAAC7I;;IAG7B,MAAM8I,WAAW,CAAC9I,aAChBA,WAAWE,MAAM,KAAK,YAAYF,WAAWE,MAAM,KAAK,WACpDV,mBAAmBQ,WAAW+I,WAAW,EAAEvD,YAC3C;IACN,MAAMwD,aAAa,CAAChJ;QAClB,MAAMiJ,OAAOjJ,WAAW4C,WAAW,IAAI5C,WAAW8B,KAAK;QACvD,IAAI,CAAC6D,SAAS,qBAAO,KAACjH;YAAWsE,SAAQ;sBAASiG;;QAClD,qBACE,KAAC1K;YACC2K,MAAMvD,QAAQ3F;YACdgD,SAAQ;YACRmG,WAAU;YACVC,SAAS,CAACpE;gBACR,IAAI,CAACU,UAAU,CAACX,WAAWC,QAAQ;gBACnCA,MAAMqE,cAAc;gBACpB3D,OAAO1F;YACT;sBAECiJ;;IAGP;IACA,MAAMK,SAAS,CAACtJ,2BACd,MAACxB;YAAM6F,SAAS;;8BACd,MAAC7F;oBAAMgG,WAAU;oBAAMH,SAAS;oBAAMlB,IAAI;wBAAEsB,YAAY;oBAAS;;wBAC9DuE,WAAWhJ;wBACXA,WAAW6B,MAAM,KAAK,uBACrB,KAACvD;4BAAKqG,MAAK;4BAAQ3B,SAAQ;4BAAWX,OAAM;6BAC1C;;;gBAELrC,WAAW4C,WAAW,iBACrB,KAAClE;oBAAWsE,SAAQ;oBAAUC,OAAM;8BACjCjD,WAAW8B,KAAK;qBAEjB;;;IAIR,MAAMyH,WAAW5C,YAAYK,IAAI;IAEjC,qBACE,MAACxI;QAAM6F,SAAS;;YACbgE;0BACD,KAAC1K;gBACC6L,SAAS5L,kBAAkB+I,YAAY8C,IAAI,CAACD,OAAO,EAAE;oBACnDjI;oBACA+G,SAAS1G;oBACT4F,SAASO;gBACX;gBACA2B,SAAS/C,YAAY8C,IAAI,CAACC,OAAO;;YAElCvB,kCACC,KAACzJ;gBAAWsE,SAAQ;gBAAUC,OAAM;0BAAiB;iBAInD;YACH4C,uBACC,KAACrH;gBACC6F,SAAS;gBACTsF,WAAU;gBACVxG,IAAI;oBAAEyG,WAAW;oBAAQC,GAAG;oBAAGC,GAAG;gBAAE;gBACpCC,cAAW;0BAEVR,SAASpH,GAAG,CAAC,CAACnC;oBACb,MAAM2C,SAAS0E,SAASrH;oBACxB,qBACE,KAAC5B;wBAAyB4E,SAAQ;wBAAW2G,WAAU;kCACrD,cAAA,KAACtL;sCACC,cAAA,MAACG;gCAAM6F,SAAS;;kDACd,MAAC7F;wCACCgG,WAAU;wCACVH,SAAS;wCACTlB,IAAI;4CAAEsB,YAAY;wCAAa;;0DAE/B,KAACjG;gDAAM2E,IAAI;oDAAE6G,UAAU;oDAAGjG,UAAU;gDAAE;0DACnCuF,OAAOtJ;;0DAEV,KAACL;gDAA6BO,QAAQF,WAAWE,MAAM;;4CACtD0D,WAAW5D;;;kDAEd,MAACxB;wCAAMgG,WAAU;wCAAMH,SAAS;wCAAMlB,IAAI;4CAAEsB,YAAY;wCAAS;;0DAC/D,KAAC/F;gDAAWsE,SAAQ;gDAAQC,OAAM;0DAC/BlD,yBAAyBC,YAAYC;;4CAEvCiB,sBAAsBlB,4BACrB,KAAC1B;gDAAKqG,MAAK;gDAAQ3B,SAAQ;gDAAWC,OAAM;gDAAUZ,OAAM;iDAC1D;;;kDAEN,KAAC3D;wCAAWsE,SAAQ;wCAAQC,OAAM;kDAC/B,CAAC,WAAW,EAAE6F,SAAS9I,YAAY,kBAAkB,EAAER,mBAAmBoB,eAAeZ,aAAawF,WAAW;;oCAEnH/C,eAAeE,OAAOA,MAAM,iBAC3B,KAACjE;wCAAWsE,SAAQ;wCAAQC,OAAM;kDAC/B,CAAC,QAAQ,EAAEnE,wBAAwB6D,SAAS;yCAE7C;oCACHnC,kBAAkBR,4BACjB,KAACtB;wCAAWsE,SAAQ;wCAAQC,OAAM;kDAC/BzC,kBAAkBR;yCAEnB;;;;uBAlCCA,WAAWmH,EAAE;gBAuC5B;+BAGF,KAACpJ;gBACCgM,cAAW;gBACXpG,SAASzF,sBACPyF,QAAQC,YAAYnB,cACpBlB,QACAwG,eACAnG;gBAEFqI,cAAc;oBAAEtG,SAAS;wBAAEuG,uBAAuBlC;oBAAc;gBAAE;gBAClEmC,SAASxD,YAAYzG,MAAM,KAAK;gBAChC8G,MAAMuC,SAASpH,GAAG,CAAC,CAACnC;wBAcOA;oBAbzB,MAAM2C,SAAS0E,SAASrH;oBACxB,OAAO;wBACLoK,KAAKpK,WAAWmH,EAAE;uBACfzE,UAAU1C,YAAYC,OAAO0C;wBAChCmG,UAAUA,SAAS9I;wBACnBqK,cAAc7K,mBACZoB,eAAeZ,aACfwF;wBAEF,8DAA8D;wBAC9D7C,QAAQ7D,wBAAwB6D;wBAChC2H,eAAe3H,OAAOE,QAAQ,CAAC0H,IAAI,CAAC,QAAQ;wBAC5CC,WAAW7H,OAAOA,MAAM,GACpBnD,oBAAmBQ,yBAAAA,WAAWoH,UAAU,qBAArBpH,uBAAuByK,aAAa,EAAEjF,YACzD;wBACJkF,eAAe/H,OAAOgI,aAAa,GAAGtJ,OAAOsB,OAAOgI,aAAa,IAAI;wBACrElK,YAAYD,kBAAkBR,eAAe;;gBAEjD;gBACA0F,QACEA,SACI,CAACkF,KAAKzG,MAA4CuB,OAAOvB,IAAInE,UAAU,IACvE8E;gBAEN;;;;;WAKC,GACD+F,YAAW;gBACXC,aAAa7E,WAAW6E,WAAW;gBACnCC,qBAAqB9E,WAAW8E,mBAAmB;gBACnDC,WAAW;gBACXC,oBAAoB;gBACpBC,aAAY;gBACZC,UAAU;;0BAGd,KAACtN;gBACC4K,MAAM9B,YAAY8B,IAAI;gBACtB2C,UAAUzE,YAAYyE,QAAQ;gBAC9BC,UAAU9B,SAASnJ,MAAM;gBACzBkL,SAAS3E,YAAY2E,OAAO;gBAC5BC,cAAc5E,YAAY6E,OAAO;gBACjCC,kBAAkB9E,YAAY+E,WAAW;;YAE1C1F,QAAQ2F,OAAO;;;AAGtB;AACApG,yBAAyBqG,WAAW,GAAG;AAEvC,eAAerG,yBAAwB"}
|
|
@@ -64,14 +64,24 @@ import SequencesSection from "./sequences-section.js";
|
|
|
64
64
|
* `orgMount`; each section takes the organization from the mount, and the
|
|
65
65
|
* Sequences section its own pages from the path below it.
|
|
66
66
|
*/ export function OutreachConsolePage(props) {
|
|
67
|
+
var _props_segments;
|
|
67
68
|
const { section, sections, basePath } = props;
|
|
68
69
|
/*
|
|
69
70
|
* Nothing until the URL names a section. The shell redirects a bare
|
|
70
71
|
* `/outreach` to the landing section and holds a spinner while it does, so
|
|
71
72
|
* this state is transient.
|
|
72
73
|
*/ if (!section || !(sections == null ? void 0 : sections.length) || !basePath) return null;
|
|
74
|
+
/*
|
|
75
|
+
* A page below the sequence list — one sequence, its enrollments, one
|
|
76
|
+
* person's history, a new sequence — takes the whole row, with the rail
|
|
77
|
+
* as a strip above it (AGL-3332). Beside the rail, the enrollments table
|
|
78
|
+
* was squeezed into three quarters of the page while the column under
|
|
79
|
+
* the three section tabs stood empty, and its cells were cut off. The
|
|
80
|
+
* section lists keep the rail beside them.
|
|
81
|
+
*/ const wide = section === 'sequences' && ((_props_segments = props.segments) != null ? _props_segments : []).length > 1;
|
|
73
82
|
return /*#__PURE__*/ _jsx(HubSections, {
|
|
74
83
|
sections: sections,
|
|
84
|
+
wide: wide,
|
|
75
85
|
children: sectionBody(section, props)
|
|
76
86
|
});
|
|
77
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/outreach-console-page.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 type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport type { ReactNode } from 'react'\nimport ComplianceSection from './compliance-section'\nimport MailboxesSection from './mailboxes-section'\nimport type { OutreachConsoleSectionId } from './outreach-console-sections'\nimport SequencesSection from './sequences-section'\n\n/**\n * The body of the section being read, and only that one — a function rather\n * than a map of nodes so an unopened section is never constructed, which is\n * what keeps its listens closed once sections read data.\n */\nfunction sectionBody(section: OutreachConsoleSectionId, props: ConsolePluginPageProps): ReactNode {\n switch (section) {\n case 'sequences':\n return (\n <SequencesSection\n orgId={props.orgMount?.orgId ?? null}\n orgMount={props.orgMount}\n org={(props.org as Record<string, unknown> | undefined) ?? null}\n sectionPath={`${props.basePath}/sequences`}\n subpath={(props.segments ?? []).slice(1)}\n mailboxesPath={`${props.basePath}/mailboxes`}\n compliancePath={`${props.basePath}/compliance`}\n />\n )\n case 'mailboxes':\n return <MailboxesSection orgId={props.orgMount?.orgId ?? null} />\n case 'compliance':\n return <ComplianceSection orgId={props.orgMount?.orgId ?? null} />\n default:\n return null\n }\n}\n\n/**\n * The Sequences hub (AGL-2974), mounted by the console's generic\n * ORGANIZATION-level route at `/[orgSlug]/outreach/<section>`.\n *\n * The shell has already decided everything about access before this renders:\n * the `release_outreach` gate through the nav item's tab id, the\n * `features.outreach` entitlement and the `outreach.use` permission declared\n * on the extension, and org-wide reach. It hands over `hostId: null` and an\n * `orgMount`; each section takes the organization from the mount, and the\n * Sequences section its own pages from the path below it.\n */\nexport function OutreachConsolePage(props: ConsolePluginPageProps) {\n const { section, sections, basePath } = props\n\n /*\n * Nothing until the URL names a section. The shell redirects a bare\n * `/outreach` to the landing section and holds a spinner while it does, so\n * this state is transient.\n */\n if (!section || !sections?.length || !basePath) return null\n\n return (\n <HubSections sections={sections}>\n {sectionBody(section as OutreachConsoleSectionId, props)}\n </HubSections>\n )\n}\nOutreachConsolePage.displayName = 'OutreachConsolePage'\n\nexport default OutreachConsolePage\n"],"names":["HubSections","ComplianceSection","MailboxesSection","SequencesSection","sectionBody","section","props","orgId","orgMount","org","sectionPath","basePath","subpath","segments","slice","mailboxesPath","compliancePath","OutreachConsolePage","sections","length","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,SAASA,WAAW,QAAQ,wBAAuB;AAEnD,OAAOC,uBAAuB,0BAAsB;AACpD,OAAOC,sBAAsB,yBAAqB;AAElD,OAAOC,sBAAsB,yBAAqB;AAElD;;;;CAIC,GACD,SAASC,YAAYC,OAAiC,EAAEC,KAA6B;IACnF,OAAQD;QACN,KAAK;sBAKOC,YAEIA;gBAJHA;YAFX,qBACE,KAACH;gBACCI,KAAK,WAAED,kBAAAA,MAAME,QAAQ,qBAAdF,gBAAgBC,KAAK,mBAAI;gBAChCC,UAAUF,MAAME,QAAQ;gBACxBC,GAAG,GAAGH,aAAAA,MAAMG,GAAG,YAATH,aAAqD;gBAC3DI,aAAa,GAAGJ,MAAMK,QAAQ,CAAC,UAAU,CAAC;gBAC1CC,SAAS,EAACN,kBAAAA,MAAMO,QAAQ,YAAdP,kBAAkB,EAAE,EAAEQ,KAAK,CAAC;gBACtCC,eAAe,GAAGT,MAAMK,QAAQ,CAAC,UAAU,CAAC;gBAC5CK,gBAAgB,GAAGV,MAAMK,QAAQ,CAAC,WAAW,CAAC;;QAGpD,KAAK;;gBAC6BL;YAAhC,qBAAO,KAACJ;gBAAiBK,KAAK,YAAED,mBAAAA,MAAME,QAAQ,qBAAdF,iBAAgBC,KAAK,oBAAI;;QAC3D,KAAK;;gBAC8BD;YAAjC,qBAAO,KAACL;gBAAkBM,KAAK,YAAED,mBAAAA,MAAME,QAAQ,qBAAdF,iBAAgBC,KAAK,oBAAI;;QAC5D;YACE,OAAO;IACX;AACF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASU,oBAAoBX,KAA6B;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/outreach-console-page.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 type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport type { ReactNode } from 'react'\nimport ComplianceSection from './compliance-section'\nimport MailboxesSection from './mailboxes-section'\nimport type { OutreachConsoleSectionId } from './outreach-console-sections'\nimport SequencesSection from './sequences-section'\n\n/**\n * The body of the section being read, and only that one — a function rather\n * than a map of nodes so an unopened section is never constructed, which is\n * what keeps its listens closed once sections read data.\n */\nfunction sectionBody(section: OutreachConsoleSectionId, props: ConsolePluginPageProps): ReactNode {\n switch (section) {\n case 'sequences':\n return (\n <SequencesSection\n orgId={props.orgMount?.orgId ?? null}\n orgMount={props.orgMount}\n org={(props.org as Record<string, unknown> | undefined) ?? null}\n sectionPath={`${props.basePath}/sequences`}\n subpath={(props.segments ?? []).slice(1)}\n mailboxesPath={`${props.basePath}/mailboxes`}\n compliancePath={`${props.basePath}/compliance`}\n />\n )\n case 'mailboxes':\n return <MailboxesSection orgId={props.orgMount?.orgId ?? null} />\n case 'compliance':\n return <ComplianceSection orgId={props.orgMount?.orgId ?? null} />\n default:\n return null\n }\n}\n\n/**\n * The Sequences hub (AGL-2974), mounted by the console's generic\n * ORGANIZATION-level route at `/[orgSlug]/outreach/<section>`.\n *\n * The shell has already decided everything about access before this renders:\n * the `release_outreach` gate through the nav item's tab id, the\n * `features.outreach` entitlement and the `outreach.use` permission declared\n * on the extension, and org-wide reach. It hands over `hostId: null` and an\n * `orgMount`; each section takes the organization from the mount, and the\n * Sequences section its own pages from the path below it.\n */\nexport function OutreachConsolePage(props: ConsolePluginPageProps) {\n const { section, sections, basePath } = props\n\n /*\n * Nothing until the URL names a section. The shell redirects a bare\n * `/outreach` to the landing section and holds a spinner while it does, so\n * this state is transient.\n */\n if (!section || !sections?.length || !basePath) return null\n\n /*\n * A page below the sequence list — one sequence, its enrollments, one\n * person's history, a new sequence — takes the whole row, with the rail\n * as a strip above it (AGL-3332). Beside the rail, the enrollments table\n * was squeezed into three quarters of the page while the column under\n * the three section tabs stood empty, and its cells were cut off. The\n * section lists keep the rail beside them.\n */\n const wide = section === 'sequences' && (props.segments ?? []).length > 1\n\n return (\n <HubSections sections={sections} wide={wide}>\n {sectionBody(section as OutreachConsoleSectionId, props)}\n </HubSections>\n )\n}\nOutreachConsolePage.displayName = 'OutreachConsolePage'\n\nexport default OutreachConsolePage\n"],"names":["HubSections","ComplianceSection","MailboxesSection","SequencesSection","sectionBody","section","props","orgId","orgMount","org","sectionPath","basePath","subpath","segments","slice","mailboxesPath","compliancePath","OutreachConsolePage","sections","length","wide","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,SAASA,WAAW,QAAQ,wBAAuB;AAEnD,OAAOC,uBAAuB,0BAAsB;AACpD,OAAOC,sBAAsB,yBAAqB;AAElD,OAAOC,sBAAsB,yBAAqB;AAElD;;;;CAIC,GACD,SAASC,YAAYC,OAAiC,EAAEC,KAA6B;IACnF,OAAQD;QACN,KAAK;sBAKOC,YAEIA;gBAJHA;YAFX,qBACE,KAACH;gBACCI,KAAK,WAAED,kBAAAA,MAAME,QAAQ,qBAAdF,gBAAgBC,KAAK,mBAAI;gBAChCC,UAAUF,MAAME,QAAQ;gBACxBC,GAAG,GAAGH,aAAAA,MAAMG,GAAG,YAATH,aAAqD;gBAC3DI,aAAa,GAAGJ,MAAMK,QAAQ,CAAC,UAAU,CAAC;gBAC1CC,SAAS,EAACN,kBAAAA,MAAMO,QAAQ,YAAdP,kBAAkB,EAAE,EAAEQ,KAAK,CAAC;gBACtCC,eAAe,GAAGT,MAAMK,QAAQ,CAAC,UAAU,CAAC;gBAC5CK,gBAAgB,GAAGV,MAAMK,QAAQ,CAAC,WAAW,CAAC;;QAGpD,KAAK;;gBAC6BL;YAAhC,qBAAO,KAACJ;gBAAiBK,KAAK,YAAED,mBAAAA,MAAME,QAAQ,qBAAdF,iBAAgBC,KAAK,oBAAI;;QAC3D,KAAK;;gBAC8BD;YAAjC,qBAAO,KAACL;gBAAkBM,KAAK,YAAED,mBAAAA,MAAME,QAAQ,qBAAdF,iBAAgBC,KAAK,oBAAI;;QAC5D;YACE,OAAO;IACX;AACF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASU,oBAAoBX,KAA6B;QAkBtBA;IAjBzC,MAAM,EAAED,OAAO,EAAEa,QAAQ,EAAEP,QAAQ,EAAE,GAAGL;IAExC;;;;GAIC,GACD,IAAI,CAACD,WAAW,EAACa,4BAAAA,SAAUC,MAAM,KAAI,CAACR,UAAU,OAAO;IAEvD;;;;;;;GAOC,GACD,MAAMS,OAAOf,YAAY,eAAe,EAACC,kBAAAA,MAAMO,QAAQ,YAAdP,kBAAkB,EAAE,EAAEa,MAAM,GAAG;IAExE,qBACE,KAACnB;QAAYkB,UAAUA;QAAUE,MAAMA;kBACpChB,YAAYC,SAAqCC;;AAGxD;AACAW,oBAAoBI,WAAW,GAAG;AAElC,eAAeJ,oBAAmB"}
|
|
@@ -42,6 +42,8 @@ export declare function outreachActivationBlockers(input: {
|
|
|
42
42
|
compliance: string;
|
|
43
43
|
};
|
|
44
44
|
}): OutreachActivationBlocker[];
|
|
45
|
+
/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */
|
|
46
|
+
export declare function outreachEnrollmentPath(sectionPath: string, sequenceId: string, enrollmentId: string): string;
|
|
45
47
|
/**
|
|
46
48
|
* One sequence (AGL-2980): its steps in the editor, the people enrolled in
|
|
47
49
|
* it, and what can be done with it — activate, pause, archive, delete a
|
|
@@ -22,10 +22,13 @@ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
|
22
22
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
23
23
|
import { Alert, Button, Card, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, Tab, Tabs, Typography } from "@mui/material";
|
|
24
24
|
import EmptyStateComponent from "@aglyn/shared-ui-jsx/components/empty-state.component";
|
|
25
|
-
import {
|
|
25
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
26
|
+
import { useMemo, useState } from "react";
|
|
26
27
|
import { validateOutreachOrgSettings } from "../engine/sequence-validation.js";
|
|
28
|
+
import { outreachSequenceLinkReport } from "../model/sequence-report.js";
|
|
27
29
|
import { outreachMailboxActivationIssue, outreachSequenceMailboxState } from "../model/sequence-draft.js";
|
|
28
30
|
import { OutreachEnrollDialog } from "./enroll-dialog.js";
|
|
31
|
+
import { outreachClauseLivesInUrl, outreachEnrollmentFilterSearch, outreachEnrollmentsFilterHref, outreachEnrollmentUrlClauses } from "./enrollment-filter-params.js";
|
|
29
32
|
import { OutreachEnrollmentsTable } from "./enrollments-table.js";
|
|
30
33
|
import { OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
|
|
31
34
|
import { OutreachSequenceEditor } from "./sequence-editor.js";
|
|
@@ -72,6 +75,9 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
72
75
|
paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',
|
|
73
76
|
reconnect_required: 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.'
|
|
74
77
|
};
|
|
78
|
+
/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */ export function outreachEnrollmentPath(sectionPath, sequenceId, enrollmentId) {
|
|
79
|
+
return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`;
|
|
80
|
+
}
|
|
75
81
|
/** The words an action's button and its snackbar use. */ const ACTIONS = {
|
|
76
82
|
activate: {
|
|
77
83
|
label: 'Activate',
|
|
@@ -91,7 +97,8 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
91
97
|
* it, and what can be done with it — activate, pause, archive, delete a
|
|
92
98
|
* draft, and enroll people in an active one.
|
|
93
99
|
*/ export function OutreachSequenceDetail(props) {
|
|
94
|
-
var
|
|
100
|
+
var _ref, _props_mailboxes_mailboxes_find, _ref1, _ref2, _props_org, _ref3;
|
|
101
|
+
var _sequence_stats;
|
|
95
102
|
const { orgId, sequenceId, sectionPath } = props;
|
|
96
103
|
const api = useOutreachApi(orgId);
|
|
97
104
|
const navigate = useOutreachNavigate();
|
|
@@ -105,8 +112,48 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
105
112
|
const [refusal, setRefusal] = useState(null);
|
|
106
113
|
const [confirming, setConfirming] = useState(null);
|
|
107
114
|
const [enrolling, setEnrolling] = useState(false);
|
|
115
|
+
/*
|
|
116
|
+
* The enrollments table's filter (AGL-3332). "Who clicked" and "who
|
|
117
|
+
* followed this destination" live in the URL, where the Results card
|
|
118
|
+
* sets them and where back from a person finds them again; every other
|
|
119
|
+
* clause the panel sets is the page's own.
|
|
120
|
+
*/ const router = useRouter();
|
|
121
|
+
const pathname = usePathname();
|
|
122
|
+
const searchParams = useSearchParams();
|
|
123
|
+
const search = (_ref = searchParams == null ? void 0 : searchParams.toString()) != null ? _ref : '';
|
|
124
|
+
const urlClauses = useMemo(()=>outreachEnrollmentUrlClauses(new URLSearchParams(search)), [
|
|
125
|
+
search
|
|
126
|
+
]);
|
|
127
|
+
const [ownClauses, setOwnClauses] = useState([]);
|
|
128
|
+
const clauses = useMemo(()=>[
|
|
129
|
+
...ownClauses,
|
|
130
|
+
...urlClauses
|
|
131
|
+
], [
|
|
132
|
+
ownClauses,
|
|
133
|
+
urlClauses
|
|
134
|
+
]);
|
|
135
|
+
const setClauses = (next)=>{
|
|
136
|
+
setOwnClauses(next.filter((clause)=>!outreachClauseLivesInUrl(clause)));
|
|
137
|
+
const nextSearch = outreachEnrollmentFilterSearch(next, search);
|
|
138
|
+
if (nextSearch !== search) {
|
|
139
|
+
router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, {
|
|
140
|
+
scroll: false
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const linkOptions = useMemo(()=>{
|
|
145
|
+
var _links_data;
|
|
146
|
+
return outreachSequenceLinkReport(links.status === 'ready' ? (_links_data = links.data) != null ? _links_data : undefined : undefined).rows.map((row)=>({
|
|
147
|
+
value: row.url,
|
|
148
|
+
label: row.url
|
|
149
|
+
}));
|
|
150
|
+
}, [
|
|
151
|
+
links
|
|
152
|
+
]);
|
|
108
153
|
const sequence = loaded.data;
|
|
109
154
|
const detailPath = `${sectionPath}/${sequenceId}`;
|
|
155
|
+
const enrollmentsPath = `${detailPath}/enrollments`;
|
|
156
|
+
const enrollmentHref = (enrollment)=>outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id);
|
|
110
157
|
const back = /*#__PURE__*/ _jsx(OutreachLink, {
|
|
111
158
|
href: sectionPath,
|
|
112
159
|
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
@@ -358,11 +405,19 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
358
405
|
sequence.status === 'draft' ? null : /*#__PURE__*/ _jsx(OutreachSequenceReportCard, {
|
|
359
406
|
sequence: sequence,
|
|
360
407
|
links: links,
|
|
361
|
-
timeZone: (
|
|
408
|
+
timeZone: (_ref1 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref1 : null,
|
|
409
|
+
// Each leads to the people behind it, on the Enrollments tab's own
|
|
410
|
+
// filter (AGL-3332).
|
|
411
|
+
onShowClickers: ()=>navigate(outreachEnrollmentsFilterHref(enrollmentsPath, {
|
|
412
|
+
clicked: true
|
|
413
|
+
})),
|
|
414
|
+
onShowLink: (url)=>navigate(outreachEnrollmentsFilterHref(enrollmentsPath, {
|
|
415
|
+
link: url
|
|
416
|
+
}))
|
|
362
417
|
}),
|
|
363
418
|
/*#__PURE__*/ _jsxs(Tabs, {
|
|
364
419
|
value: props.tab,
|
|
365
|
-
onChange: (_event, next)=>navigate(next === 'steps' ? detailPath :
|
|
420
|
+
onChange: (_event, next)=>navigate(next === 'steps' ? detailPath : enrollmentsPath),
|
|
366
421
|
"aria-label": "Sequence",
|
|
367
422
|
children: [
|
|
368
423
|
/*#__PURE__*/ _jsx(Tab, {
|
|
@@ -388,10 +443,17 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
388
443
|
orgId: orgId,
|
|
389
444
|
sequenceId: sequence.id,
|
|
390
445
|
steps: sequence.steps,
|
|
391
|
-
|
|
392
|
-
|
|
446
|
+
// A sequence that counted clicks once has them to show, whether or
|
|
447
|
+
// not it counts them now.
|
|
448
|
+
trackClicks: sequence.settings.trackClicks || ((_sequence_stats = sequence.stats) == null ? void 0 : _sequence_stats.clickTracked) === true,
|
|
449
|
+
timeZone: (_ref2 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref2 : null,
|
|
393
450
|
api: api,
|
|
394
|
-
enrollAction: sequence.status === 'active' ? enrollButton : undefined
|
|
451
|
+
enrollAction: sequence.status === 'active' ? enrollButton : undefined,
|
|
452
|
+
onOpen: (enrollment)=>navigate(enrollmentHref(enrollment)),
|
|
453
|
+
hrefFor: enrollmentHref,
|
|
454
|
+
clauses: clauses,
|
|
455
|
+
onClausesChange: setClauses,
|
|
456
|
+
linkOptions: linkOptions
|
|
395
457
|
}),
|
|
396
458
|
/*#__PURE__*/ _jsx(OutreachEnrollDialog, {
|
|
397
459
|
open: enrolling,
|
|
@@ -399,7 +461,7 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
|
|
|
399
461
|
orgId: orgId,
|
|
400
462
|
sequence: sequence,
|
|
401
463
|
contactGroupId: sequence.hostId ? consentGroupForHost((_props_org = props.org) != null ? _props_org : null, sequence.hostId).groupId : '',
|
|
402
|
-
uid: (
|
|
464
|
+
uid: (_ref3 = user == null ? void 0 : user.uid) != null ? _ref3 : null,
|
|
403
465
|
api: api
|
|
404
466
|
}),
|
|
405
467
|
/*#__PURE__*/ _jsxs(Dialog, {
|
|
@@ -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 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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"}
|
|
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 type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { useMemo, useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport type { OutreachEnrollment } from '../model/outreach.types'\nimport { outreachSequenceLinkReport } from '../model/sequence-report'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport {\n outreachClauseLivesInUrl,\n outreachEnrollmentFilterSearch,\n outreachEnrollmentsFilterHref,\n outreachEnrollmentUrlClauses,\n} from './enrollment-filter-params'\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/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */\nexport function outreachEnrollmentPath(\n sectionPath: string,\n sequenceId: string,\n enrollmentId: string,\n): string {\n return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`\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 /*\n * The enrollments table's filter (AGL-3332). \"Who clicked\" and \"who\n * followed this destination\" live in the URL, where the Results card\n * sets them and where back from a person finds them again; every other\n * clause the panel sets is the page's own.\n */\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const search = searchParams?.toString() ?? ''\n const urlClauses = useMemo(\n () => outreachEnrollmentUrlClauses(new URLSearchParams(search)),\n [search],\n )\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const clauses = useMemo(() => [...ownClauses, ...urlClauses], [ownClauses, urlClauses])\n const setClauses = (next: ListFilterClause[]) => {\n setOwnClauses(next.filter((clause) => !outreachClauseLivesInUrl(clause)))\n const nextSearch = outreachEnrollmentFilterSearch(next, search)\n if (nextSearch !== search) {\n router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })\n }\n }\n const linkOptions = useMemo(\n () =>\n outreachSequenceLinkReport(\n links.status === 'ready' ? (links.data ?? undefined) : undefined,\n ).rows.map((row) => ({ value: row.url, label: row.url })),\n [links],\n )\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const enrollmentsPath = `${detailPath}/enrollments`\n const enrollmentHref = (enrollment: OutreachEnrollment) =>\n outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id)\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 // Each leads to the people behind it, on the Enrollments tab's own\n // filter (AGL-3332).\n onShowClickers={() =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { clicked: true }))\n }\n onShowLink={(url) =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { link: url }))\n }\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : enrollmentsPath)\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 // A sequence that counted clicks once has them to show, whether or\n // not it counts them now.\n trackClicks={sequence.settings.trackClicks || sequence.stats?.clickTracked === true}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n onOpen={(enrollment) => navigate(enrollmentHref(enrollment))}\n hrefFor={enrollmentHref}\n clauses={clauses}\n onClausesChange={setClauses}\n linkOptions={linkOptions}\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","usePathname","useRouter","useSearchParams","useMemo","useState","validateOutreachOrgSettings","outreachSequenceLinkReport","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","outreachClauseLivesInUrl","outreachEnrollmentFilterSearch","outreachEnrollmentsFilterHref","outreachEnrollmentUrlClauses","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","outreachEnrollmentPath","sectionPath","sequenceId","enrollmentId","encodeURIComponent","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","router","pathname","searchParams","search","toString","urlClauses","URLSearchParams","ownClauses","setOwnClauses","clauses","setClauses","next","filter","clause","nextSearch","replace","scroll","linkOptions","undefined","rows","map","row","value","url","detailPath","enrollmentsPath","enrollmentHref","enrollment","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","li","data-testid","blocker","timeZone","timezone","onShowClickers","clicked","onShowLink","link","tab","onChange","_event","aria-label","orgMount","org","onSaved","trackClicks","stats","clickTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
@@ -19,5 +19,22 @@ export interface OutreachSequenceReportCardProps {
|
|
|
19
19
|
links: OutreachSequenceLinksLoad;
|
|
20
20
|
/** The mailbox's zone, which times are read in; `null` for the reader's own. */
|
|
21
21
|
timeZone: string | null;
|
|
22
|
+
/** Lists the people who clicked, on the Enrollments tab (AGL-3332). */
|
|
23
|
+
onShowClickers?: () => void;
|
|
24
|
+
/** Lists the people who followed one destination, on the Enrollments tab (AGL-3332). */
|
|
25
|
+
onShowLink?: (url: string) => void;
|
|
22
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* One figure, with what it counts under it — and where it leads, when it
|
|
29
|
+
* leads somewhere. The sequence's Results and one person's page (AGL-3332)
|
|
30
|
+
* draw their numbers alike.
|
|
31
|
+
*/
|
|
32
|
+
export declare function OutreachFigure(props: {
|
|
33
|
+
label: string;
|
|
34
|
+
value: string;
|
|
35
|
+
hint?: string;
|
|
36
|
+
/** Opens the list behind the figure; named for a screen reader by `action`. */
|
|
37
|
+
onClick?: () => void;
|
|
38
|
+
action?: string;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
23
40
|
export declare function OutreachSequenceReportCard(props: OutreachSequenceReportCardProps): import("react").JSX.Element;
|