@aglyn/plugins-outreach 1.0.0-beta.231 → 1.0.0-beta.233
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/curated-step-editor.js +1 -0
- package/src/lib/components/curated-step-editor.js.map +1 -1
- package/src/lib/components/do-not-contact-domains.js +23 -8
- package/src/lib/components/do-not-contact-domains.js.map +1 -1
- package/src/lib/components/enroll-dialog.js +6 -0
- package/src/lib/components/enroll-dialog.js.map +1 -1
- package/src/lib/components/enrollment-detail.js +3 -0
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/enrollments-table.js +94 -48
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/link-domains-card.js +2 -0
- package/src/lib/components/link-domains-card.js.map +1 -1
- package/src/lib/components/mailboxes-section.js +1 -0
- package/src/lib/components/mailboxes-section.js.map +1 -1
- package/src/lib/components/sequence-detail.js +1 -0
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-report-card.js +1 -0
- package/src/lib/components/sequence-report-card.js.map +1 -1
- package/src/lib/components/sequences-section.js +47 -15
- package/src/lib/components/sequences-section.js.map +1 -1
- package/src/lib/model/do-not-contact-domain-list-query.js +60 -2
- package/src/lib/model/do-not-contact-domain-list-query.js.map +1 -1
- package/src/lib/model/enrollment-list-query.js +55 -2
- package/src/lib/model/enrollment-list-query.js.map +1 -1
- package/src/lib/model/sequence-list-query.js +36 -2
- package/src/lib/model/sequence-list-query.js.map +1 -1
|
@@ -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 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 'The provider 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 // The same for opens (AGL-3488).\n countOpens={sequence.settings.countOpens || sequence.stats?.openTracked === true}\n timeZone={mailbox?.timezone ?? null}\n mailbox={props.mailboxes.status === 'ready' ? mailbox : undefined}\n sequence={sequence}\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","countOpens","openTracked","enrollAction","onOpen","hrefFor","onClausesChange","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 'The provider 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 useFlexGap 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 // The same for opens (AGL-3488).\n countOpens={sequence.settings.countOpens || sequence.stats?.openTracked === true}\n timeZone={mailbox?.timezone ?? null}\n mailbox={props.mailboxes.status === 'ready' ? mailbox : undefined}\n sequence={sequence}\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","useFlexGap","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","countOpens","openTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-report-card.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 { SendRate } from '@aglyn/shared-ui-email-campaigns/model/send-report'\nimport {\n Alert,\n ButtonBase,\n Card,\n CardContent,\n Divider,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { ReactNode } from 'react'\n// Not a raw MUI `Table`: a destination is a long string, and a raw table is\n// cut off by the card around it with nothing able to scroll to what is past\n// the edge (AGL-3045).\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport type { OutreachSequence } from '../model/outreach.types'\nimport {\n outreachSequenceLinkReport,\n outreachSequenceReport,\n} from '../model/sequence-report'\nimport { formatOutreachTime, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachSequenceLinksLoad } from './use-outreach-data'\n\n/**\n * WHAT A SEQUENCE MEASURED (AGL-3239).\n *\n * Four figures and one rate — and, for a sequence that counts opens\n * (AGL-3395), the opens beside them — and the sentences saying what each of\n * them is not. The decisions are all in `model/sequence-report.ts`; this renders\n * them and adds no arithmetic of its own — in particular it never divides,\n * so there is no second place a percentage can be computed differently from\n * the way a campaign's report computes it.\n *\n * The caveats are rendered as prominently as the numbers rather than as\n * small print. \"Opens aren't measured\" is the thing a reader coming from\n * the campaign report most needs told, and a blank where an open rate would\n * be says the opposite of what is true.\n */\n\nexport interface OutreachSequenceReportCardProps {\n sequence: Pick<OutreachSequence, 'stats' | 'settings'>\n links: OutreachSequenceLinksLoad\n /** The mailbox's zone, which times are read in; `null` for the reader's own. */\n timeZone: string | null\n /** Lists the people who clicked, on the Enrollments tab (AGL-3332). */\n onShowClickers?: () => void\n /** Lists the people who followed one destination, on the Enrollments tab (AGL-3332). */\n onShowLink?: (url: string) => void\n}\n\n/**\n * One figure, with what it counts under it — and where it leads, when it\n * leads somewhere. The sequence's Results and one person's page (AGL-3332)\n * draw their numbers alike.\n */\nexport function OutreachFigure(props: {\n label: string\n value: string\n hint?: string\n /** Opens the list behind the figure; named for a screen reader by `action`. */\n onClick?: () => void\n action?: string\n}) {\n const body: ReactNode = (\n <>\n <Typography variant=\"h5\" component=\"p\" color={props.onClick ? 'primary' : undefined}>\n {props.value}\n </Typography>\n <Typography variant=\"body2\">{props.label}</Typography>\n {props.hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.hint}\n </Typography>\n ) : null}\n </>\n )\n if (!props.onClick) {\n return (\n <Stack spacing={0} sx={{ minWidth: 110 }}>\n {body}\n </Stack>\n )\n }\n return (\n <Tooltip title={props.action}>\n <ButtonBase\n onClick={props.onClick}\n aria-label={`${props.value} ${props.label}. ${props.action}`}\n sx={{\n minWidth: 110,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n textAlign: 'left',\n borderRadius: 1,\n px: 1,\n mx: -1,\n '&:hover, &.Mui-focusVisible': { bgcolor: 'action.hover' },\n }}\n >\n {body}\n </ButtonBase>\n </Tooltip>\n )\n}\n\n/** A whole number as the card prints one. */\nconst count = (value: number): string => value.toLocaleString()\n\n/**\n * A rate and the denominator it was taken over, or the em dash that stands\n * for a rate the model refused to take. The denominator is printed beside\n * the percentage, never dropped: \"12% of people emailed\" and \"12% of people\n * who opened\" are different claims and a bare \"12%\" is neither.\n */\nfunction rateText(rate: SendRate | null): { value: string; hint?: string } {\n if (!rate) return { value: '—' }\n return {\n value: `${(rate.value * 100).toFixed(1)}%`,\n hint: `${count(rate.numerator)} of ${count(rate.denominator)} ${rate.denominatorLabel}`,\n }\n}\n\nexport function OutreachSequenceReportCard(props: OutreachSequenceReportCardProps) {\n const report = outreachSequenceReport(\n props.sequence.stats,\n props.sequence.settings.trackClicks,\n props.sequence.settings.countOpens,\n )\n const clickRate = rateText(report.rates.click)\n const openRate = rateText(report.rates.open)\n const links = outreachSequenceLinkReport(\n props.links.status === 'ready' ? (props.links.data ?? undefined) : undefined,\n )\n\n return (\n <Card variant=\"outlined\">\n <CardContent>\n <Stack spacing={2}>\n <Typography variant=\"h6\" component=\"h2\">\n Results\n </Typography>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap', rowGap: 2 }}>\n <OutreachFigure label=\"Emails sent\" value={count(report.sent)} />\n <OutreachFigure\n label=\"People emailed\"\n value={report.people === null ? '—' : count(report.people)}\n {...(report.people === null ? { hint: 'Not counted for this sequence' } : {})}\n />\n {/*\n * Opens (AGL-3395), only for a sequence whose emails have carried\n * the tracking image: for every other one, the caveat below\n * says opens are not measured, and a figure here would say\n * otherwise.\n */}\n {report.openTracked ? (\n <>\n <OutreachFigure\n label=\"Opened\"\n value={count(report.uniqueOpens)}\n hint={\n report.opens === report.uniqueOpens ? undefined : `${count(report.opens)} opens in all`\n }\n />\n <OutreachFigure\n label=\"Open rate\"\n {...(report.opensUnmeasured\n ? { value: 'Unmeasured', hint: 'Only machines fetched the image' }\n : { value: openRate.value, hint: openRate.hint })}\n />\n {report.machineOpens > 0 ? (\n <OutreachFigure\n label=\"Machine opens\"\n value={count(report.machineOpens)}\n hint={\n report.proxyOpens > 0\n ? `Not in the rate · ${count(report.proxyOpens)} by mail proxies`\n : 'Not in the rate'\n }\n />\n ) : null}\n </>\n ) : null}\n <OutreachFigure\n label=\"Clicked\"\n value={count(report.uniqueClicks)}\n hint={\n report.clicks === report.uniqueClicks\n ? undefined\n : `${count(report.clicks)} clicks in all`\n }\n {...(props.onShowClickers && report.uniqueClicks > 0\n ? { onClick: props.onShowClickers, action: 'Show who clicked' }\n : {})}\n />\n <OutreachFigure label=\"Click rate\" value={clickRate.value} hint={clickRate.hint} />\n {report.machineClicks > 0 ? (\n <OutreachFigure\n label=\"Scanner clicks\"\n value={count(report.machineClicks)}\n hint=\"Not in the rate\"\n />\n ) : null}\n </Stack>\n {report.lastOpenAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last open {formatOutreachTime(report.lastOpenAtMs, props.timeZone)}\n </Typography>\n )}\n {report.lastClickAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last click {formatOutreachTime(report.lastClickAtMs, props.timeZone)}\n </Typography>\n )}\n\n {report.caveats.map((entry) => (\n <Alert key={entry.id} severity=\"info\" variant=\"outlined\">\n {entry.message}\n </Alert>\n ))}\n\n {props.links.status === 'error' || props.links.status === 'refused' ? (\n <OutreachLoadProblem status={props.links.status} what=\"the link report\" />\n ) : links.rows.length ? (\n <Stack spacing={1}>\n <Divider />\n <Typography variant=\"subtitle2\" component=\"h3\">\n Links followed\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Destination</TableCell>\n <TableCell align=\"right\">Clicks</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {links.rows.map((row) => (\n <TableRow key={row.url} hover={Boolean(props.onShowLink)}>\n <TableCell sx={{ wordBreak: 'break-all' }}>\n {props.onShowLink ? (\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"body2\"\n underline=\"hover\"\n onClick={() => props.onShowLink?.(row.url)}\n aria-label={`Show who followed ${row.url}`}\n sx={{ textAlign: 'left', wordBreak: 'break-all' }}\n >\n {row.url}\n </Link>\n ) : (\n row.url\n )}\n </TableCell>\n <TableCell align=\"right\">{count(row.clicks)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.onShowLink ? 'Select a destination to see who followed it. ' : ''}\n Two links to the same page are counted as one destination, whatever\n tracking parameters follow the address.\n {links.truncated\n ? ` ${count(links.overflowClicks)} more clicks went to destinations past the ones listed.`\n : ''}\n </Typography>\n </Stack>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Alert","ButtonBase","Card","CardContent","Divider","Link","Stack","TableBody","TableCell","TableHead","TableRow","Tooltip","Typography","ScrollTable","outreachSequenceLinkReport","outreachSequenceReport","formatOutreachTime","OutreachLoadProblem","OutreachFigure","props","body","variant","component","color","onClick","undefined","value","label","hint","spacing","sx","minWidth","title","action","aria-label","display","flexDirection","alignItems","textAlign","borderRadius","px","mx","bgcolor","count","toLocaleString","rateText","rate","toFixed","numerator","denominator","denominatorLabel","OutreachSequenceReportCard","report","sequence","stats","settings","trackClicks","countOpens","clickRate","rates","click","openRate","open","links","status","data","direction","flexWrap","rowGap","sent","people","openTracked","uniqueOpens","opens","opensUnmeasured","machineOpens","proxyOpens","uniqueClicks","clicks","onShowClickers","machineClicks","lastOpenAtMs","timeZone","lastClickAtMs","caveats","map","entry","severity","message","id","what","rows","length","size","align","row","hover","Boolean","onShowLink","wordBreak","type","underline","url","truncated","overflowClicks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-report-card.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 { SendRate } from '@aglyn/shared-ui-email-campaigns/model/send-report'\nimport {\n Alert,\n ButtonBase,\n Card,\n CardContent,\n Divider,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { ReactNode } from 'react'\n// Not a raw MUI `Table`: a destination is a long string, and a raw table is\n// cut off by the card around it with nothing able to scroll to what is past\n// the edge (AGL-3045).\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport type { OutreachSequence } from '../model/outreach.types'\nimport {\n outreachSequenceLinkReport,\n outreachSequenceReport,\n} from '../model/sequence-report'\nimport { formatOutreachTime, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachSequenceLinksLoad } from './use-outreach-data'\n\n/**\n * WHAT A SEQUENCE MEASURED (AGL-3239).\n *\n * Four figures and one rate — and, for a sequence that counts opens\n * (AGL-3395), the opens beside them — and the sentences saying what each of\n * them is not. The decisions are all in `model/sequence-report.ts`; this renders\n * them and adds no arithmetic of its own — in particular it never divides,\n * so there is no second place a percentage can be computed differently from\n * the way a campaign's report computes it.\n *\n * The caveats are rendered as prominently as the numbers rather than as\n * small print. \"Opens aren't measured\" is the thing a reader coming from\n * the campaign report most needs told, and a blank where an open rate would\n * be says the opposite of what is true.\n */\n\nexport interface OutreachSequenceReportCardProps {\n sequence: Pick<OutreachSequence, 'stats' | 'settings'>\n links: OutreachSequenceLinksLoad\n /** The mailbox's zone, which times are read in; `null` for the reader's own. */\n timeZone: string | null\n /** Lists the people who clicked, on the Enrollments tab (AGL-3332). */\n onShowClickers?: () => void\n /** Lists the people who followed one destination, on the Enrollments tab (AGL-3332). */\n onShowLink?: (url: string) => void\n}\n\n/**\n * One figure, with what it counts under it — and where it leads, when it\n * leads somewhere. The sequence's Results and one person's page (AGL-3332)\n * draw their numbers alike.\n */\nexport function OutreachFigure(props: {\n label: string\n value: string\n hint?: string\n /** Opens the list behind the figure; named for a screen reader by `action`. */\n onClick?: () => void\n action?: string\n}) {\n const body: ReactNode = (\n <>\n <Typography variant=\"h5\" component=\"p\" color={props.onClick ? 'primary' : undefined}>\n {props.value}\n </Typography>\n <Typography variant=\"body2\">{props.label}</Typography>\n {props.hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.hint}\n </Typography>\n ) : null}\n </>\n )\n if (!props.onClick) {\n return (\n <Stack spacing={0} sx={{ minWidth: 110 }}>\n {body}\n </Stack>\n )\n }\n return (\n <Tooltip title={props.action}>\n <ButtonBase\n onClick={props.onClick}\n aria-label={`${props.value} ${props.label}. ${props.action}`}\n sx={{\n minWidth: 110,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n textAlign: 'left',\n borderRadius: 1,\n px: 1,\n mx: -1,\n '&:hover, &.Mui-focusVisible': { bgcolor: 'action.hover' },\n }}\n >\n {body}\n </ButtonBase>\n </Tooltip>\n )\n}\n\n/** A whole number as the card prints one. */\nconst count = (value: number): string => value.toLocaleString()\n\n/**\n * A rate and the denominator it was taken over, or the em dash that stands\n * for a rate the model refused to take. The denominator is printed beside\n * the percentage, never dropped: \"12% of people emailed\" and \"12% of people\n * who opened\" are different claims and a bare \"12%\" is neither.\n */\nfunction rateText(rate: SendRate | null): { value: string; hint?: string } {\n if (!rate) return { value: '—' }\n return {\n value: `${(rate.value * 100).toFixed(1)}%`,\n hint: `${count(rate.numerator)} of ${count(rate.denominator)} ${rate.denominatorLabel}`,\n }\n}\n\nexport function OutreachSequenceReportCard(props: OutreachSequenceReportCardProps) {\n const report = outreachSequenceReport(\n props.sequence.stats,\n props.sequence.settings.trackClicks,\n props.sequence.settings.countOpens,\n )\n const clickRate = rateText(report.rates.click)\n const openRate = rateText(report.rates.open)\n const links = outreachSequenceLinkReport(\n props.links.status === 'ready' ? (props.links.data ?? undefined) : undefined,\n )\n\n return (\n <Card variant=\"outlined\">\n <CardContent>\n <Stack spacing={2}>\n <Typography variant=\"h6\" component=\"h2\">\n Results\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap', rowGap: 2 }}>\n <OutreachFigure label=\"Emails sent\" value={count(report.sent)} />\n <OutreachFigure\n label=\"People emailed\"\n value={report.people === null ? '—' : count(report.people)}\n {...(report.people === null ? { hint: 'Not counted for this sequence' } : {})}\n />\n {/*\n * Opens (AGL-3395), only for a sequence whose emails have carried\n * the tracking image: for every other one, the caveat below\n * says opens are not measured, and a figure here would say\n * otherwise.\n */}\n {report.openTracked ? (\n <>\n <OutreachFigure\n label=\"Opened\"\n value={count(report.uniqueOpens)}\n hint={\n report.opens === report.uniqueOpens ? undefined : `${count(report.opens)} opens in all`\n }\n />\n <OutreachFigure\n label=\"Open rate\"\n {...(report.opensUnmeasured\n ? { value: 'Unmeasured', hint: 'Only machines fetched the image' }\n : { value: openRate.value, hint: openRate.hint })}\n />\n {report.machineOpens > 0 ? (\n <OutreachFigure\n label=\"Machine opens\"\n value={count(report.machineOpens)}\n hint={\n report.proxyOpens > 0\n ? `Not in the rate · ${count(report.proxyOpens)} by mail proxies`\n : 'Not in the rate'\n }\n />\n ) : null}\n </>\n ) : null}\n <OutreachFigure\n label=\"Clicked\"\n value={count(report.uniqueClicks)}\n hint={\n report.clicks === report.uniqueClicks\n ? undefined\n : `${count(report.clicks)} clicks in all`\n }\n {...(props.onShowClickers && report.uniqueClicks > 0\n ? { onClick: props.onShowClickers, action: 'Show who clicked' }\n : {})}\n />\n <OutreachFigure label=\"Click rate\" value={clickRate.value} hint={clickRate.hint} />\n {report.machineClicks > 0 ? (\n <OutreachFigure\n label=\"Scanner clicks\"\n value={count(report.machineClicks)}\n hint=\"Not in the rate\"\n />\n ) : null}\n </Stack>\n {report.lastOpenAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last open {formatOutreachTime(report.lastOpenAtMs, props.timeZone)}\n </Typography>\n )}\n {report.lastClickAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last click {formatOutreachTime(report.lastClickAtMs, props.timeZone)}\n </Typography>\n )}\n\n {report.caveats.map((entry) => (\n <Alert key={entry.id} severity=\"info\" variant=\"outlined\">\n {entry.message}\n </Alert>\n ))}\n\n {props.links.status === 'error' || props.links.status === 'refused' ? (\n <OutreachLoadProblem status={props.links.status} what=\"the link report\" />\n ) : links.rows.length ? (\n <Stack spacing={1}>\n <Divider />\n <Typography variant=\"subtitle2\" component=\"h3\">\n Links followed\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Destination</TableCell>\n <TableCell align=\"right\">Clicks</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {links.rows.map((row) => (\n <TableRow key={row.url} hover={Boolean(props.onShowLink)}>\n <TableCell sx={{ wordBreak: 'break-all' }}>\n {props.onShowLink ? (\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"body2\"\n underline=\"hover\"\n onClick={() => props.onShowLink?.(row.url)}\n aria-label={`Show who followed ${row.url}`}\n sx={{ textAlign: 'left', wordBreak: 'break-all' }}\n >\n {row.url}\n </Link>\n ) : (\n row.url\n )}\n </TableCell>\n <TableCell align=\"right\">{count(row.clicks)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.onShowLink ? 'Select a destination to see who followed it. ' : ''}\n Two links to the same page are counted as one destination, whatever\n tracking parameters follow the address.\n {links.truncated\n ? ` ${count(links.overflowClicks)} more clicks went to destinations past the ones listed.`\n : ''}\n </Typography>\n </Stack>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Alert","ButtonBase","Card","CardContent","Divider","Link","Stack","TableBody","TableCell","TableHead","TableRow","Tooltip","Typography","ScrollTable","outreachSequenceLinkReport","outreachSequenceReport","formatOutreachTime","OutreachLoadProblem","OutreachFigure","props","body","variant","component","color","onClick","undefined","value","label","hint","spacing","sx","minWidth","title","action","aria-label","display","flexDirection","alignItems","textAlign","borderRadius","px","mx","bgcolor","count","toLocaleString","rateText","rate","toFixed","numerator","denominator","denominatorLabel","OutreachSequenceReportCard","report","sequence","stats","settings","trackClicks","countOpens","clickRate","rates","click","openRate","open","links","status","data","useFlexGap","direction","flexWrap","rowGap","sent","people","openTracked","uniqueOpens","opens","opensUnmeasured","machineOpens","proxyOpens","uniqueClicks","clicks","onShowClickers","machineClicks","lastOpenAtMs","timeZone","lastClickAtMs","caveats","map","entry","severity","message","id","what","rows","length","size","align","row","hover","Boolean","onShowLink","wordBreak","type","underline","url","truncated","overflowClicks"],"mappings":"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"}
|
|
@@ -27,8 +27,9 @@ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/compon
|
|
|
27
27
|
import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
|
|
28
28
|
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
29
29
|
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
30
|
+
import { useListColumnSort } from "@aglyn/shared-ui-jsx/hooks/use-list-column-sort";
|
|
30
31
|
import { Button, Card, CardContent, Chip, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
|
|
31
|
-
import { useMemo } from "react";
|
|
32
|
+
import { useMemo, useState } from "react";
|
|
32
33
|
import { OUTREACH_SEQUENCE_STATUSES } from "../model/outreach.types.js";
|
|
33
34
|
import { OUTREACH_SEQUENCE_LIST_QUERY } from "../model/sequence-list-query.js";
|
|
34
35
|
import { OUTREACH_SEQUENCE_STATUS_LABELS, OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
|
|
@@ -133,6 +134,20 @@ const COUNT_COLUMNS = [
|
|
|
133
134
|
'Opted out'
|
|
134
135
|
]
|
|
135
136
|
];
|
|
137
|
+
/*
|
|
138
|
+
* Name and Status order the query (`OUTREACH_SEQUENCE_LIST_QUERY.sorts`);
|
|
139
|
+
* the mailbox's label is looked up and the counts are counted for the page,
|
|
140
|
+
* so those sort the page on screen (AGL-3680).
|
|
141
|
+
*/ const countValue = (raw)=>raw && raw !== '—' && Number.isFinite(Number(raw)) ? Number(raw) : null;
|
|
142
|
+
const SEQUENCE_PAGE_SORTS = _extends({
|
|
143
|
+
mailbox: (row)=>row.mailbox
|
|
144
|
+
}, Object.fromEntries(COUNT_COLUMNS.map(([key])=>[
|
|
145
|
+
key,
|
|
146
|
+
(row)=>countValue(row[key])
|
|
147
|
+
])));
|
|
148
|
+
const SEQUENCE_PAGE_SORT_HEADERS = _extends({
|
|
149
|
+
mailbox: 'Mailbox'
|
|
150
|
+
}, Object.fromEntries(COUNT_COLUMNS));
|
|
136
151
|
/*
|
|
137
152
|
* What the sequences grid's Filters panel offers (AGL-3317), each field on
|
|
138
153
|
* the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).
|
|
@@ -176,9 +191,11 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
|
176
191
|
'mailbox'
|
|
177
192
|
]
|
|
178
193
|
});
|
|
194
|
+
const [askedSort, setAskedSort] = useState(null);
|
|
179
195
|
const sequences = useOutreachSequenceList(orgId, {
|
|
180
196
|
clauses: gridFilter.clauses,
|
|
181
|
-
search: gridFilter.searchWords
|
|
197
|
+
search: gridFilter.searchWords,
|
|
198
|
+
sort: askedSort
|
|
182
199
|
});
|
|
183
200
|
const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word)=>word.trim());
|
|
184
201
|
const filterOptions = useMemo(()=>({
|
|
@@ -220,6 +237,24 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
|
220
237
|
children: "New sequence"
|
|
221
238
|
});
|
|
222
239
|
const count = (sequence, key)=>counts[sequence.id] ? String(counts[sequence.id][key]) : '—';
|
|
240
|
+
const tableRows = pageRows.map((sequence)=>_extends({
|
|
241
|
+
$id: sequence.id,
|
|
242
|
+
name: sequence.name || 'Untitled',
|
|
243
|
+
mailbox: mailboxLabel(sequence),
|
|
244
|
+
status: sequence.status
|
|
245
|
+
}, Object.fromEntries(COUNT_COLUMNS.map(([key])=>[
|
|
246
|
+
key,
|
|
247
|
+
count(sequence, key)
|
|
248
|
+
]))));
|
|
249
|
+
const columnSort = useListColumnSort({
|
|
250
|
+
sorts: OUTREACH_SEQUENCE_LIST_QUERY.sorts,
|
|
251
|
+
sort: askedSort,
|
|
252
|
+
onSortChange: setAskedSort,
|
|
253
|
+
orderBy: sequences.plan.orderBy,
|
|
254
|
+
rows: tableRows,
|
|
255
|
+
pageSorts: SEQUENCE_PAGE_SORTS,
|
|
256
|
+
headers: SEQUENCE_PAGE_SORT_HEADERS
|
|
257
|
+
});
|
|
223
258
|
const chips = /*#__PURE__*/ _jsx(ListFilterChips, {
|
|
224
259
|
fields: SEQUENCE_FILTER_FIELDS,
|
|
225
260
|
headers: SEQUENCE_FILTER_HEADERS,
|
|
@@ -233,7 +268,10 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
|
233
268
|
headers: SEQUENCE_FILTER_HEADERS,
|
|
234
269
|
options: filterOptions
|
|
235
270
|
}),
|
|
236
|
-
notices:
|
|
271
|
+
notices: [
|
|
272
|
+
...sequences.plan.notices,
|
|
273
|
+
...columnSort.notices
|
|
274
|
+
]
|
|
237
275
|
});
|
|
238
276
|
/** Nothing stored at all — not a filter that matched nothing. */ const none = sequences.status === 'ready' && !pageRows.length && !filtering && sequences.page === 0;
|
|
239
277
|
let body;
|
|
@@ -305,6 +343,7 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
|
305
343
|
children: mailboxLabel(sequence)
|
|
306
344
|
}),
|
|
307
345
|
/*#__PURE__*/ _jsx(Stack, {
|
|
346
|
+
useFlexGap: true,
|
|
308
347
|
direction: "row",
|
|
309
348
|
spacing: 0.5,
|
|
310
349
|
sx: {
|
|
@@ -372,27 +411,20 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
|
|
|
372
411
|
columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS
|
|
373
412
|
}
|
|
374
413
|
},
|
|
375
|
-
rows:
|
|
376
|
-
$id: sequence.id,
|
|
377
|
-
name: sequence.name || 'Untitled',
|
|
378
|
-
mailbox: mailboxLabel(sequence),
|
|
379
|
-
status: sequence.status
|
|
380
|
-
}, Object.fromEntries(COUNT_COLUMNS.map(([key])=>[
|
|
381
|
-
key,
|
|
382
|
-
count(sequence, key)
|
|
383
|
-
])))),
|
|
414
|
+
rows: columnSort.rows,
|
|
384
415
|
onOpen: (id)=>navigate(`${sectionPath}/${id}`),
|
|
385
416
|
loading: sequences.status === 'loading',
|
|
386
417
|
/*
|
|
387
418
|
* The panel and the search are the grid's; every clause and the
|
|
388
419
|
* search word are on the list's query (AGL-3321), and the grid
|
|
389
|
-
* neither filters nor sorts the page it is handed:
|
|
390
|
-
*
|
|
420
|
+
* neither filters nor sorts the page it is handed: Name and
|
|
421
|
+
* Status order the query, the rest the page (AGL-3680), newest
|
|
422
|
+
* first until a header is clicked.
|
|
391
423
|
*/ filterMode: "server",
|
|
392
424
|
filterModel: gridFilter.filterModel,
|
|
393
425
|
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
394
426
|
quickFilter: true,
|
|
395
|
-
|
|
427
|
+
columnSort: columnSort,
|
|
396
428
|
noRowsLabel: "No sequences match these filters",
|
|
397
429
|
hideFooter: true
|
|
398
430
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OUTREACH_SEQUENCE_LIST_QUERY } from '../model/sequence-list-query'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachEnrollmentDetail } from './enrollment-detail'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequenceList,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /**\n * The path below the section: `[]`, `['new']`, `[id]`, `[id, 'enrollments']`\n * or `[id, 'enrollments', enrollmentId]` — one person's page (AGL-3332).\n */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second, third] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first && second === 'enrollments' && third) {\n return (\n <OutreachEnrollmentDetail\n orgId={orgId}\n orgMount={props.orgMount}\n sectionPath={sectionPath}\n sequenceId={first}\n enrollmentId={third}\n mailboxes={mailboxes}\n />\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317), each field on\n * the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).\n */\nconst SEQUENCE_FILTER_FIELDS = OUTREACH_SEQUENCE_LIST_QUERY.fields\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n created: 'Created',\n}\n/** `created` is no column of its own: a filter-only column, never shown. */\nconst SEQUENCE_HIDDEN_COLUMNS = hiddenFilterVisibility(SEQUENCE_FILTER_FIELDS, [\n 'name',\n 'mailbox',\n 'status',\n])\nconst SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n}))\n\n/** How a mailbox reads in the list: the address it sends as. */\nconst mailboxName = (mailbox: OutreachMailbox) => mailbox.sendAs || mailbox.email\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n *\n * ## Asked of Firestore, a page at a time\n *\n * The Filters panel and the search box are the grid's; the clauses and the\n * words go onto ONE query over the organization's sequences, newest first,\n * and each page is a page of that query's matches (AGL-3321). A clause the\n * query cannot hold beside the others is not applied, and the notice above\n * the list says which and why — never answered over the sequences that\n * happen to be loaded.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const gridFilter = useListGridFilter({ selectFields: ['status', 'mailbox'] })\n const sequences = useOutreachSequenceList(orgId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const filterOptions = useMemo(\n () => ({\n status: SEQUENCE_STATUS_OPTIONS,\n mailbox: props.mailboxes.map((mailbox) => ({\n value: mailbox.id,\n label: mailboxName(mailbox),\n })),\n }),\n [props.mailboxes],\n )\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailboxName(mailbox)\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n const pageRows = sequences.rows\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={sequences.page}\n pageSize={sequences.pageSize}\n rowCount={pageRows.length}\n hasMore={sequences.hasMore}\n onPageChange={sequences.setPage}\n onPageSizeChange={sequences.setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n const notices = (\n <ListQueryNotices\n refused={listQueryRefusals(sequences.plan.refused, {\n fields: SEQUENCE_FILTER_FIELDS,\n headers: SEQUENCE_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={sequences.plan.notices}\n />\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n sequences.status === 'ready' &&\n !pageRows.length &&\n !filtering &&\n sequences.page === 0\n let body\n if (sequences.status === 'loading' && !filtering)\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = (\n <Stack spacing={1}>\n {chips}\n <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n </Stack>\n )\n } else if (none) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n {pageRows.length ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sequences.status === 'loading'\n ? 'Loading sequences…'\n : 'No sequences match these filters'}\n </Typography>\n )}\n {footer}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, filterOptions, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n initialState={{ columns: { columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS } }}\n rows={pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n loading={sequences.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the list's query (AGL-3321), and the grid\n * neither filters nor sorts the page it is handed: the query's\n * one order, newest first, is the list's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action: none || sequences.status === 'loading' ? undefined : newButton,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","ListQueryNotices","listQueryRefusals","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useMemo","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_LIST_QUERY","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachEnrollmentDetail","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequenceList","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","third","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","sequenceId","enrollmentId","org","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","SEQUENCE_FILTER_FIELDS","fields","SEQUENCE_FILTER_HEADERS","name","mailbox","status","created","SEQUENCE_HIDDEN_COLUMNS","SEQUENCE_STATUS_OPTIONS","map","value","mailboxName","sendAs","email","theme","narrow","breakpoints","down","gridFilter","selectFields","sequences","clauses","search","searchWords","filtering","length","some","word","trim","filterOptions","mailboxLabel","find","entry","mailboxId","pageRows","rows","counts","footer","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","count","key","String","chips","headers","onChange","setClauses","options","notices","refused","plan","none","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","row","align","headerAlign","initialState","columnVisibilityModel","$id","Object","fromEntries","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OUTREACH_SEQUENCE_LIST_QUERY } from '../model/sequence-list-query'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachEnrollmentDetail } from './enrollment-detail'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequenceList,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /**\n * The path below the section: `[]`, `['new']`, `[id]`, `[id, 'enrollments']`\n * or `[id, 'enrollments', enrollmentId]` — one person's page (AGL-3332).\n */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second, third] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first && second === 'enrollments' && third) {\n return (\n <OutreachEnrollmentDetail\n orgId={orgId}\n orgMount={props.orgMount}\n sectionPath={sectionPath}\n sequenceId={first}\n enrollmentId={third}\n mailboxes={mailboxes}\n />\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/** A sequences-grid row: the sequence's name, mailbox label, status and counts. */\ntype SequenceTableRow = { $id: string; name: string; mailbox: string; status: string } & Record<\n string,\n string\n>\n\n/*\n * Name and Status order the query (`OUTREACH_SEQUENCE_LIST_QUERY.sorts`);\n * the mailbox's label is looked up and the counts are counted for the page,\n * so those sort the page on screen (AGL-3680).\n */\nconst countValue = (raw: string | undefined): number | null =>\n raw && raw !== '—' && Number.isFinite(Number(raw)) ? Number(raw) : null\nconst SEQUENCE_PAGE_SORTS = {\n mailbox: (row: SequenceTableRow) => row.mailbox,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, (row: SequenceTableRow) => countValue(row[key])]),\n ),\n}\nconst SEQUENCE_PAGE_SORT_HEADERS = {\n mailbox: 'Mailbox',\n ...Object.fromEntries(COUNT_COLUMNS),\n}\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317), each field on\n * the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).\n */\nconst SEQUENCE_FILTER_FIELDS = OUTREACH_SEQUENCE_LIST_QUERY.fields\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n created: 'Created',\n}\n/** `created` is no column of its own: a filter-only column, never shown. */\nconst SEQUENCE_HIDDEN_COLUMNS = hiddenFilterVisibility(SEQUENCE_FILTER_FIELDS, [\n 'name',\n 'mailbox',\n 'status',\n])\nconst SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n}))\n\n/** How a mailbox reads in the list: the address it sends as. */\nconst mailboxName = (mailbox: OutreachMailbox) => mailbox.sendAs || mailbox.email\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n *\n * ## Asked of Firestore, a page at a time\n *\n * The Filters panel and the search box are the grid's; the clauses and the\n * words go onto ONE query over the organization's sequences, newest first,\n * and each page is a page of that query's matches (AGL-3321). A clause the\n * query cannot hold beside the others is not applied, and the notice above\n * the list says which and why — never answered over the sequences that\n * happen to be loaded.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const gridFilter = useListGridFilter({ selectFields: ['status', 'mailbox'] })\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(null)\n const sequences = useOutreachSequenceList(orgId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n sort: askedSort,\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const filterOptions = useMemo(\n () => ({\n status: SEQUENCE_STATUS_OPTIONS,\n mailbox: props.mailboxes.map((mailbox) => ({\n value: mailbox.id,\n label: mailboxName(mailbox),\n })),\n }),\n [props.mailboxes],\n )\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailboxName(mailbox)\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n const pageRows = sequences.rows\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={sequences.page}\n pageSize={sequences.pageSize}\n rowCount={pageRows.length}\n hasMore={sequences.hasMore}\n onPageChange={sequences.setPage}\n onPageSizeChange={sequences.setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n const tableRows: SequenceTableRow[] = pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))\n const columnSort = useListColumnSort<SequenceTableRow>({\n sorts: OUTREACH_SEQUENCE_LIST_QUERY.sorts,\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: sequences.plan.orderBy,\n rows: tableRows,\n pageSorts: SEQUENCE_PAGE_SORTS,\n headers: SEQUENCE_PAGE_SORT_HEADERS,\n })\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n const notices = (\n <ListQueryNotices\n refused={listQueryRefusals(sequences.plan.refused, {\n fields: SEQUENCE_FILTER_FIELDS,\n headers: SEQUENCE_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={[...sequences.plan.notices, ...columnSort.notices]}\n />\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n sequences.status === 'ready' &&\n !pageRows.length &&\n !filtering &&\n sequences.page === 0\n let body\n if (sequences.status === 'loading' && !filtering)\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = (\n <Stack spacing={1}>\n {chips}\n <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n </Stack>\n )\n } else if (none) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n {pageRows.length ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sequences.status === 'loading'\n ? 'Loading sequences…'\n : 'No sequences match these filters'}\n </Typography>\n )}\n {footer}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, filterOptions, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n initialState={{ columns: { columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS } }}\n rows={columnSort.rows}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n loading={sequences.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the list's query (AGL-3321), and the grid\n * neither filters nor sorts the page it is handed: Name and\n * Status order the query, the rest the page (AGL-3680), newest\n * first until a header is clicked.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n columnSort={columnSort}\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action: none || sequences.status === 'loading' ? undefined : newButton,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","ListQueryNotices","listQueryRefusals","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","useListColumnSort","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useMemo","useState","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_LIST_QUERY","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachEnrollmentDetail","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequenceList","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","third","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","sequenceId","enrollmentId","org","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","countValue","raw","Number","isFinite","SEQUENCE_PAGE_SORTS","mailbox","row","Object","fromEntries","map","key","SEQUENCE_PAGE_SORT_HEADERS","SEQUENCE_FILTER_FIELDS","fields","SEQUENCE_FILTER_HEADERS","name","status","created","SEQUENCE_HIDDEN_COLUMNS","SEQUENCE_STATUS_OPTIONS","value","mailboxName","sendAs","email","theme","narrow","breakpoints","down","gridFilter","selectFields","askedSort","setAskedSort","sequences","clauses","search","searchWords","sort","filtering","length","some","word","trim","filterOptions","mailboxLabel","find","entry","mailboxId","pageRows","rows","counts","footer","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","count","String","tableRows","$id","columnSort","sorts","onSortChange","orderBy","plan","pageSorts","headers","chips","onChange","setClauses","options","notices","refused","none","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","useFlexGap","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","align","headerAlign","initialState","columnVisibilityModel","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"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"}
|