@aglyn/plugins-marketing 1.0.0-beta.232 → 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.
Files changed (34) hide show
  1. package/package.json +16 -16
  2. package/src/lib/components/campaign-conversions-card.js +58 -15
  3. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  4. package/src/lib/components/campaign-detail-card.js +50 -5
  5. package/src/lib/components/campaign-detail-card.js.map +1 -1
  6. package/src/lib/components/campaign-members-section.js +36 -8
  7. package/src/lib/components/campaign-members-section.js.map +1 -1
  8. package/src/lib/components/campaigns-card.js +64 -10
  9. package/src/lib/components/campaigns-card.js.map +1 -1
  10. package/src/lib/components/email-recipients-card.js +37 -2
  11. package/src/lib/components/email-recipients-card.js.map +1 -1
  12. package/src/lib/components/emails-list-card.js +59 -6
  13. package/src/lib/components/emails-list-card.js.map +1 -1
  14. package/src/lib/components/host-experiments-card.component.js +32 -6
  15. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  16. package/src/lib/components/staff-org-email-card.component.js +82 -6
  17. package/src/lib/components/staff-org-email-card.component.js.map +1 -1
  18. package/src/lib/model/campaign-conversions.d.ts +2 -0
  19. package/src/lib/model/campaign-conversions.js +32 -0
  20. package/src/lib/model/campaign-conversions.js.map +1 -1
  21. package/src/lib/model/campaign-list-query.d.ts +6 -2
  22. package/src/lib/model/campaign-list-query.js +64 -9
  23. package/src/lib/model/campaign-list-query.js.map +1 -1
  24. package/src/lib/model/campaign-members-query.d.ts +2 -1
  25. package/src/lib/model/campaign-members-query.js +23 -1
  26. package/src/lib/model/campaign-members-query.js.map +1 -1
  27. package/src/lib/model/experiment-list-query.js +30 -2
  28. package/src/lib/model/experiment-list-query.js.map +1 -1
  29. package/src/lib/model/native-vocabulary.d.ts +26 -0
  30. package/src/lib/model/native-vocabulary.js +31 -0
  31. package/src/lib/model/native-vocabulary.js.map +1 -0
  32. package/src/lib/server/campaign-manage.d.ts +1 -1
  33. package/src/lib/server/campaign-manage.js +1 -0
  34. package/src/lib/server/campaign-manage.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-experiments-card.component.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 {\n type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n HOST_EVENT_TYPES,\n isScreenGroup,\n pluginDocsHelp,\n SITE_EVENT_TYPES,\n} from '@aglyn/aglyn'\nimport { nameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport { describeVariantComparison, experimentResultRows, validateExperiment, type ExperimentTarget, type ExperimentVariant, type HostExperiment } from '../model'\nimport { EXPERIMENT_LIST_QUERY } from '../model/experiment-list-query'\nimport {\n mdiChartBar,\n mdiDeleteOutline,\n mdiPause,\n mdiPencilOutline,\n mdiPlay,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n doc,\n getDocs,\n limit,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostActivityLogger,\n writeGuardedBySeed,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n ExperimentResultZone,\n ExperimentVariantsZone,\n marketingExperimentVariantDrafts,\n type MarketingExperimentVariantDraft,\n} from './experiment-zones'\n\nexport interface HostExperimentsCardProps {\n hostId: string\n /** Resolved entitlement source (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ntype ExperimentDraft = HostExperiment & { $id?: string }\n\n/** An experiment's status as a chip color, in every list of experiments. */\nexport const EXPERIMENT_STATUS_COLORS: Record<\n string,\n 'default' | 'success' | 'info' | 'warning'\n> = {\n draft: 'default',\n running: 'success',\n paused: 'warning',\n done: 'info',\n}\n\n/*\n * What the experiments grid's Filters panel offers: the fields of the list's\n * query declaration (`EXPERIMENT_LIST_QUERY`), every one served by Firestore\n * (AGL-3321).\n */\nconst EXPERIMENT_FILTER_FIELDS = EXPERIMENT_LIST_QUERY.fields\nconst EXPERIMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Experiment',\n target: 'Tests',\n status: 'Status',\n}\n/** What each stored `target` reads as; the stored value stays `screen`. */\nconst EXPERIMENT_TARGET_NOUNS: Readonly<Record<string, string>> = {\n screen: 'page',\n section: 'section',\n email: 'email',\n}\nconst EXPERIMENT_FILTER_OPTIONS = {\n target: [\n { value: 'screen', label: 'Page' },\n { value: 'section', label: 'Section' },\n { value: 'email', label: 'Email' },\n ],\n status: Object.keys(EXPERIMENT_STATUS_COLORS).map((status) => ({\n value: status,\n label: status.charAt(0).toUpperCase() + status.slice(1),\n })),\n}\n\n/**\n * Experiments manager (AGL-252): create screen/section/email A/B tests\n * with weighted variants and a conversion goal; start/pause/finish them\n * and read exposures/conversions per variant. Business tier\n * (`ab-testing`).\n */\nexport function HostExperimentsCard(props: HostExperimentsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { org } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId)\n const entitled = checkEntitlement(org, 'abTesting')\n\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 site's experiments, ordered by name, and\n * 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 experiments that\n * happen to be loaded.\n */\n const gridFilter = useListGridFilter({ selectFields: ['target', 'status'] })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const {\n rows: experimentRows,\n hasMore: hasMoreExperiments,\n page: experimentPage,\n setPage: setExperimentPage,\n pageSize: experimentPageSize,\n setPageSize: setExperimentPageSize,\n status: experimentsStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored experiment into `editor`\n * and writes all of it back, so `merge: true` protects nothing — every\n * field is in the payload. `status` is the one that bites: it is\n * otherwise only ever moved by the start/pause/finish controls below, so\n * a cached seed can quietly restart a finished test, or stop a running\n * one, along with reverting its variant weights and goal.\n */\n fromCache: experimentsFromCache,\n plan: experimentPlan,\n } = useListQuery<ExperimentDraft>({\n collection: collection(firestore, 'hosts', hostId, 'experiments'),\n /*\n * Ordered by the server and paged (AGL-2501, AGL-2292): the declaration's\n * one order is `name`, which every writer sets — the editor refuses a save\n * without one (`validateExperiment`), the besigner's section shortcut\n * names its draft, the two send-path readers only ever write the stats\n * beneath an experiment, and `experiments` is not in `IMPORTABLE_FIELDS`.\n * There is no writer that can produce a nameless experiment for `orderBy`\n * to drop.\n *\n * Nothing is dropped after the query either. An experiment is never\n * soft-deleted — Delete below is a `deleteDoc` — so every row the query\n * returns is live, and a page holds as many rows as its size.\n */\n declaration: EXPERIMENT_LIST_QUERY,\n request: { clauses: gridFilter.clauses, search: gridFilter.searchWords },\n deps: [firestore, hostId],\n idField: '$id',\n })\n const experiments: ExperimentDraft[] = experimentRows\n const experimentColumnsFor = (columns: GridColDef[]) =>\n listFilterGridColumns(\n columns,\n EXPERIMENT_FILTER_FIELDS,\n EXPERIMENT_FILTER_OPTIONS,\n EXPERIMENT_FILTER_HEADERS,\n )\n const experimentRefusals = useMemo(\n () =>\n listQueryRefusals(experimentPlan.refused, {\n fields: EXPERIMENT_FILTER_FIELDS,\n headers: EXPERIMENT_FILTER_HEADERS,\n options: EXPERIMENT_FILTER_OPTIONS,\n }),\n [experimentPlan.refused],\n )\n const [editor, setEditor] = useState<ExperimentDraft | null>(null)\n /*\n * The screens and their versions fill the EDITOR's pickers and nothing\n * else on the card, so they are read while the editor is open and not\n * before: a reader who came to see which tests are running pays for the\n * experiments list alone, not for a hundred screens they never pick from.\n */\n const editorOpen = editor !== null\n const { data: screenDocs } = useFirestoreCollection<any>(\n () =>\n editorOpen\n ? query(collection(firestore, 'hosts', hostId, 'screens'), limit(100))\n : null,\n [firestore, hostId, editorOpen],\n { idField: '$id' },\n )\n // A page group (AGL-3463) has no versions to test against each other.\n const screenOptions = [...(screenDocs ?? [])]\n .filter((screen: any) => !screen.deletedAt && !isScreenGroup(screen))\n .map((screen: any) => ({\n id: screen.$id as string,\n name: (screen.displayName ?? screen.name ?? screen.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n // Versions of the screen under test (AGL-253): variants pin one each.\n const { data: versionDocs } = useFirestoreCollection<any>(\n () =>\n editor?.screenId\n ? query(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'screens',\n editor.screenId,\n 'versions',\n ),\n limit(50),\n )\n : null,\n [firestore, hostId, editor?.screenId],\n { idField: '$id' },\n )\n const versionOptions = [...(versionDocs ?? [])]\n .filter((version: any) => !version.deletedAt)\n .map((version: any) => ({\n id: version.$id as string,\n name: (version.name ?? version.displayName ?? version.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n const [results, setResults] = useState<{\n experiment: ExperimentDraft\n stats: Record<string, { exposures?: number; conversions?: number }>\n } | null>(null)\n\n const patch = (partial: Partial<ExperimentDraft>) =>\n setEditor((previous) =>\n previous ? { ...previous, ...partial } : previous,\n )\n /**\n * What a widget in the variants zone proposes, put into the editor's own\n * fields as unsaved changes. Only the variants the editor already holds are\n * filled, in order: the test's shape — how many arms, their ids, their\n * weights, the version each screen variant pins — is the person's, and a\n * proposal that could add or drop an arm would be changing the test rather\n * than writing copy for it. A field a widget left empty is left alone, so\n * a proposal for an email's subject does not blank a body someone typed.\n */\n const proposeVariants = (\n proposed: readonly MarketingExperimentVariantDraft[],\n ) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index) => {\n const draft = proposed[index]\n if (!draft) return variant\n return {\n ...variant,\n ...(draft.name ? { name: draft.name } : {}),\n ...(draft.subject ? { subject: draft.subject } : {}),\n ...(draft.body ? { body: draft.body } : {}),\n // A draft version a widget made for this variant (AGL-3603),\n // pinned unsaved like a version picked from the list.\n ...(draft.versionId && previous.target !== 'email' ? { versionId: draft.versionId } : {}),\n }\n }),\n }\n : previous,\n )\n const patchVariant = (index: number, partial: Partial<ExperimentVariant>) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index2) =>\n index2 === index ? { ...variant, ...partial } : variant,\n ),\n }\n : previous,\n )\n\n const newExperiment = (): ExperimentDraft => ({\n name: '',\n status: 'draft',\n target: 'screen' as ExperimentTarget,\n variants: [\n { id: 'a', name: 'A (control)', weight: 1 },\n { id: 'b', name: 'B', weight: 1 },\n ],\n goal: { event: 'formSubmission' },\n })\n\n const handleSave = async () => {\n if (!editor) return\n const problem = validateExperiment(editor)\n if (problem) {\n return void enqueueSnackbar(problem, {\n variant: 'warning',\n persist: false,\n })\n }\n const id = editor.$id ?? createResourceUid()\n const { $id: _ignored, ...payload } = editor\n try {\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * `merge: true` is here so a cleared end date or auto-winner overwrites\n * (AGL-273), not to protect untouched fields — there are none, because\n * the payload is the whole stored row spread into `editor`. `status`\n * and `winnerVariantId` ride along with it, and those are otherwise\n * only moved by `setStatus`, so a cached seed can restart a test the\n * owner finished or halt one that is splitting live traffic.\n *\n * Only the edit path. A NEW experiment comes from `newExperiment()` at\n * a fresh uid and can overwrite nothing, and the first snapshot of any\n * listener is `fromCache: true`, so guarding a create would refuse a\n * save that was never unsafe.\n *\n * The guard WRAPS the write, and the activity log with it: a logged\n * \"Updated experiment\" whose write never happened leaves the history\n * disagreeing with the experiment it claims to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'experiment',\n unreadable: Boolean(editor.$id) && experimentsStatus === 'error',\n fromCache: Boolean(editor.$id) && experimentsFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'experiments', id),\n {\n ...JSON.parse(JSON.stringify(payload)),\n /*\n * The name's search keys, from the name being written — the\n * list's Experiment filter and its search box read them\n * (AGL-3321). After the payload, so keys copied from the stored\n * row cannot outlive a rename.\n */\n ...nameSearchFields(editor.name ?? ''),\n // Clearing the end date / auto-winner must overwrite — a\n // merge-set keeps absent keys otherwise (AGL-273).\n endAtMs: editor.endAtMs ?? null,\n autoWinner: editor.autoWinner ?? null,\n updatedAt: Timestamp.now(),\n ...(editor.$id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n ),\n })\n logActivity(\n editor.$id ? 'Updated experiment' : 'Created experiment',\n { type: 'content', id, name: editor.name },\n )\n },\n )\n // A refusal keeps the editor open with everything that was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n enqueueSnackbar('Experiment saved', {\n variant: 'success',\n persist: false,\n })\n setEditor(null)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n }\n }\n\n const setStatus = async (\n experiment: ExperimentDraft,\n status: HostExperiment['status'],\n winnerVariantId?: string,\n ) => {\n if (!experiment.$id) return\n /*\n * One running experiment per screen (AGL-265): the page runner only\n * serves the first, so a second would silently never split traffic.\n *\n * Asked of Firestore, not of the rows on screen: the list is one page of\n * a query the reader may have narrowed, and the running test on the same\n * screen is as likely to be on another page, or filtered out, as on this\n * one. Two equalities, served by merging their single-field indexes.\n */\n if (status === 'running' && experiment.screenId) {\n const running = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n where('status', '==', 'running'),\n where('screenId', '==', experiment.screenId),\n limit(2),\n ),\n )\n const clash = running.docs\n .map((entry) => ({ ...(entry.data() as HostExperiment), $id: entry.id }))\n .find((candidate) => candidate.$id !== experiment.$id)\n if (clash) {\n return void enqueueSnackbar(\n `\"${clash.name}\" is already running on that page — pause or ` +\n 'finish it first',\n { variant: 'warning', persist: false },\n )\n }\n }\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n {\n status,\n ...(winnerVariantId ? { winnerVariantId } : {}),\n updatedAt: Timestamp.now(),\n },\n { merge: true },\n ),\n })\n logActivity(`Experiment ${status}`, {\n type: 'content',\n id: experiment.$id,\n name: experiment.name,\n })\n }\n\n const handleDelete = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const accepted = await confirm({\n title: 'Delete experiment?',\n description: `\"${experiment.name}\" and its results are removed.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n // Resolves with no value and rejects on cancel — gating on the\n // resolved value alone made this always return (AGL-950).\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.delete(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n ),\n })\n }\n\n const openResults = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const snapshot = await getDocs(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'experiments',\n experiment.$id,\n 'stats',\n ),\n )\n const stats: Record<string, any> = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ experiment, stats })\n }\n\n /*\n * One row per experiment. The row opens its results, the one thing every\n * experiment has to show; starting, pausing, editing and deleting it are in\n * the row's menu rather than a row of buttons one mis-click from each other.\n */\n const experimentActions = (experiment: ExperimentDraft): RowActionsMenuItem[] => [\n ...(experiment.status === 'running'\n ? [\n {\n key: 'pause',\n label: 'Pause',\n icon: <MdiIcon path={mdiPause.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'paused'),\n },\n ]\n : experiment.status !== 'done'\n ? [\n {\n key: 'start',\n label: 'Start',\n icon: <MdiIcon path={mdiPlay.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'running'),\n },\n ]\n : []),\n {\n key: 'edit',\n label: 'Edit',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n onClick: () => setEditor({ ...experiment }),\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(experiment),\n },\n ]\n const experimentColumns: GridColDef<ExperimentDraft>[] = [\n { field: 'name', headerName: 'Experiment', flex: 1, minWidth: 200 },\n {\n field: 'target',\n headerName: 'Tests',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, experiment) =>\n `${EXPERIMENT_TARGET_NOUNS[experiment.target] ?? experiment.target} · ${(experiment.variants ?? []).length} variants`,\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n renderCell: ({ row: experiment }) => (\n <Chip\n size=\"small\"\n color={EXPERIMENT_STATUS_COLORS[experiment.status] ?? 'default'}\n label={experiment.status}\n />\n ),\n },\n listActionsColumn(\n (experiment: ExperimentDraft) => (\n <ListRowActions\n label={experiment.name || 'this experiment'}\n quick={{\n icon: mdiChartBar.path,\n label: 'Results',\n onClick: () => void openResults(experiment),\n }}\n items={experimentActions(experiment)}\n />\n ),\n ),\n ]\n\n return (\n <CardDisplay\n header=\"Experiments\"\n help={pluginDocsHelp('emailCampaigns', { anchor: '#experiments' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!entitled ? (\n <Alert severity=\"info\">\n {'A/B experiments are included in the Business plan — see ' +\n 'Billing to upgrade.'}\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Split traffic between variants of a page, a section, or a ' +\n 'campaign email, and measure which one converts. Visitors ' +\n 'are assigned deterministically, so everyone keeps seeing ' +\n 'the same variant.'}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => setEditor(newExperiment())}\n >\n {'New experiment'}\n </Button>\n {experiments.length === 0 && !hasMoreExperiments && experimentPage === 0 && !filtering ? null : (\n <>\n <ListFilterChips\n fields={EXPERIMENT_FILTER_FIELDS}\n headers={EXPERIMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={EXPERIMENT_FILTER_OPTIONS}\n />\n <ListQueryNotices refused={experimentRefusals} notices={experimentPlan.notices} />\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentColumnsFor(experimentColumns as GridColDef[])}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, experiment) => void openResults(experiment)}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n // The rows keep the query's order; a header sort would order\n // only the page on screen and read as the whole list's.\n disableColumnSorting\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), so the grid\n * never narrows the page it is handed.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No experiments match these filters\"\n />\n <ListPagination\n page={experimentPage}\n pageSize={experimentPageSize}\n rowCount={experiments.length}\n hasMore={hasMoreExperiments}\n onPageChange={setExperimentPage}\n onPageSizeChange={setExperimentPageSize}\n />\n </>\n )}\n </Stack>\n )}\n\n <Dialog\n open={Boolean(editor)}\n onClose={() => setEditor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>\n {editor?.$id ? 'Edit experiment' : 'New experiment'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={editor?.name ?? ''}\n onChange={(event) => patch({ name: event.target.value })}\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n size=\"small\"\n label=\"Tests\"\n value={editor?.target ?? 'screen'}\n onChange={(event) =>\n patch({ target: event.target.value as ExperimentTarget })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"screen\">{'A page'}</MenuItem>\n <MenuItem value=\"section\">{'A section'}</MenuItem>\n <MenuItem value=\"email\">{'An email'}</MenuItem>\n </TextField>\n {editor?.target !== 'email' ? (\n <TextField\n select\n size=\"small\"\n label=\"Page\"\n value={editor?.screenId ?? ''}\n onChange={(event) => patch({ screenId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {screenOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'section' ? (\n <TextField\n size=\"small\"\n label=\"Element id\"\n helperText=\"The canvas element the variants swap (from the besigner)\"\n value={editor?.nodeId ?? ''}\n onChange={(event) => patch({ nodeId: event.target.value })}\n />\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Conversion goal\"\n value={editor?.goal?.event ?? 'formSubmission'}\n onChange={(event) =>\n patch({ goal: { ...editor?.goal, event: event.target.value } })\n }\n sx={{ maxWidth: 260 }}\n >\n {[...HOST_EVENT_TYPES, ...SITE_EVENT_TYPES].map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {eventType}\n </MenuItem>\n ))}\n </TextField>\n {/* Schedule + auto-winner (AGL-273). */}\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Ends at (optional)\"\n slotProps={{ inputLabel: { shrink: true } }}\n value={\n editor?.endAtMs\n ? new Date(\n editor.endAtMs -\n new Date().getTimezoneOffset() * 60000,\n )\n .toISOString()\n .slice(0, 16)\n : ''\n }\n onChange={(event) => {\n const ms = event.target.value\n ? new Date(event.target.value).getTime()\n : undefined\n patch({\n endAtMs: Number.isFinite(ms as number) ? ms : undefined,\n })\n }}\n helperText=\"Past this, visitors get the default\"\n />\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(editor?.autoWinner)}\n onChange={(event) =>\n patch({\n autoWinner: event.target.checked\n ? { minExposures: 200, confidence: 0.95 }\n : undefined,\n })\n }\n />\n }\n label=\"Auto-declare winner\"\n />\n {editor?.autoWinner ? (\n <>\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Min exposures / variant\"\n value={editor.autoWinner.minExposures}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n minExposures: Number(event.target.value),\n },\n })\n }\n sx={{ width: 170 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Confidence\"\n value={String(editor.autoWinner.confidence)}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n confidence: Number(event.target.value),\n },\n })\n }\n sx={{ width: 120 }}\n >\n <MenuItem value=\"0.9\">{'90%'}</MenuItem>\n <MenuItem value=\"0.95\">{'95%'}</MenuItem>\n <MenuItem value=\"0.99\">{'99%'}</MenuItem>\n </TextField>\n </>\n ) : null}\n </Stack>\n <Typography variant=\"subtitle2\">{'Variants'}</Typography>\n {(editor?.variants ?? []).map((variant, index) => (\n <Stack key={variant.id} spacing={1}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label={`Variant ${variant.id.toUpperCase()}`}\n value={variant.name ?? ''}\n onChange={(event) =>\n patchVariant(index, { name: event.target.value })\n }\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Weight\"\n value={variant.weight ?? 1}\n onChange={(event) =>\n patchVariant(index, { weight: Number(event.target.value) })\n }\n sx={{ width: 100 }}\n />\n {editor?.target !== 'email' ? (\n // Screen/section variants pin a screen version\n // (AGL-253); empty = the published version.\n <TextField\n select\n size=\"small\"\n label=\"Version\"\n value={variant.versionId ?? ''}\n onChange={(event) =>\n patchVariant(index, { versionId: event.target.value })\n }\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'Published (control)'}</MenuItem>\n {versionOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'email' ? (\n // Email variants override the campaign copy (AGL-255).\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Subject override\"\n value={variant.subject ?? ''}\n onChange={(event) =>\n patchVariant(index, { subject: event.target.value })\n }\n sx={{ width: 220 }}\n />\n <TextField\n size=\"small\"\n label=\"Body override\"\n value={variant.body ?? ''}\n onChange={(event) =>\n patchVariant(index, { body: event.target.value })\n }\n multiline\n maxRows={3}\n sx={{ flex: 1 }}\n />\n </Stack>\n ) : null}\n </Stack>\n ))}\n {/*\n Beneath the variants, never above them: a widget here writes copy\n FOR the list that precedes it, and the list is what the person\n came to the dialog for.\n */}\n {editor ? (\n <ExperimentVariantsZone\n hostId={hostId}\n experimentId={editor.$id ?? ''}\n name={editor.name ?? ''}\n target={editor.target ?? 'screen'}\n goal={editor.goal?.event ?? ''}\n screenId={editor.target === 'email' ? '' : (editor.screenId ?? '')}\n nodeId={editor.target === 'section' ? (editor.nodeId ?? '') : ''}\n variants={marketingExperimentVariantDrafts(editor.variants)}\n proposeVariants={proposeVariants}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setEditor(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => void handleSave()}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(results)}\n onClose={() => setResults(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {`Results — ${results?.experiment.name ?? ''}`}\n {results?.experiment.autoCompleted ? (\n // The track pipeline decided this one (AGL-273).\n <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Exposures'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'vs control'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {/* Lift + z-test confidence vs the first variant (AGL-265). */}\n {(results\n ? experimentResultRows(results.experiment, results.stats)\n : []\n ).map(({ variant, summary, comparison, leader, winner }) => (\n <TableRow key={variant.id} selected={leader}>\n <TableCell>\n {variant.name ?? variant.id.toUpperCase()}\n {winner ? ' 🏆' : leader ? ' ▲' : ''}\n </TableCell>\n <TableCell>{summary.exposures}</TableCell>\n <TableCell>{summary.conversions}</TableCell>\n <TableCell>{`${(summary.rate * 100).toFixed(1)}%`}</TableCell>\n <TableCell>{describeVariantComparison(comparison)}</TableCell>\n <TableCell align=\"right\">\n {results?.experiment.status !== 'done' ? (\n <Button\n size=\"small\"\n onClick={() => {\n if (results) {\n void setStatus(results.experiment, 'done', variant.id)\n setResults(null)\n }\n }}\n >\n {'Pick winner'}\n </Button>\n ) : null}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/*\n Below the figures, so anything a widget says about this test is\n read after the counts it is talking about, and the test is named\n as the results reader labels it — its name, or its id where it has\n none, which is the same fallback that reader takes.\n */}\n {results ? (\n <ExperimentResultZone\n hostId={hostId}\n experimentId={results.experiment.$id ?? ''}\n test={results.experiment.name?.trim() || (results.experiment.$id ?? '')}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostExperimentsCard.displayName = 'HostExperimentsCard'\n\nexport default HostExperimentsCard\n"],"names":["checkEntitlement","createResourceUid","HOST_EVENT_TYPES","isScreenGroup","pluginDocsHelp","SITE_EVENT_TYPES","nameSearchFields","describeVariantComparison","experimentResultRows","validateExperiment","EXPERIMENT_LIST_QUERY","mdiChartBar","mdiDeleteOutline","mdiPause","mdiPencilOutline","mdiPlay","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","listFilterGridColumns","useListGridFilter","ScrollTable","TABLE_ROW_HEIGHT","useSnackbar","Timestamp","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","doc","getDocs","limit","query","where","useMemo","useState","useFirestore","useFirestoreCollection","useHostActivityLogger","writeGuardedBySeed","useUser","writeSiteWideChange","useListQuery","ExperimentResultZone","ExperimentVariantsZone","marketingExperimentVariantDrafts","EXPERIMENT_STATUS_COLORS","draft","running","paused","done","EXPERIMENT_FILTER_FIELDS","fields","EXPERIMENT_FILTER_HEADERS","name","target","status","EXPERIMENT_TARGET_NOUNS","screen","section","email","EXPERIMENT_FILTER_OPTIONS","value","label","Object","keys","map","charAt","toUpperCase","slice","HostExperimentsCard","props","editor","results","hostId","firestore","data","user","org","enqueueSnackbar","confirm","logActivity","entitled","gridFilter","selectFields","filtering","clauses","length","searchWords","some","word","trim","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","experimentsStatus","fromCache","experimentsFromCache","plan","experimentPlan","declaration","request","search","deps","idField","experiments","experimentColumnsFor","columns","experimentRefusals","refused","headers","options","setEditor","editorOpen","screenDocs","screenOptions","filter","deletedAt","id","$id","displayName","sort","a","b","localeCompare","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variants","variant","index","subject","body","versionId","patchVariant","index2","newExperiment","weight","goal","event","handleSave","problem","persist","_ignored","payload","verdict","unreadable","Boolean","write","batch","set","JSON","parse","stringify","endAtMs","autoWinner","updatedAt","now","createdAt","merge","type","ok","message","error","console","setStatus","experiment","winnerVariantId","clash","docs","entry","find","candidate","handleDelete","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","delete","openResults","snapshot","stats","forEach","experimentActions","key","icon","path","size","onClick","destructive","experimentColumns","field","headerName","flex","minWidth","valueGetter","_value","width","renderCell","row","quick","items","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","spacing","sx","alignSelf","onChange","setClauses","notices","aria-label","rowHeight","onOpen","_id","hideFooter","disableColumnSorting","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","mt","direction","select","option","helperText","nodeId","eventType","useFlexGap","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","winner","selected","exposures","conversions","rate","toFixed","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-experiments-card.component.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 {\n type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n HOST_EVENT_TYPES,\n isScreenGroup,\n pluginDocsHelp,\n SITE_EVENT_TYPES,\n} from '@aglyn/aglyn'\nimport { nameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport { describeVariantComparison, experimentResultRows, validateExperiment, type ExperimentTarget, type ExperimentVariant, type HostExperiment } from '../model'\nimport { EXPERIMENT_LIST_QUERY } from '../model/experiment-list-query'\nimport {\n mdiChartBar,\n mdiDeleteOutline,\n mdiPause,\n mdiPencilOutline,\n mdiPlay,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\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 type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n doc,\n getDocs,\n limit,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostActivityLogger,\n writeGuardedBySeed,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n ExperimentResultZone,\n ExperimentVariantsZone,\n marketingExperimentVariantDrafts,\n type MarketingExperimentVariantDraft,\n} from './experiment-zones'\n\nexport interface HostExperimentsCardProps {\n hostId: string\n /** Resolved entitlement source (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ntype ExperimentDraft = HostExperiment & { $id?: string }\n\n/** An experiment's status as a chip color, in every list of experiments. */\nexport const EXPERIMENT_STATUS_COLORS: Record<\n string,\n 'default' | 'success' | 'info' | 'warning'\n> = {\n draft: 'default',\n running: 'success',\n paused: 'warning',\n done: 'info',\n}\n\n/*\n * What the experiments grid's Filters panel offers: the fields of the list's\n * query declaration (`EXPERIMENT_LIST_QUERY`), every one served by Firestore\n * (AGL-3321).\n */\nconst EXPERIMENT_FILTER_FIELDS = EXPERIMENT_LIST_QUERY.fields\nconst EXPERIMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Experiment',\n target: 'Tests',\n status: 'Status',\n}\n/** What each stored `target` reads as; the stored value stays `screen`. */\nconst EXPERIMENT_TARGET_NOUNS: Readonly<Record<string, string>> = {\n screen: 'page',\n section: 'section',\n email: 'email',\n}\nconst EXPERIMENT_FILTER_OPTIONS = {\n target: [\n { value: 'screen', label: 'Page' },\n { value: 'section', label: 'Section' },\n { value: 'email', label: 'Email' },\n ],\n status: Object.keys(EXPERIMENT_STATUS_COLORS).map((status) => ({\n value: status,\n label: status.charAt(0).toUpperCase() + status.slice(1),\n })),\n}\n\n/** Tests is drawn from the target and the variant count: it sorts the page (AGL-3680). */\nconst EXPERIMENT_PAGE_SORTS = {\n target: (experiment: ExperimentDraft) =>\n `${EXPERIMENT_TARGET_NOUNS[experiment.target] ?? experiment.target} ${(experiment.variants ?? []).length}`,\n}\nconst EXPERIMENT_PAGE_SORT_HEADERS = { target: 'Tests' }\n\n/**\n * Experiments manager (AGL-252): create screen/section/email A/B tests\n * with weighted variants and a conversion goal; start/pause/finish them\n * and read exposures/conversions per variant. Business tier\n * (`ab-testing`).\n */\nexport function HostExperimentsCard(props: HostExperimentsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { org } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId)\n const entitled = checkEntitlement(org, 'abTesting')\n\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 site's experiments, ordered by name, and\n * 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 experiments that\n * happen to be loaded.\n */\n const gridFilter = useListGridFilter({ selectFields: ['target', 'status'] })\n // Experiment and Status order the query, Tests the page (AGL-3680).\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(null)\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const {\n rows: experimentRows,\n hasMore: hasMoreExperiments,\n page: experimentPage,\n setPage: setExperimentPage,\n pageSize: experimentPageSize,\n setPageSize: setExperimentPageSize,\n status: experimentsStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored experiment into `editor`\n * and writes all of it back, so `merge: true` protects nothing — every\n * field is in the payload. `status` is the one that bites: it is\n * otherwise only ever moved by the start/pause/finish controls below, so\n * a cached seed can quietly restart a finished test, or stop a running\n * one, along with reverting its variant weights and goal.\n */\n fromCache: experimentsFromCache,\n plan: experimentPlan,\n } = useListQuery<ExperimentDraft>({\n collection: collection(firestore, 'hosts', hostId, 'experiments'),\n /*\n * Ordered by the server and paged (AGL-2501, AGL-2292): the declaration's\n * one order is `name`, which every writer sets — the editor refuses a save\n * without one (`validateExperiment`), the besigner's section shortcut\n * names its draft, the two send-path readers only ever write the stats\n * beneath an experiment, and `experiments` is not in `IMPORTABLE_FIELDS`.\n * There is no writer that can produce a nameless experiment for `orderBy`\n * to drop.\n *\n * Nothing is dropped after the query either. An experiment is never\n * soft-deleted — Delete below is a `deleteDoc` — so every row the query\n * returns is live, and a page holds as many rows as its size.\n */\n declaration: EXPERIMENT_LIST_QUERY,\n request: { clauses: gridFilter.clauses, search: gridFilter.searchWords, sort: askedSort },\n deps: [firestore, hostId],\n idField: '$id',\n })\n const experimentSort = useListColumnSort<ExperimentDraft>({\n sorts: EXPERIMENT_LIST_QUERY.sorts,\n defaultSort: EXPERIMENT_LIST_QUERY.sorts[0],\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: experimentPlan.orderBy,\n rows: experimentRows,\n pageSorts: EXPERIMENT_PAGE_SORTS,\n headers: EXPERIMENT_PAGE_SORT_HEADERS,\n })\n const experiments: ExperimentDraft[] = experimentSort.rows as ExperimentDraft[]\n const experimentColumnsFor = (columns: GridColDef[]) =>\n listFilterGridColumns(\n columns,\n EXPERIMENT_FILTER_FIELDS,\n EXPERIMENT_FILTER_OPTIONS,\n EXPERIMENT_FILTER_HEADERS,\n )\n const experimentRefusals = useMemo(\n () =>\n listQueryRefusals(experimentPlan.refused, {\n fields: EXPERIMENT_FILTER_FIELDS,\n headers: EXPERIMENT_FILTER_HEADERS,\n options: EXPERIMENT_FILTER_OPTIONS,\n }),\n [experimentPlan.refused],\n )\n const [editor, setEditor] = useState<ExperimentDraft | null>(null)\n /*\n * The screens and their versions fill the EDITOR's pickers and nothing\n * else on the card, so they are read while the editor is open and not\n * before: a reader who came to see which tests are running pays for the\n * experiments list alone, not for a hundred screens they never pick from.\n */\n const editorOpen = editor !== null\n const { data: screenDocs } = useFirestoreCollection<any>(\n () =>\n editorOpen\n ? query(collection(firestore, 'hosts', hostId, 'screens'), limit(100))\n : null,\n [firestore, hostId, editorOpen],\n { idField: '$id' },\n )\n // A page group (AGL-3463) has no versions to test against each other.\n const screenOptions = [...(screenDocs ?? [])]\n .filter((screen: any) => !screen.deletedAt && !isScreenGroup(screen))\n .map((screen: any) => ({\n id: screen.$id as string,\n name: (screen.displayName ?? screen.name ?? screen.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n // Versions of the screen under test (AGL-253): variants pin one each.\n const { data: versionDocs } = useFirestoreCollection<any>(\n () =>\n editor?.screenId\n ? query(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'screens',\n editor.screenId,\n 'versions',\n ),\n limit(50),\n )\n : null,\n [firestore, hostId, editor?.screenId],\n { idField: '$id' },\n )\n const versionOptions = [...(versionDocs ?? [])]\n .filter((version: any) => !version.deletedAt)\n .map((version: any) => ({\n id: version.$id as string,\n name: (version.name ?? version.displayName ?? version.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n const [results, setResults] = useState<{\n experiment: ExperimentDraft\n stats: Record<string, { exposures?: number; conversions?: number }>\n } | null>(null)\n\n const patch = (partial: Partial<ExperimentDraft>) =>\n setEditor((previous) =>\n previous ? { ...previous, ...partial } : previous,\n )\n /**\n * What a widget in the variants zone proposes, put into the editor's own\n * fields as unsaved changes. Only the variants the editor already holds are\n * filled, in order: the test's shape — how many arms, their ids, their\n * weights, the version each screen variant pins — is the person's, and a\n * proposal that could add or drop an arm would be changing the test rather\n * than writing copy for it. A field a widget left empty is left alone, so\n * a proposal for an email's subject does not blank a body someone typed.\n */\n const proposeVariants = (\n proposed: readonly MarketingExperimentVariantDraft[],\n ) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index) => {\n const draft = proposed[index]\n if (!draft) return variant\n return {\n ...variant,\n ...(draft.name ? { name: draft.name } : {}),\n ...(draft.subject ? { subject: draft.subject } : {}),\n ...(draft.body ? { body: draft.body } : {}),\n // A draft version a widget made for this variant (AGL-3603),\n // pinned unsaved like a version picked from the list.\n ...(draft.versionId && previous.target !== 'email' ? { versionId: draft.versionId } : {}),\n }\n }),\n }\n : previous,\n )\n const patchVariant = (index: number, partial: Partial<ExperimentVariant>) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index2) =>\n index2 === index ? { ...variant, ...partial } : variant,\n ),\n }\n : previous,\n )\n\n const newExperiment = (): ExperimentDraft => ({\n name: '',\n status: 'draft',\n target: 'screen' as ExperimentTarget,\n variants: [\n { id: 'a', name: 'A (control)', weight: 1 },\n { id: 'b', name: 'B', weight: 1 },\n ],\n goal: { event: 'formSubmission' },\n })\n\n const handleSave = async () => {\n if (!editor) return\n const problem = validateExperiment(editor)\n if (problem) {\n return void enqueueSnackbar(problem, {\n variant: 'warning',\n persist: false,\n })\n }\n const id = editor.$id ?? createResourceUid()\n const { $id: _ignored, ...payload } = editor\n try {\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * `merge: true` is here so a cleared end date or auto-winner overwrites\n * (AGL-273), not to protect untouched fields — there are none, because\n * the payload is the whole stored row spread into `editor`. `status`\n * and `winnerVariantId` ride along with it, and those are otherwise\n * only moved by `setStatus`, so a cached seed can restart a test the\n * owner finished or halt one that is splitting live traffic.\n *\n * Only the edit path. A NEW experiment comes from `newExperiment()` at\n * a fresh uid and can overwrite nothing, and the first snapshot of any\n * listener is `fromCache: true`, so guarding a create would refuse a\n * save that was never unsafe.\n *\n * The guard WRAPS the write, and the activity log with it: a logged\n * \"Updated experiment\" whose write never happened leaves the history\n * disagreeing with the experiment it claims to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'experiment',\n unreadable: Boolean(editor.$id) && experimentsStatus === 'error',\n fromCache: Boolean(editor.$id) && experimentsFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'experiments', id),\n {\n ...JSON.parse(JSON.stringify(payload)),\n /*\n * The name's search keys, from the name being written — the\n * list's Experiment filter and its search box read them\n * (AGL-3321). After the payload, so keys copied from the stored\n * row cannot outlive a rename.\n */\n ...nameSearchFields(editor.name ?? ''),\n // Clearing the end date / auto-winner must overwrite — a\n // merge-set keeps absent keys otherwise (AGL-273).\n endAtMs: editor.endAtMs ?? null,\n autoWinner: editor.autoWinner ?? null,\n updatedAt: Timestamp.now(),\n ...(editor.$id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n ),\n })\n logActivity(\n editor.$id ? 'Updated experiment' : 'Created experiment',\n { type: 'content', id, name: editor.name },\n )\n },\n )\n // A refusal keeps the editor open with everything that was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n enqueueSnackbar('Experiment saved', {\n variant: 'success',\n persist: false,\n })\n setEditor(null)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n }\n }\n\n const setStatus = async (\n experiment: ExperimentDraft,\n status: HostExperiment['status'],\n winnerVariantId?: string,\n ) => {\n if (!experiment.$id) return\n /*\n * One running experiment per screen (AGL-265): the page runner only\n * serves the first, so a second would silently never split traffic.\n *\n * Asked of Firestore, not of the rows on screen: the list is one page of\n * a query the reader may have narrowed, and the running test on the same\n * screen is as likely to be on another page, or filtered out, as on this\n * one. Two equalities, served by merging their single-field indexes.\n */\n if (status === 'running' && experiment.screenId) {\n const running = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n where('status', '==', 'running'),\n where('screenId', '==', experiment.screenId),\n limit(2),\n ),\n )\n const clash = running.docs\n .map((entry) => ({ ...(entry.data() as HostExperiment), $id: entry.id }))\n .find((candidate) => candidate.$id !== experiment.$id)\n if (clash) {\n return void enqueueSnackbar(\n `\"${clash.name}\" is already running on that page — pause or ` +\n 'finish it first',\n { variant: 'warning', persist: false },\n )\n }\n }\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n {\n status,\n ...(winnerVariantId ? { winnerVariantId } : {}),\n updatedAt: Timestamp.now(),\n },\n { merge: true },\n ),\n })\n logActivity(`Experiment ${status}`, {\n type: 'content',\n id: experiment.$id,\n name: experiment.name,\n })\n }\n\n const handleDelete = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const accepted = await confirm({\n title: 'Delete experiment?',\n description: `\"${experiment.name}\" and its results are removed.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n // Resolves with no value and rejects on cancel — gating on the\n // resolved value alone made this always return (AGL-950).\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.delete(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n ),\n })\n }\n\n const openResults = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const snapshot = await getDocs(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'experiments',\n experiment.$id,\n 'stats',\n ),\n )\n const stats: Record<string, any> = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ experiment, stats })\n }\n\n /*\n * One row per experiment. The row opens its results, the one thing every\n * experiment has to show; starting, pausing, editing and deleting it are in\n * the row's menu rather than a row of buttons one mis-click from each other.\n */\n const experimentActions = (experiment: ExperimentDraft): RowActionsMenuItem[] => [\n ...(experiment.status === 'running'\n ? [\n {\n key: 'pause',\n label: 'Pause',\n icon: <MdiIcon path={mdiPause.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'paused'),\n },\n ]\n : experiment.status !== 'done'\n ? [\n {\n key: 'start',\n label: 'Start',\n icon: <MdiIcon path={mdiPlay.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'running'),\n },\n ]\n : []),\n {\n key: 'edit',\n label: 'Edit',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n onClick: () => setEditor({ ...experiment }),\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(experiment),\n },\n ]\n const experimentColumns: GridColDef<ExperimentDraft>[] = [\n { field: 'name', headerName: 'Experiment', flex: 1, minWidth: 200 },\n {\n field: 'target',\n headerName: 'Tests',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, experiment) =>\n `${EXPERIMENT_TARGET_NOUNS[experiment.target] ?? experiment.target} · ${(experiment.variants ?? []).length} variants`,\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n renderCell: ({ row: experiment }) => (\n <Chip\n size=\"small\"\n color={EXPERIMENT_STATUS_COLORS[experiment.status] ?? 'default'}\n label={experiment.status}\n />\n ),\n },\n listActionsColumn(\n (experiment: ExperimentDraft) => (\n <ListRowActions\n label={experiment.name || 'this experiment'}\n quick={{\n icon: mdiChartBar.path,\n label: 'Results',\n onClick: () => void openResults(experiment),\n }}\n items={experimentActions(experiment)}\n />\n ),\n ),\n ]\n\n return (\n <CardDisplay\n header=\"Experiments\"\n help={pluginDocsHelp('emailCampaigns', { anchor: '#experiments' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!entitled ? (\n <Alert severity=\"info\">\n {'A/B experiments are included in the Business plan — see ' +\n 'Billing to upgrade.'}\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Split traffic between variants of a page, a section, or a ' +\n 'campaign email, and measure which one converts. Visitors ' +\n 'are assigned deterministically, so everyone keeps seeing ' +\n 'the same variant.'}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => setEditor(newExperiment())}\n >\n {'New experiment'}\n </Button>\n {experiments.length === 0 && !hasMoreExperiments && experimentPage === 0 && !filtering ? null : (\n <>\n <ListFilterChips\n fields={EXPERIMENT_FILTER_FIELDS}\n headers={EXPERIMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={EXPERIMENT_FILTER_OPTIONS}\n />\n <ListQueryNotices\n refused={experimentRefusals}\n notices={[...experimentPlan.notices, ...experimentSort.notices]}\n />\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentColumnsFor(experimentColumns as GridColDef[])}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, experiment) => void openResults(experiment)}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n // Experiment and Status order the query; Tests sorts the page\n // and says so (AGL-3680).\n columnSort={experimentSort}\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), so the grid\n * never narrows the page it is handed.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No experiments match these filters\"\n />\n <ListPagination\n page={experimentPage}\n pageSize={experimentPageSize}\n rowCount={experiments.length}\n hasMore={hasMoreExperiments}\n onPageChange={setExperimentPage}\n onPageSizeChange={setExperimentPageSize}\n />\n </>\n )}\n </Stack>\n )}\n\n <Dialog\n open={Boolean(editor)}\n onClose={() => setEditor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>\n {editor?.$id ? 'Edit experiment' : 'New experiment'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={editor?.name ?? ''}\n onChange={(event) => patch({ name: event.target.value })}\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n size=\"small\"\n label=\"Tests\"\n value={editor?.target ?? 'screen'}\n onChange={(event) =>\n patch({ target: event.target.value as ExperimentTarget })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"screen\">{'A page'}</MenuItem>\n <MenuItem value=\"section\">{'A section'}</MenuItem>\n <MenuItem value=\"email\">{'An email'}</MenuItem>\n </TextField>\n {editor?.target !== 'email' ? (\n <TextField\n select\n size=\"small\"\n label=\"Page\"\n value={editor?.screenId ?? ''}\n onChange={(event) => patch({ screenId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {screenOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'section' ? (\n <TextField\n size=\"small\"\n label=\"Element id\"\n helperText=\"The canvas element the variants swap (from the besigner)\"\n value={editor?.nodeId ?? ''}\n onChange={(event) => patch({ nodeId: event.target.value })}\n />\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Conversion goal\"\n value={editor?.goal?.event ?? 'formSubmission'}\n onChange={(event) =>\n patch({ goal: { ...editor?.goal, event: event.target.value } })\n }\n sx={{ maxWidth: 260 }}\n >\n {[...HOST_EVENT_TYPES, ...SITE_EVENT_TYPES].map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {eventType}\n </MenuItem>\n ))}\n </TextField>\n {/* Schedule + auto-winner (AGL-273). */}\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Ends at (optional)\"\n slotProps={{ inputLabel: { shrink: true } }}\n value={\n editor?.endAtMs\n ? new Date(\n editor.endAtMs -\n new Date().getTimezoneOffset() * 60000,\n )\n .toISOString()\n .slice(0, 16)\n : ''\n }\n onChange={(event) => {\n const ms = event.target.value\n ? new Date(event.target.value).getTime()\n : undefined\n patch({\n endAtMs: Number.isFinite(ms as number) ? ms : undefined,\n })\n }}\n helperText=\"Past this, visitors get the default\"\n />\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(editor?.autoWinner)}\n onChange={(event) =>\n patch({\n autoWinner: event.target.checked\n ? { minExposures: 200, confidence: 0.95 }\n : undefined,\n })\n }\n />\n }\n label=\"Auto-declare winner\"\n />\n {editor?.autoWinner ? (\n <>\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Min exposures / variant\"\n value={editor.autoWinner.minExposures}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n minExposures: Number(event.target.value),\n },\n })\n }\n sx={{ width: 170 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Confidence\"\n value={String(editor.autoWinner.confidence)}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n confidence: Number(event.target.value),\n },\n })\n }\n sx={{ width: 120 }}\n >\n <MenuItem value=\"0.9\">{'90%'}</MenuItem>\n <MenuItem value=\"0.95\">{'95%'}</MenuItem>\n <MenuItem value=\"0.99\">{'99%'}</MenuItem>\n </TextField>\n </>\n ) : null}\n </Stack>\n <Typography variant=\"subtitle2\">{'Variants'}</Typography>\n {(editor?.variants ?? []).map((variant, index) => (\n <Stack key={variant.id} spacing={1}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label={`Variant ${variant.id.toUpperCase()}`}\n value={variant.name ?? ''}\n onChange={(event) =>\n patchVariant(index, { name: event.target.value })\n }\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Weight\"\n value={variant.weight ?? 1}\n onChange={(event) =>\n patchVariant(index, { weight: Number(event.target.value) })\n }\n sx={{ width: 100 }}\n />\n {editor?.target !== 'email' ? (\n // Screen/section variants pin a screen version\n // (AGL-253); empty = the published version.\n <TextField\n select\n size=\"small\"\n label=\"Version\"\n value={variant.versionId ?? ''}\n onChange={(event) =>\n patchVariant(index, { versionId: event.target.value })\n }\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'Published (control)'}</MenuItem>\n {versionOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'email' ? (\n // Email variants override the campaign copy (AGL-255).\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Subject override\"\n value={variant.subject ?? ''}\n onChange={(event) =>\n patchVariant(index, { subject: event.target.value })\n }\n sx={{ width: 220 }}\n />\n <TextField\n size=\"small\"\n label=\"Body override\"\n value={variant.body ?? ''}\n onChange={(event) =>\n patchVariant(index, { body: event.target.value })\n }\n multiline\n maxRows={3}\n sx={{ flex: 1 }}\n />\n </Stack>\n ) : null}\n </Stack>\n ))}\n {/*\n Beneath the variants, never above them: a widget here writes copy\n FOR the list that precedes it, and the list is what the person\n came to the dialog for.\n */}\n {editor ? (\n <ExperimentVariantsZone\n hostId={hostId}\n experimentId={editor.$id ?? ''}\n name={editor.name ?? ''}\n target={editor.target ?? 'screen'}\n goal={editor.goal?.event ?? ''}\n screenId={editor.target === 'email' ? '' : (editor.screenId ?? '')}\n nodeId={editor.target === 'section' ? (editor.nodeId ?? '') : ''}\n variants={marketingExperimentVariantDrafts(editor.variants)}\n proposeVariants={proposeVariants}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setEditor(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => void handleSave()}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(results)}\n onClose={() => setResults(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {`Results — ${results?.experiment.name ?? ''}`}\n {results?.experiment.autoCompleted ? (\n // The track pipeline decided this one (AGL-273).\n <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Exposures'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'vs control'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {/* Lift + z-test confidence vs the first variant (AGL-265). */}\n {(results\n ? experimentResultRows(results.experiment, results.stats)\n : []\n ).map(({ variant, summary, comparison, leader, winner }) => (\n <TableRow key={variant.id} selected={leader}>\n <TableCell>\n {variant.name ?? variant.id.toUpperCase()}\n {winner ? ' 🏆' : leader ? ' ▲' : ''}\n </TableCell>\n <TableCell>{summary.exposures}</TableCell>\n <TableCell>{summary.conversions}</TableCell>\n <TableCell>{`${(summary.rate * 100).toFixed(1)}%`}</TableCell>\n <TableCell>{describeVariantComparison(comparison)}</TableCell>\n <TableCell align=\"right\">\n {results?.experiment.status !== 'done' ? (\n <Button\n size=\"small\"\n onClick={() => {\n if (results) {\n void setStatus(results.experiment, 'done', variant.id)\n setResults(null)\n }\n }}\n >\n {'Pick winner'}\n </Button>\n ) : null}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/*\n Below the figures, so anything a widget says about this test is\n read after the counts it is talking about, and the test is named\n as the results reader labels it — its name, or its id where it has\n none, which is the same fallback that reader takes.\n */}\n {results ? (\n <ExperimentResultZone\n hostId={hostId}\n experimentId={results.experiment.$id ?? ''}\n test={results.experiment.name?.trim() || (results.experiment.$id ?? '')}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostExperimentsCard.displayName = 'HostExperimentsCard'\n\nexport default HostExperimentsCard\n"],"names":["checkEntitlement","createResourceUid","HOST_EVENT_TYPES","isScreenGroup","pluginDocsHelp","SITE_EVENT_TYPES","nameSearchFields","describeVariantComparison","experimentResultRows","validateExperiment","EXPERIMENT_LIST_QUERY","mdiChartBar","mdiDeleteOutline","mdiPause","mdiPencilOutline","mdiPlay","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","listFilterGridColumns","useListGridFilter","useListColumnSort","ScrollTable","TABLE_ROW_HEIGHT","useSnackbar","Timestamp","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","doc","getDocs","limit","query","where","useMemo","useState","useFirestore","useFirestoreCollection","useHostActivityLogger","writeGuardedBySeed","useUser","writeSiteWideChange","useListQuery","ExperimentResultZone","ExperimentVariantsZone","marketingExperimentVariantDrafts","EXPERIMENT_STATUS_COLORS","draft","running","paused","done","EXPERIMENT_FILTER_FIELDS","fields","EXPERIMENT_FILTER_HEADERS","name","target","status","EXPERIMENT_TARGET_NOUNS","screen","section","email","EXPERIMENT_FILTER_OPTIONS","value","label","Object","keys","map","charAt","toUpperCase","slice","EXPERIMENT_PAGE_SORTS","experiment","variants","length","EXPERIMENT_PAGE_SORT_HEADERS","HostExperimentsCard","props","editor","results","hostId","firestore","data","user","org","enqueueSnackbar","confirm","logActivity","entitled","gridFilter","selectFields","askedSort","setAskedSort","filtering","clauses","searchWords","some","word","trim","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","experimentsStatus","fromCache","experimentsFromCache","plan","experimentPlan","declaration","request","search","sort","deps","idField","experimentSort","sorts","defaultSort","onSortChange","orderBy","pageSorts","headers","experiments","experimentColumnsFor","columns","experimentRefusals","refused","options","setEditor","editorOpen","screenDocs","screenOptions","filter","deletedAt","id","$id","displayName","a","b","localeCompare","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variant","index","subject","body","versionId","patchVariant","index2","newExperiment","weight","goal","event","handleSave","problem","persist","_ignored","payload","verdict","unreadable","Boolean","write","batch","set","JSON","parse","stringify","endAtMs","autoWinner","updatedAt","now","createdAt","merge","type","ok","message","error","console","setStatus","winnerVariantId","clash","docs","entry","find","candidate","handleDelete","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","delete","openResults","snapshot","stats","forEach","experimentActions","key","icon","path","size","onClick","destructive","experimentColumns","field","headerName","flex","minWidth","valueGetter","_value","width","renderCell","row","quick","items","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","spacing","sx","alignSelf","onChange","setClauses","notices","aria-label","rowHeight","onOpen","_id","hideFooter","columnSort","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","mt","direction","select","option","helperText","nodeId","eventType","useFlexGap","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","winner","selected","exposures","conversions","rate","toFixed","test"],"mappings":"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"}
@@ -22,6 +22,8 @@ import { CardDisplay } from "@aglyn/shared-ui-jsx";
22
22
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
23
23
  import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
24
24
  import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
25
+ import ListQueryNotices from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
26
+ import { useListColumnSort } from "@aglyn/shared-ui-jsx/hooks/use-list-column-sort";
25
27
  import { useFirestore, usePagedCollection } from "@aglyn/tenant-feature-instance";
26
28
  import { Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Tab, Tabs, Typography } from "@mui/material";
27
29
  import { collection, documentId, limit, orderBy, query } from "firebase/firestore";
@@ -85,16 +87,80 @@ const whenOf = (value)=>{
85
87
  ]
86
88
  });
