@aglyn/plugins-outreach 1.0.0-beta.155 → 1.0.0-beta.157
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 +13 -12
- package/src/lib/components/enrollments-table.d.ts +7 -0
- package/src/lib/components/enrollments-table.js +46 -2
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/sequence-detail.js +14 -4
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.js +22 -0
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-report-card.d.ts +23 -0
- package/src/lib/components/sequence-report-card.js +191 -0
- package/src/lib/components/sequence-report-card.js.map +1 -0
- package/src/lib/components/use-outreach-data.d.ts +11 -0
- package/src/lib/components/use-outreach-data.js +24 -1
- package/src/lib/components/use-outreach-data.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +9 -0
- package/src/lib/constants/api-routes.js +9 -1
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/click-tracking.d.ts +98 -0
- package/src/lib/engine/click-tracking.js +252 -0
- package/src/lib/engine/click-tracking.js.map +1 -0
- package/src/lib/engine/compose.d.ts +24 -0
- package/src/lib/engine/compose.js +15 -2
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/sequence-validation.js +4 -1
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/model/enrollment-engagement.d.ts +31 -0
- package/src/lib/model/enrollment-engagement.js +44 -0
- package/src/lib/model/enrollment-engagement.js.map +1 -0
- package/src/lib/model/outreach.types.d.ts +89 -0
- package/src/lib/model/outreach.types.js +25 -0
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sequence-draft.js +5 -1
- package/src/lib/model/sequence-draft.js.map +1 -1
- package/src/lib/model/sequence-report.d.ts +68 -0
- package/src/lib/model/sequence-report.js +118 -0
- package/src/lib/model/sequence-report.js.map +1 -0
- package/src/lib/model/stored-records.d.ts +11 -1
- package/src/lib/model/stored-records.js +34 -1
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +4 -1
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/runtime/click-events.d.ts +67 -0
- package/src/lib/runtime/click-events.js +232 -0
- package/src/lib/runtime/click-events.js.map +1 -0
- package/src/lib/runtime/click-link.d.ts +94 -0
- package/src/lib/runtime/click-link.js +158 -0
- package/src/lib/runtime/click-link.js.map +1 -0
- package/src/lib/runtime/click-route.d.ts +19 -0
- package/src/lib/runtime/click-route.js +108 -0
- package/src/lib/runtime/click-route.js.map +1 -0
- package/src/lib/runtime/platform-runtime-deps.d.ts +2 -0
- package/src/lib/runtime/platform-runtime-deps.js +9 -0
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/runtime-deps.d.ts +11 -0
- package/src/lib/runtime/runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +65 -5
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/server.js +8 -0
- package/src/lib/server.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-outreach",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.157",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,17 +25,18 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-
|
|
31
|
-
"@aglyn/shared-ui-
|
|
32
|
-
"@aglyn/shared-ui-
|
|
33
|
-
"@aglyn/shared-
|
|
34
|
-
"@aglyn/shared-util-
|
|
35
|
-
"@aglyn/shared-util-
|
|
36
|
-
"@aglyn/
|
|
37
|
-
"@aglyn/tenant-
|
|
38
|
-
"@aglyn/tenant-
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.157",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.157",
|
|
30
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.157",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.157",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.157",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.157",
|
|
34
|
+
"@aglyn/shared-util-http": "1.0.0-beta.157",
|
|
35
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.157",
|
|
36
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.157",
|
|
37
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.157",
|
|
38
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.157",
|
|
39
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.157",
|
|
39
40
|
"@swc/helpers": "0.5.23"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
@@ -5,6 +5,13 @@ import type { OutreachEnrollmentsLoad } from './use-outreach-data';
|
|
|
5
5
|
export interface OutreachEnrollmentsTableProps {
|
|
6
6
|
enrollments: OutreachEnrollmentsLoad;
|
|
7
7
|
steps: readonly OutreachSequenceStep[];
|
|
8
|
+
/**
|
|
9
|
+
* Whether this sequence counts link clicks (AGL-3239). The Clicks column
|
|
10
|
+
* is shown only when it does: a column of dashes for a sequence that
|
|
11
|
+
* measures nothing reads as "nobody clicked", which is a different claim
|
|
12
|
+
* and a false one.
|
|
13
|
+
*/
|
|
14
|
+
trackClicks?: boolean;
|
|
8
15
|
/** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */
|
|
9
16
|
timeZone: string | null;
|
|
10
17
|
api: OutreachApi;
|
|
@@ -22,6 +22,7 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
|
|
|
22
22
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
23
23
|
import { Button, Card, CardContent, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField, Typography, useMediaQuery, useTheme, Chip } from "@mui/material";
|
|
24
24
|
import { useState } from "react";
|
|
25
|
+
import { readOutreachEngagement } from "../model/enrollment-engagement.js";
|
|
25
26
|
import { OUTREACH_TASK_KIND_LABELS } from "../model/outreach.types.js";
|
|
26
27
|
import { formatOutreachTime, OUTREACH_STOP_REASON_LABELS, OutreachEnrollmentStatusChip, OutreachLoading, OutreachLoadProblem } from "./outreach-ui.js";
|
|
27
28
|
/** The step an enrollment is on, as a person reads it. */ export function outreachCurrentStepLabel(enrollment, steps) {
|
|
@@ -70,7 +71,7 @@ const ACTION_LABELS = {
|
|
|
70
71
|
label: 'Mark do-not-contact'
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
|
-
/** The table's columns; the person and the actions are drawn from the row's enrollment. */ function columns(rowActions) {
|
|
74
|
+
/** The table's columns; the person and the actions are drawn from the row's enrollment. */ function columns(rowActions, trackClicks) {
|
|
74
75
|
return [
|
|
75
76
|
{
|
|
76
77
|
field: 'person',
|
|
@@ -141,6 +142,43 @@ const ACTION_LABELS = {
|
|
|
141
142
|
width: 190,
|
|
142
143
|
sortable: false
|
|
143
144
|
},
|
|
145
|
+
...trackClicks ? [
|
|
146
|
+
{
|
|
147
|
+
field: 'clicks',
|
|
148
|
+
headerName: 'Clicks',
|
|
149
|
+
width: 130,
|
|
150
|
+
sortable: false,
|
|
151
|
+
renderCell: ({ row })=>{
|
|
152
|
+
const engagement = readOutreachEngagement(row.enrollment.engagement);
|
|
153
|
+
if (!engagement.clicks) {
|
|
154
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
155
|
+
variant: "body2",
|
|
156
|
+
color: "text.secondary",
|
|
157
|
+
children: "—"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
161
|
+
spacing: 0,
|
|
162
|
+
sx: {
|
|
163
|
+
justifyContent: 'center',
|
|
164
|
+
height: '100%'
|
|
165
|
+
},
|
|
166
|
+
children: [
|
|
167
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
168
|
+
variant: "body2",
|
|
169
|
+
children: engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`
|
|
170
|
+
}),
|
|
171
|
+
engagement.lastClickUrl ? /*#__PURE__*/ _jsx(Typography, {
|
|
172
|
+
variant: "caption",
|
|
173
|
+
color: "text.secondary",
|
|
174
|
+
noWrap: true,
|
|
175
|
+
children: engagement.lastClickUrl
|
|
176
|
+
}) : null
|
|
177
|
+
]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
] : [],
|
|
144
182
|
{
|
|
145
183
|
field: 'stopReason',
|
|
146
184
|
headerName: 'Stop reason',
|
|
@@ -160,6 +198,7 @@ const ACTION_LABELS = {
|
|
|
160
198
|
*/ export function OutreachEnrollmentsTable(props) {
|
|
161
199
|
var _CONFIRM_confirming_action, _CONFIRM_confirming_action1, _CONFIRM_confirming_action2;
|
|
162
200
|
const { enrollments, steps, timeZone, api } = props;
|
|
201
|
+
const trackClicks = props.trackClicks === true;
|
|
163
202
|
const theme = useTheme();
|
|
164
203
|
const narrow = useMediaQuery(theme.breakpoints.down('md'));
|
|
165
204
|
const { enqueueSnackbar } = useSnackbar();
|
|
@@ -310,6 +349,11 @@ const ACTION_LABELS = {
|
|
|
310
349
|
color: "text.secondary",
|
|
311
350
|
children: `Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`
|
|
312
351
|
}),
|
|
352
|
+
trackClicks && readOutreachEngagement(enrollment.engagement).clicks ? /*#__PURE__*/ _jsx(Typography, {
|
|
353
|
+
variant: "body2",
|
|
354
|
+
color: "text.secondary",
|
|
355
|
+
children: `Clicked ${readOutreachEngagement(enrollment.engagement).clicks} ${readOutreachEngagement(enrollment.engagement).clicks === 1 ? 'time' : 'times'}`
|
|
356
|
+
}) : null,
|
|
313
357
|
outreachStopLabel(enrollment) ? /*#__PURE__*/ _jsx(Typography, {
|
|
314
358
|
variant: "body2",
|
|
315
359
|
color: "text.secondary",
|
|
@@ -321,7 +365,7 @@ const ACTION_LABELS = {
|
|
|
321
365
|
}, enrollment.id))
|
|
322
366
|
}) : /*#__PURE__*/ _jsx(ListTable, {
|
|
323
367
|
"aria-label": "Enrollments",
|
|
324
|
-
columns: columns(rowActions),
|
|
368
|
+
columns: columns(rowActions, trackClicks),
|
|
325
369
|
rows: pageRows.map((enrollment)=>({
|
|
326
370
|
$id: enrollment.id,
|
|
327
371
|
enrollment,
|
|
@@ -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 { 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 { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\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 type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /** 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/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\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<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\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/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\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 { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\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 { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const 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 [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\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: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {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 <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\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={columns(rowActions)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <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","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","email","target","size","color","width","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { 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 { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\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 { readOutreachEngagement } from '../model/enrollment-engagement'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /**\n * Whether this sequence counts link clicks (AGL-3239). The Clicks column\n * is shown only when it does: a column of dashes for a sequence that\n * measures nothing reads as \"nobody clicked\", which is a different claim\n * and a false one.\n */\n trackClicks?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\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<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\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/** 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 { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { enrollment: OutreachEnrollment } }) => {\n const engagement = readOutreachEngagement(row.enrollment.engagement)\n if (!engagement.clicks) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\">\n {engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`}\n </Typography>\n {engagement.lastClickUrl ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {engagement.lastClickUrl}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n ]\n : []),\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const trackClicks = props.trackClicks === true\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\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: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {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 <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\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={columns(rowActions, trackClicks)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <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","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","readOutreachEngagement","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","trackClicks","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","email","target","size","color","width","engagement","clicks","lastClickUrl","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
|
|
@@ -29,8 +29,9 @@ import { OutreachEnrollDialog } from "./enroll-dialog.js";
|
|
|
29
29
|
import { OutreachEnrollmentsTable } from "./enrollments-table.js";
|
|
30
30
|
import { OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
|
|
31
31
|
import { OutreachSequenceEditor } from "./sequence-editor.js";
|
|
32
|
+
import { OutreachSequenceReportCard } from "./sequence-report-card.js";
|
|
32
33
|
import { OutreachRouteError, useOutreachApi } from "./use-outreach-api.js";
|
|
33
|
-
import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data.js";
|
|
34
|
+
import { useOutreachEnrollments, useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-data.js";
|
|
34
35
|
/**
|
|
35
36
|
* What stops a sequence being activated, read before anyone clicks: its
|
|
36
37
|
* mailbox not sending, and a footer the organization cannot write yet —
|
|
@@ -90,7 +91,7 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
|
|
|
90
91
|
* it, and what can be done with it — activate, pause, archive, delete a
|
|
91
92
|
* draft, and enroll people in an active one.
|
|
92
93
|
*/ export function OutreachSequenceDetail(props) {
|
|
93
|
-
var _props_mailboxes_mailboxes_find, _ref, _props_org,
|
|
94
|
+
var _props_mailboxes_mailboxes_find, _ref, _ref1, _props_org, _ref2;
|
|
94
95
|
const { orgId, sequenceId, sectionPath } = props;
|
|
95
96
|
const api = useOutreachApi(orgId);
|
|
96
97
|
const navigate = useOutreachNavigate();
|
|
@@ -98,6 +99,9 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
|
|
|
98
99
|
const { data: user } = useUser();
|
|
99
100
|
const loaded = useOutreachSequence(orgId, sequenceId);
|
|
100
101
|
const enrollments = useOutreachEnrollments(props.tab === 'enrollments' ? orgId : null, sequenceId);
|
|
102
|
+
// One document however large the sequence, and read on both tabs, because
|
|
103
|
+
// the report card sits above them (AGL-3239).
|
|
104
|
+
const links = useOutreachSequenceLinks(orgId, sequenceId);
|
|
101
105
|
const [busy, setBusy] = useState(false);
|
|
102
106
|
const [refusal, setRefusal] = useState(null);
|
|
103
107
|
const [confirming, setConfirming] = useState(null);
|
|
@@ -352,6 +356,11 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
|
|
|
352
356
|
})
|
|
353
357
|
]
|
|
354
358
|
}) : null,
|
|
359
|
+
sequence.status === 'draft' ? null : /*#__PURE__*/ _jsx(OutreachSequenceReportCard, {
|
|
360
|
+
sequence: sequence,
|
|
361
|
+
links: links,
|
|
362
|
+
timeZone: (_ref = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref : null
|
|
363
|
+
}),
|
|
355
364
|
/*#__PURE__*/ _jsxs(Tabs, {
|
|
356
365
|
value: props.tab,
|
|
357
366
|
onChange: (_event, next)=>navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`),
|
|
@@ -378,7 +387,8 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
|
|
|
378
387
|
}) : /*#__PURE__*/ _jsx(OutreachEnrollmentsTable, {
|
|
379
388
|
enrollments: enrollments,
|
|
380
389
|
steps: sequence.steps,
|
|
381
|
-
|
|
390
|
+
trackClicks: sequence.settings.trackClicks,
|
|
391
|
+
timeZone: (_ref1 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref1 : null,
|
|
382
392
|
api: api,
|
|
383
393
|
enrollAction: sequence.status === 'active' ? enrollButton : undefined
|
|
384
394
|
}),
|
|
@@ -388,7 +398,7 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
|
|
|
388
398
|
orgId: orgId,
|
|
389
399
|
sequence: sequence,
|
|
390
400
|
contactGroupId: sequence.hostId ? consentGroupForHost((_props_org = props.org) != null ? _props_org : null, sequence.hostId).groupId : '',
|
|
391
|
-
uid: (
|
|
401
|
+
uid: (_ref2 = user == null ? void 0 : user.uid) != null ? _ref2 : null,
|
|
392
402
|
api: api
|
|
393
403
|
}),
|
|
394
404
|
/*#__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 { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n const enrollments = useOutreachEnrollments(\n props.tab === 'enrollments' ? orgId : null,\n sequenceId,\n )\n 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 <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 sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n enrollments={enrollments}\n steps={sequence.steps}\n 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","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","enrollments","tab","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","value","onChange","_event","next","aria-label","orgMount","onSaved","undefined","timeZone","timezone","enrollAction","open","contactGroupId","hostId","org","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n const enrollments = useOutreachEnrollments(\n props.tab === 'enrollments' ? orgId : null,\n sequenceId,\n )\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n enrollments={enrollments}\n steps={sequence.steps}\n trackClicks={sequence.settings.trackClicks}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","useState","validateOutreachOrgSettings","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","enrollments","tab","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","detailPath","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","filter","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","map","li","data-testid","blocker","timeZone","timezone","value","onChange","_event","next","aria-label","orgMount","onSaved","undefined","trackClicks","enrollAction","open","contactGroupId","hostId","org","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
@@ -408,6 +408,28 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
408
408
|
}),
|
|
409
409
|
label: "Include customers"
|
|
410
410
|
}),
|
|
411
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
412
|
+
spacing: 0,
|
|
413
|
+
children: [
|
|
414
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
415
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
416
|
+
checked: draft.settings.trackClicks,
|
|
417
|
+
onChange: (event)=>update({
|
|
418
|
+
settings: _extends({}, draft.settings, {
|
|
419
|
+
trackClicks: event.target.checked
|
|
420
|
+
})
|
|
421
|
+
}),
|
|
422
|
+
disabled: archived
|
|
423
|
+
}),
|
|
424
|
+
label: "Count link clicks"
|
|
425
|
+
}),
|
|
426
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
427
|
+
variant: "caption",
|
|
428
|
+
color: "text.secondary",
|
|
429
|
+
children: "Links in these emails are replaced with links of ours that forward to the same page, so clicks can be counted. The recipient sees the replacement, not your address. Opens aren’t counted either way: these emails are plain text, and counting an open needs a tracking image."
|
|
430
|
+
})
|
|
431
|
+
]
|
|
432
|
+
}),
|
|
411
433
|
/*#__PURE__*/ _jsx(OutreachSendWindowFields, {
|
|
412
434
|
window: draft.settings.window,
|
|
413
435
|
onChange: (window)=>update({
|