87
89
  }
88
- /** One paged table of an organization subcollection. */ function OrgCollectionTable(props) {
89
- const { orgId, collectionId, columns, emptyLabel, onOpen } = props;
90
+ /** One paged table of an organization subcollection. */ /*
91
+ * EVERY HEADER SORTS (AGL-3680). Both tables walk their collection by id;
92
+ * the name column orders the QUERY by its lower-cased key instead — a send's
93
+ * `subjectLower`, a campaign's `nameLower`, each stamped by every writer
94
+ * (`campaignSendSearchFields`, `campaignContainerSearchFields`) and on older
95
+ * records by `backfill-campaign-list-fields.mjs`. With no filter on these
96
+ * staff tables, an order on the unscoped collection needs no composite.
97
+ * The rest is drawn from several fields, or a value a send may not carry
98
+ * (a draft names no audience), and sorts the page.
99
+ */ const nameSorts = (path, column, label)=>[
100
+ {
101
+ path,
102
+ direction: 'asc',
103
+ column,
104
+ label
105
+ },
106
+ {
107
+ path,
108
+ direction: 'desc',
109
+ column,
110
+ label
111
+ }
112
+ ];
113
+ const SEND_SORTS = nameSorts('subjectLower', 'subject', 'Subject');
114
+ const CAMPAIGN_SORTS = nameSorts('nameLower', 'name', 'Campaign');
115
+ const millisOf = (value)=>typeof (value == null ? void 0 : value.toMillis) === 'function' ? value.toMillis() : typeof (value == null ? void 0 : value.seconds) === 'number' ? value.seconds * 1000 : typeof value === 'number' ? value : null;
116
+ const SEND_PAGE_SORTS = {
117
+ status: (row)=>{
118
+ var _row_status;
119
+ return String((_row_status = row['status']) != null ? _row_status : 'draft');
120
+ },
121
+ audience: (row)=>row['audience'] ? String(row['audience']) : null,
122
+ when: (row)=>{
123
+ var _ref, _millisOf;
124
+ return (_ref = (_millisOf = millisOf(row['sentAt'])) != null ? _millisOf : millisOf(row['sendAtMs'])) != null ? _ref : millisOf(row['createdAtMs']);
125
+ }
126
+ };
127
+ const SEND_PAGE_SORT_HEADERS = {
128
+ status: 'Status',
129
+ audience: 'Audience',
130
+ when: 'When'
131
+ };
132
+ const CAMPAIGN_PAGE_SORTS = {
133
+ visibleTo: (row)=>Array.isArray(row['visibleTo']) ? row['visibleTo'].join(', ') : null,
134
+ window: (row)=>{
135
+ var _millisOf;
136
+ return (_millisOf = millisOf(row['startAtMs'])) != null ? _millisOf : millisOf(row['endAtMs']);
137
+ }
138
+ };
139
+ const CAMPAIGN_PAGE_SORT_HEADERS = {
140
+ visibleTo: 'Runs on',
141
+ window: 'Window'
142
+ };
143
+ function OrgCollectionTable(props) {
144
+ const { orgId, collectionId, columns, emptyLabel, onOpen, sorts, pageSorts, sortHeaders } = props;
90
145
  const firestore = useFirestore();
91
- const paged = usePagedCollection((pageLimit)=>orgId ? query(collection(firestore, 'orgs', orgId, collectionId), orderBy(documentId()), limit(pageLimit)) : null, [
146
+ const [askedSort, setAskedSort] = useState(null);
147
+ const paged = usePagedCollection((pageLimit)=>orgId ? query(collection(firestore, 'orgs', orgId, collectionId), askedSort ? orderBy(askedSort.path, askedSort.direction) : orderBy(documentId()), limit(pageLimit)) : null, [
92
148
  firestore,
93
149
  orgId,
94
- collectionId
150
+ collectionId,
151
+ askedSort == null ? void 0 : askedSort.path,
152
+ askedSort == null ? void 0 : askedSort.direction
95
153
  ], {
96
154
  idField: '$id'
97
155
  });
156
+ const columnSort = useListColumnSort({
157
+ sorts,
158
+ sort: askedSort,
159
+ onSortChange: setAskedSort,
160
+ rows: paged.rows,
161
+ pageSorts,
162
+ headers: sortHeaders
163
+ });
98
164
  const byId = useMemo(()=>new Map(paged.rows.map((row)=>[
99
165
  row.$id,
100
166
  row
@@ -104,11 +170,15 @@ const whenOf = (value)=>{
104
170
  return /*#__PURE__*/ _jsxs(Stack, {
105
171
  spacing: 1,
106
172
  children: [
173
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
174
+ refused: [],
175
+ notices: columnSort.notices
176
+ }),
107
177
  /*#__PURE__*/ _jsx(ListTable, {
108
- rows: paged.rows,
178
+ rows: columnSort.rows,
109
179
  columns: columns,
110
180
  loading: paged.status === 'loading',
111
- disableColumnSorting: true,
181
+ columnSort: columnSort,
112
182
  noRowsLabel: emptyLabel,
113
183
  onOpen: onOpen ? (id)=>{
114
184
  const row = byId.get(String(id));
@@ -360,12 +430,18 @@ const chips = (items)=>/*#__PURE__*/ _jsx(Stack, {
360
430
  orgId: orgId,
361
431
  collectionId: "campaigns",
362
432
  columns: sendColumns,
433
+ sorts: SEND_SORTS,
434
+ pageSorts: SEND_PAGE_SORTS,
435
+ sortHeaders: SEND_PAGE_SORT_HEADERS,
363
436
  emptyLabel: "This organization has sent no campaigns",
364
437
  onOpen: (row)=>row['body'] ? setMessage(row) : undefined
365
438
  }, "sends") : /*#__PURE__*/ _jsx(OrgCollectionTable, {
366
439
  orgId: orgId,
367
440
  collectionId: "emailCampaigns",
368
441
  columns: campaignColumns,
442
+ sorts: CAMPAIGN_SORTS,
443
+ pageSorts: CAMPAIGN_PAGE_SORTS,
444
+ sortHeaders: CAMPAIGN_PAGE_SORT_HEADERS,
369
445
  emptyLabel: "This organization has no email campaigns"
370
446
  }, "campaigns")
371
447
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/staff-org-email-card.component.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiEyeOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useFirestore, usePagedCollection } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, documentId, limit, orderBy, query } from 'firebase/firestore'\nimport { type ReactNode, useMemo, useState } from 'react'\n\n/*\n * AN ORGANIZATION'S EMAIL WORK, AS STAFF SEE IT (AGL-3380).\n *\n * The staff organization page is about a customer the staffer is not a\n * member of, so nothing here sends, schedules or edits. Each tab is a paged\n * walk of one collection in document-id order. A send opens the message it\n * stored, drawn in a frame that runs nothing, or the design it was composed\n * from in the staff preview.\n */\n\ntype Row = { $id: string } & Record<string, any>\n\nconst whenOf = (value: any): string => {\n if (typeof value?.toDate === 'function') return value.toDate().toLocaleString()\n if (typeof value?.seconds === 'number') return new Date(value.seconds * 1000).toLocaleString()\n if (typeof value === 'number') return new Date(value).toLocaleString()\n return '—'\n}\n\n/** When a send went, or is due: the sent stamp, else the schedule, else its creation. */\nconst sendWhen = (row: Row): string =>\n row['sentAt']\n ? whenOf(row['sentAt'])\n : row['sendAtMs']\n ? `due ${whenOf(row['sendAtMs'])}`\n : whenOf(row['createdAtMs'])\n\n/** The staff preview of the design a send was composed from, if it names one. */\nconst designPreviewHref = (row: Row): string | undefined =>\n typeof row['templateScreenId'] === 'string' &&\n row['templateScreenId'] &&\n typeof row['hostId'] === 'string' &&\n row['hostId']\n ? buildRoute(Route.ADMIN_SITE_PREVIEW, {\n hostId: row['hostId'],\n kind: 'screen',\n docId: row['templateScreenId'],\n })\n : undefined\n\n/**\n * A stored message body, in a frame with `sandbox` and no permissions: a\n * customer's HTML is only ever pixels on the staff page.\n */\nfunction MessageDialog(props: { row: Row | null; onClose: () => void }) {\n const { row, onClose } = props\n return (\n <Dialog open={Boolean(row)} onClose={onClose} maxWidth=\"md\" fullWidth>\n <DialogTitle>{row ? String(row['subject'] || row.$id) : ''}</DialogTitle>\n <DialogContent>\n {row?.['preheader'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {String(row['preheader'])}\n </Typography>\n ) : null}\n <Box\n component=\"iframe\"\n title=\"Message\"\n sandbox=\"\"\n srcDoc={String(row?.['body'] ?? '')}\n sx={{ mt: 1, width: '100%', height: 560, border: 1, borderColor: 'divider', bgcolor: '#fff' }}\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\n/** One paged table of an organization subcollection. */\nfunction OrgCollectionTable(props: {\n orgId: string\n collectionId: string\n columns: GridColDef[]\n emptyLabel: string\n onOpen?: (row: Row) => void\n}) {\n const { orgId, collectionId, columns, emptyLabel, onOpen } = props\n const firestore = useFirestore()\n const paged = usePagedCollection<Row>(\n (pageLimit) =>\n orgId\n ? query(\n collection(firestore, 'orgs', orgId, collectionId),\n orderBy(documentId()),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, collectionId],\n { idField: '$id' },\n )\n const byId = useMemo(() => new Map(paged.rows.map((row) => [row.$id, row])), [paged.rows])\n return (\n <Stack spacing={1}>\n <ListTable\n rows={paged.rows}\n columns={columns}\n loading={paged.status === 'loading'}\n disableColumnSorting\n noRowsLabel={emptyLabel}\n onOpen={\n onOpen\n ? (id) => {\n const row = byId.get(String(id))\n if (row) onOpen(row)\n }\n : undefined\n }\n hideFooter\n rowHeight={TABLE_ROW_HEIGHT}\n />\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n )\n}\n\nconst chips = (items: Array<{ label: string; color?: 'success' | 'warning' | 'error' }>): ReactNode => (\n <Stack direction=\"row\" spacing={0.5} useFlexGap sx={{ alignItems: 'center', height: '100%', flexWrap: 'wrap' }}>\n {items.map((item) => (\n <Chip\n key={item.label}\n size=\"small\"\n label={item.label}\n color={item.color ?? 'default'}\n variant={item.color ? 'filled' : 'outlined'}\n />\n ))}\n </Stack>\n)\n\ntype EmailTab = 'sends' | 'campaigns'\n\n/**\n * The staff organization page's email card (`adminOrgDetail` zone): every\n * campaign send with its state and numbers, and the campaign containers\n * with the sites each runs on.\n */\nexport function StaffOrgEmailCard(props: { orgId: string }) {\n const { orgId } = props\n const [tab, setTab] = useState<EmailTab>('sends')\n const [message, setMessage] = useState<Row | null>(null)\n\n const sendColumns: GridColDef[] = useMemo(\n () => [\n {\n field: 'subject',\n headerName: 'Subject',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: Row) => String(row['subject'] || '(no subject)'),\n renderCell: ({ row }: { row: Row }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" noWrap sx={{ lineHeight: 1.25 }}>\n {String(row['subject'] || '(no subject)')}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace', lineHeight: 1.25 }}\n >\n {String(row['hostId'] ?? 'no site')}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 1.4,\n minWidth: 220,\n valueGetter: (_value, row: Row) => String(row['status'] ?? 'draft'),\n renderCell: ({ row }: { row: Row }) => {\n const stats = row['stats'] ?? {}\n return chips([\n {\n label: String(row['status'] ?? 'draft'),\n color: row['status'] === 'sent' ? 'success' : row['status'] === 'failed' ? 'error' : undefined,\n },\n ...(row['staffReview']?.state === 'held'\n ? [{ label: 'held for review', color: 'warning' as const }]\n : []),\n ...(typeof stats.sent === 'number' ? [{ label: `${stats.sent} sent` }] : []),\n ...(typeof stats.uniqueOpens === 'number' ? [{ label: `${stats.uniqueOpens} opened` }] : []),\n ...(typeof stats.uniqueClicks === 'number' ? [{ label: `${stats.uniqueClicks} clicked` }] : []),\n ...(stats.bounced ? [{ label: `${stats.bounced} bounced`, color: 'error' as const }] : []),\n ])\n },\n },\n {\n field: 'audience',\n headerName: 'Audience',\n flex: 0.6,\n minWidth: 110,\n valueGetter: (_value, row: Row) => String(row['audience'] ?? '—'),\n },\n {\n field: 'when',\n headerName: 'When',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: Row) => sendWhen(row),\n },\n listActionsColumn(\n (row: Row) => {\n const design = designPreviewHref(row)\n return (\n <ListRowActions\n label={String(row['subject'] || row.$id)}\n quick={{\n icon: mdiEyeOutline.path,\n label: 'View message',\n onClick: () => setMessage(row),\n unavailableReason: row['body'] ? undefined : 'No message body was stored',\n }}\n items={[\n {\n key: 'design',\n label: 'Open design preview',\n href: design,\n external: true,\n disabled: !design,\n disabledReason: design ? undefined : 'This send was not composed from a design',\n },\n {\n key: 'site',\n label: 'Open site',\n href: row['hostId']\n ? buildRoute(Route.ADMIN_SITE_DETAIL, { hostId: String(row['hostId']) })\n : undefined,\n disabled: !row['hostId'],\n disabledReason: row['hostId'] ? undefined : 'This send names no site',\n },\n ]}\n />\n )\n },\n { width: 110 },\n ),\n ],\n [],\n )\n\n const campaignColumns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Campaign',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: Row) => String(row['name'] || row.$id),\n },\n {\n field: 'visibleTo',\n headerName: 'Runs on',\n flex: 1.2,\n minWidth: 180,\n valueGetter: (_value, row: Row) =>\n (Array.isArray(row['visibleTo']) ? row['visibleTo'] : []).join(', '),\n renderCell: ({ row }: { row: Row }) =>\n chips([\n ...(Array.isArray(row['visibleTo']) ? (row['visibleTo'] as string[]) : []).map((token) => ({\n label: token === 'org' ? 'every site' : token.replace(/^host:/, ''),\n })),\n ...(row['deletedAt'] ? [{ label: 'deleted', color: 'error' as const }] : []),\n ]),\n },\n {\n field: 'window',\n headerName: 'Window',\n flex: 1,\n minWidth: 180,\n valueGetter: (_value, row: Row) =>\n row['startAtMs'] || row['endAtMs']\n ? `${whenOf(row['startAtMs'])} – ${row['endAtMs'] ? whenOf(row['endAtMs']) : 'open'}`\n : '—',\n },\n ],\n [],\n )\n\n return (\n <>\n <CardDisplay\n header={'Email campaigns'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-org-email',\n excerpt:\n \"The organization's campaign sends and campaigns. A send opens its stored message or its design preview.\",\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Tabs\n value={tab}\n onChange={(_event, next: EmailTab) => setTab(next)}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n allowScrollButtonsMobile\n >\n\n <Tab value=\"sends\" label=\"Sends\" />\n <Tab value=\"campaigns\" label=\"Campaigns\" />\n </Tabs>\n {tab === 'sends' ? (\n <OrgCollectionTable\n key=\"sends\"\n orgId={orgId}\n collectionId=\"campaigns\"\n columns={sendColumns}\n emptyLabel=\"This organization has sent no campaigns\"\n onOpen={(row) => (row['body'] ? setMessage(row) : undefined)}\n />\n ) : (\n <OrgCollectionTable\n key=\"campaigns\"\n orgId={orgId}\n collectionId=\"emailCampaigns\"\n columns={campaignColumns}\n emptyLabel=\"This organization has no email campaigns\"\n />\n )}\n </Stack>\n </CardDisplay>\n <MessageDialog row={message} onClose={() => setMessage(null)} />\n </>\n )\n}\n"],"names":["buildRoute","Route","pluginDocsHelp","mdiEyeOutline","CardDisplay","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useFirestore","usePagedCollection","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Tab","Tabs","Typography","collection","documentId","limit","orderBy","query","useMemo","useState","whenOf","value","toDate","toLocaleString","seconds","Date","sendWhen","row","designPreviewHref","ADMIN_SITE_PREVIEW","hostId","kind","docId","undefined","MessageDialog","props","onClose","open","Boolean","maxWidth","fullWidth","String","$id","variant","color","component","title","sandbox","srcDoc","sx","mt","width","height","border","borderColor","bgcolor","onClick","OrgCollectionTable","orgId","collectionId","columns","emptyLabel","onOpen","firestore","paged","pageLimit","idField","byId","Map","rows","map","spacing","loading","status","disableColumnSorting","noRowsLabel","id","get","hideFooter","rowHeight","page","pageSize","rowCount","length","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","chips","items","direction","useFlexGap","alignItems","flexWrap","item","size","label","StaffOrgEmailCard","tab","setTab","message","setMessage","sendColumns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","justifyContent","lineHeight","noWrap","fontFamily","stats","state","sent","uniqueOpens","uniqueClicks","bounced","design","quick","icon","path","unavailableReason","key","href","external","disabled","disabledReason","ADMIN_SITE_DETAIL","campaignColumns","Array","isArray","join","token","replace","header","help","anchor","excerpt","contentGutterX","contentGutterY","onChange","_event","next","scrollButtons","allowScrollButtonsMobile"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/staff-org-email-card.component.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiEyeOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport { useFirestore, usePagedCollection } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, documentId, limit, orderBy, query } from 'firebase/firestore'\nimport { type ReactNode, useMemo, useState } from 'react'\n\n/*\n * AN ORGANIZATION'S EMAIL WORK, AS STAFF SEE IT (AGL-3380).\n *\n * The staff organization page is about a customer the staffer is not a\n * member of, so nothing here sends, schedules or edits. Each tab is a paged\n * walk of one collection in document-id order. A send opens the message it\n * stored, drawn in a frame that runs nothing, or the design it was composed\n * from in the staff preview.\n */\n\ntype Row = { $id: string } & Record<string, any>\n\nconst whenOf = (value: any): string => {\n if (typeof value?.toDate === 'function') return value.toDate().toLocaleString()\n if (typeof value?.seconds === 'number') return new Date(value.seconds * 1000).toLocaleString()\n if (typeof value === 'number') return new Date(value).toLocaleString()\n return '—'\n}\n\n/** When a send went, or is due: the sent stamp, else the schedule, else its creation. */\nconst sendWhen = (row: Row): string =>\n row['sentAt']\n ? whenOf(row['sentAt'])\n : row['sendAtMs']\n ? `due ${whenOf(row['sendAtMs'])}`\n : whenOf(row['createdAtMs'])\n\n/** The staff preview of the design a send was composed from, if it names one. */\nconst designPreviewHref = (row: Row): string | undefined =>\n typeof row['templateScreenId'] === 'string' &&\n row['templateScreenId'] &&\n typeof row['hostId'] === 'string' &&\n row['hostId']\n ? buildRoute(Route.ADMIN_SITE_PREVIEW, {\n hostId: row['hostId'],\n kind: 'screen',\n docId: row['templateScreenId'],\n })\n : undefined\n\n/**\n * A stored message body, in a frame with `sandbox` and no permissions: a\n * customer's HTML is only ever pixels on the staff page.\n */\nfunction MessageDialog(props: { row: Row | null; onClose: () => void }) {\n const { row, onClose } = props\n return (\n <Dialog open={Boolean(row)} onClose={onClose} maxWidth=\"md\" fullWidth>\n <DialogTitle>{row ? String(row['subject'] || row.$id) : ''}</DialogTitle>\n <DialogContent>\n {row?.['preheader'] ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {String(row['preheader'])}\n </Typography>\n ) : null}\n <Box\n component=\"iframe\"\n title=\"Message\"\n sandbox=\"\"\n srcDoc={String(row?.['body'] ?? '')}\n sx={{ mt: 1, width: '100%', height: 560, border: 1, borderColor: 'divider', bgcolor: '#fff' }}\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\n/** One paged table of an organization subcollection. */\n/*\n * EVERY HEADER SORTS (AGL-3680). Both tables walk their collection by id;\n * the name column orders the QUERY by its lower-cased key instead — a send's\n * `subjectLower`, a campaign's `nameLower`, each stamped by every writer\n * (`campaignSendSearchFields`, `campaignContainerSearchFields`) and on older\n * records by `backfill-campaign-list-fields.mjs`. With no filter on these\n * staff tables, an order on the unscoped collection needs no composite.\n * The rest is drawn from several fields, or a value a send may not carry\n * (a draft names no audience), and sorts the page.\n */\nconst nameSorts = (path: string, column: string, label: string): ListQuerySort[] => [\n { path, direction: 'asc', column, label },\n { path, direction: 'desc', column, label },\n]\nconst SEND_SORTS = nameSorts('subjectLower', 'subject', 'Subject')\nconst CAMPAIGN_SORTS = nameSorts('nameLower', 'name', 'Campaign')\nconst millisOf = (value: any): number | null =>\n typeof value?.toMillis === 'function'\n ? value.toMillis()\n : typeof value?.seconds === 'number'\n ? value.seconds * 1000\n : typeof value === 'number'\n ? value\n : null\nconst SEND_PAGE_SORTS = {\n status: (row: Row) => String(row['status'] ?? 'draft'),\n audience: (row: Row) => (row['audience'] ? String(row['audience']) : null),\n when: (row: Row) => millisOf(row['sentAt']) ?? millisOf(row['sendAtMs']) ?? millisOf(row['createdAtMs']),\n}\nconst SEND_PAGE_SORT_HEADERS = { status: 'Status', audience: 'Audience', when: 'When' }\nconst CAMPAIGN_PAGE_SORTS = {\n visibleTo: (row: Row) => (Array.isArray(row['visibleTo']) ? row['visibleTo'].join(', ') : null),\n window: (row: Row) => millisOf(row['startAtMs']) ?? millisOf(row['endAtMs']),\n}\nconst CAMPAIGN_PAGE_SORT_HEADERS = { visibleTo: 'Runs on', window: 'Window' }\n\nfunction OrgCollectionTable(props: {\n orgId: string\n collectionId: string\n columns: GridColDef[]\n emptyLabel: string\n onOpen?: (row: Row) => void\n sorts: readonly ListQuerySort[]\n pageSorts: Readonly<Record<string, (row: Row) => string | number | null>>\n sortHeaders: Readonly<Record<string, string>>\n}) {\n const { orgId, collectionId, columns, emptyLabel, onOpen, sorts, pageSorts, sortHeaders } = props\n const firestore = useFirestore()\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(null)\n const paged = usePagedCollection<Row>(\n (pageLimit) =>\n orgId\n ? query(\n collection(firestore, 'orgs', orgId, collectionId),\n askedSort ? orderBy(askedSort.path, askedSort.direction) : orderBy(documentId()),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, collectionId, askedSort?.path, askedSort?.direction],\n { idField: '$id' },\n )\n const columnSort = useListColumnSort<Row>({\n sorts,\n sort: askedSort,\n onSortChange: setAskedSort,\n rows: paged.rows,\n pageSorts,\n headers: sortHeaders,\n })\n const byId = useMemo(() => new Map(paged.rows.map((row) => [row.$id, row])), [paged.rows])\n return (\n <Stack spacing={1}>\n <ListQueryNotices refused={[]} notices={columnSort.notices} />\n <ListTable\n rows={columnSort.rows}\n columns={columns}\n loading={paged.status === 'loading'}\n columnSort={columnSort}\n noRowsLabel={emptyLabel}\n onOpen={\n onOpen\n ? (id) => {\n const row = byId.get(String(id))\n if (row) onOpen(row)\n }\n : undefined\n }\n hideFooter\n rowHeight={TABLE_ROW_HEIGHT}\n />\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n )\n}\n\nconst chips = (items: Array<{ label: string; color?: 'success' | 'warning' | 'error' }>): ReactNode => (\n <Stack direction=\"row\" spacing={0.5} useFlexGap sx={{ alignItems: 'center', height: '100%', flexWrap: 'wrap' }}>\n {items.map((item) => (\n <Chip\n key={item.label}\n size=\"small\"\n label={item.label}\n color={item.color ?? 'default'}\n variant={item.color ? 'filled' : 'outlined'}\n />\n ))}\n </Stack>\n)\n\ntype EmailTab = 'sends' | 'campaigns'\n\n/**\n * The staff organization page's email card (`adminOrgDetail` zone): every\n * campaign send with its state and numbers, and the campaign containers\n * with the sites each runs on.\n */\nexport function StaffOrgEmailCard(props: { orgId: string }) {\n const { orgId } = props\n const [tab, setTab] = useState<EmailTab>('sends')\n const [message, setMessage] = useState<Row | null>(null)\n\n const sendColumns: GridColDef[] = useMemo(\n () => [\n {\n field: 'subject',\n headerName: 'Subject',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: Row) => String(row['subject'] || '(no subject)'),\n renderCell: ({ row }: { row: Row }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}>\n <Typography variant=\"body2\" noWrap sx={{ lineHeight: 1.25 }}>\n {String(row['subject'] || '(no subject)')}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace', lineHeight: 1.25 }}\n >\n {String(row['hostId'] ?? 'no site')}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 1.4,\n minWidth: 220,\n valueGetter: (_value, row: Row) => String(row['status'] ?? 'draft'),\n renderCell: ({ row }: { row: Row }) => {\n const stats = row['stats'] ?? {}\n return chips([\n {\n label: String(row['status'] ?? 'draft'),\n color: row['status'] === 'sent' ? 'success' : row['status'] === 'failed' ? 'error' : undefined,\n },\n ...(row['staffReview']?.state === 'held'\n ? [{ label: 'held for review', color: 'warning' as const }]\n : []),\n ...(typeof stats.sent === 'number' ? [{ label: `${stats.sent} sent` }] : []),\n ...(typeof stats.uniqueOpens === 'number' ? [{ label: `${stats.uniqueOpens} opened` }] : []),\n ...(typeof stats.uniqueClicks === 'number' ? [{ label: `${stats.uniqueClicks} clicked` }] : []),\n ...(stats.bounced ? [{ label: `${stats.bounced} bounced`, color: 'error' as const }] : []),\n ])\n },\n },\n {\n field: 'audience',\n headerName: 'Audience',\n flex: 0.6,\n minWidth: 110,\n valueGetter: (_value, row: Row) => String(row['audience'] ?? '—'),\n },\n {\n field: 'when',\n headerName: 'When',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: Row) => sendWhen(row),\n },\n listActionsColumn(\n (row: Row) => {\n const design = designPreviewHref(row)\n return (\n <ListRowActions\n label={String(row['subject'] || row.$id)}\n quick={{\n icon: mdiEyeOutline.path,\n label: 'View message',\n onClick: () => setMessage(row),\n unavailableReason: row['body'] ? undefined : 'No message body was stored',\n }}\n items={[\n {\n key: 'design',\n label: 'Open design preview',\n href: design,\n external: true,\n disabled: !design,\n disabledReason: design ? undefined : 'This send was not composed from a design',\n },\n {\n key: 'site',\n label: 'Open site',\n href: row['hostId']\n ? buildRoute(Route.ADMIN_SITE_DETAIL, { hostId: String(row['hostId']) })\n : undefined,\n disabled: !row['hostId'],\n disabledReason: row['hostId'] ? undefined : 'This send names no site',\n },\n ]}\n />\n )\n },\n { width: 110 },\n ),\n ],\n [],\n )\n\n const campaignColumns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Campaign',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: Row) => String(row['name'] || row.$id),\n },\n {\n field: 'visibleTo',\n headerName: 'Runs on',\n flex: 1.2,\n minWidth: 180,\n valueGetter: (_value, row: Row) =>\n (Array.isArray(row['visibleTo']) ? row['visibleTo'] : []).join(', '),\n renderCell: ({ row }: { row: Row }) =>\n chips([\n ...(Array.isArray(row['visibleTo']) ? (row['visibleTo'] as string[]) : []).map((token) => ({\n label: token === 'org' ? 'every site' : token.replace(/^host:/, ''),\n })),\n ...(row['deletedAt'] ? [{ label: 'deleted', color: 'error' as const }] : []),\n ]),\n },\n {\n field: 'window',\n headerName: 'Window',\n flex: 1,\n minWidth: 180,\n valueGetter: (_value, row: Row) =>\n row['startAtMs'] || row['endAtMs']\n ? `${whenOf(row['startAtMs'])} – ${row['endAtMs'] ? whenOf(row['endAtMs']) : 'open'}`\n : '—',\n },\n ],\n [],\n )\n\n return (\n <>\n <CardDisplay\n header={'Email campaigns'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-org-email',\n excerpt:\n \"The organization's campaign sends and campaigns. A send opens its stored message or its design preview.\",\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Tabs\n value={tab}\n onChange={(_event, next: EmailTab) => setTab(next)}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n allowScrollButtonsMobile\n >\n\n <Tab value=\"sends\" label=\"Sends\" />\n <Tab value=\"campaigns\" label=\"Campaigns\" />\n </Tabs>\n {tab === 'sends' ? (\n <OrgCollectionTable\n key=\"sends\"\n orgId={orgId}\n collectionId=\"campaigns\"\n columns={sendColumns}\n sorts={SEND_SORTS}\n pageSorts={SEND_PAGE_SORTS}\n sortHeaders={SEND_PAGE_SORT_HEADERS}\n emptyLabel=\"This organization has sent no campaigns\"\n onOpen={(row) => (row['body'] ? setMessage(row) : undefined)}\n />\n ) : (\n <OrgCollectionTable\n key=\"campaigns\"\n orgId={orgId}\n collectionId=\"emailCampaigns\"\n columns={campaignColumns}\n sorts={CAMPAIGN_SORTS}\n pageSorts={CAMPAIGN_PAGE_SORTS}\n sortHeaders={CAMPAIGN_PAGE_SORT_HEADERS}\n emptyLabel=\"This organization has no email campaigns\"\n />\n )}\n </Stack>\n </CardDisplay>\n <MessageDialog row={message} onClose={() => setMessage(null)} />\n </>\n )\n}\n"],"names":["buildRoute","Route","pluginDocsHelp","mdiEyeOutline","CardDisplay","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","ListQueryNotices","useListColumnSort","useFirestore","usePagedCollection","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Tab","Tabs","Typography","collection","documentId","limit","orderBy","query","useMemo","useState","whenOf","value","toDate","toLocaleString","seconds","Date","sendWhen","row","designPreviewHref","ADMIN_SITE_PREVIEW","hostId","kind","docId","undefined","MessageDialog","props","onClose","open","Boolean","maxWidth","fullWidth","String","$id","variant","color","component","title","sandbox","srcDoc","sx","mt","width","height","border","borderColor","bgcolor","onClick","nameSorts","path","column","label","direction","SEND_SORTS","CAMPAIGN_SORTS","millisOf","toMillis","SEND_PAGE_SORTS","status","audience","when","SEND_PAGE_SORT_HEADERS","CAMPAIGN_PAGE_SORTS","visibleTo","Array","isArray","join","window","CAMPAIGN_PAGE_SORT_HEADERS","OrgCollectionTable","orgId","collectionId","columns","emptyLabel","onOpen","sorts","pageSorts","sortHeaders","firestore","askedSort","setAskedSort","paged","pageLimit","idField","columnSort","sort","onSortChange","rows","headers","byId","Map","map","spacing","refused","notices","loading","noRowsLabel","id","get","hideFooter","rowHeight","page","pageSize","rowCount","length","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","chips","items","useFlexGap","alignItems","flexWrap","item","size","StaffOrgEmailCard","tab","setTab","message","setMessage","sendColumns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","justifyContent","lineHeight","noWrap","fontFamily","stats","state","sent","uniqueOpens","uniqueClicks","bounced","design","quick","icon","unavailableReason","key","href","external","disabled","disabledReason","ADMIN_SITE_DETAIL","campaignColumns","token","replace","header","help","anchor","excerpt","contentGutterX","contentGutterY","onChange","_event","next","scrollButtons","allowScrollButtonsMobile"],"mappings":"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"}
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { CampaignCaveat } from './campaign-report';
18
+ import type { ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan';
18
19
  /**
19
20
  * WHAT A CAMPAIGN CAUSED — the read half of the identify-moment join.
20
21
  *
@@ -350,3 +351,4 @@ export declare function campaignConversionsCoverage(options: {
350
351
  * one of them invites the reader to look for a campaign called "(none)".
351
352
  */
352
353
  export declare function campaignTouchLabel(record: CampaignConversionRecord | null | undefined): string;
354
+ export declare const CAMPAIGN_CONVERSION_SORTS: readonly ListQuerySort[];