@aglyn/plugins-crm 1.0.0-beta.200 → 1.0.0-beta.202

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 (78) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/activity-list.d.ts +36 -5
  3. package/src/lib/components/activity-list.js +159 -137
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/activity-timeline.d.ts +84 -0
  6. package/src/lib/components/activity-timeline.js +224 -0
  7. package/src/lib/components/activity-timeline.js.map +1 -0
  8. package/src/lib/components/company-properties-card.js +53 -36
  9. package/src/lib/components/company-properties-card.js.map +1 -1
  10. package/src/lib/components/contact-associations-card.d.ts +3 -2
  11. package/src/lib/components/contact-associations-card.js +12 -12
  12. package/src/lib/components/contact-associations-card.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -3
  14. package/src/lib/components/contact-detail-page.js +4 -10
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-properties-card.d.ts +21 -4
  17. package/src/lib/components/contact-properties-card.js +129 -29
  18. package/src/lib/components/contact-properties-card.js.map +1 -1
  19. package/src/lib/components/contact-timeline-card.d.ts +12 -6
  20. package/src/lib/components/contact-timeline-card.js +132 -174
  21. package/src/lib/components/contact-timeline-card.js.map +1 -1
  22. package/src/lib/components/contacts-section.js +197 -198
  23. package/src/lib/components/contacts-section.js.map +1 -1
  24. package/src/lib/components/crm-email-state-chip.js +4 -1
  25. package/src/lib/components/crm-email-state-chip.js.map +1 -1
  26. package/src/lib/components/deal-products-card.js +15 -13
  27. package/src/lib/components/deal-products-card.js.map +1 -1
  28. package/src/lib/components/deal-properties-card.js +7 -0
  29. package/src/lib/components/deal-properties-card.js.map +1 -1
  30. package/src/lib/components/deal-stage-card.js +31 -29
  31. package/src/lib/components/deal-stage-card.js.map +1 -1
  32. package/src/lib/components/deals-section.js +9 -14
  33. package/src/lib/components/deals-section.js.map +1 -1
  34. package/src/lib/components/fields-section.d.ts +4 -0
  35. package/src/lib/components/fields-section.js +368 -154
  36. package/src/lib/components/fields-section.js.map +1 -1
  37. package/src/lib/components/form-contact-fields-card.js +24 -20
  38. package/src/lib/components/form-contact-fields-card.js.map +1 -1
  39. package/src/lib/components/lead-campaigns-card.d.ts +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +32 -35
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-properties-card.d.ts +14 -6
  43. package/src/lib/components/lead-properties-card.js +317 -313
  44. package/src/lib/components/lead-properties-card.js.map +1 -1
  45. package/src/lib/components/lead-surfaces-note.js +13 -2
  46. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  47. package/src/lib/components/linked-deals-card.js +12 -10
  48. package/src/lib/components/linked-deals-card.js.map +1 -1
  49. package/src/lib/components/recent-activity-feed.d.ts +12 -8
  50. package/src/lib/components/recent-activity-feed.js +54 -29
  51. package/src/lib/components/recent-activity-feed.js.map +1 -1
  52. package/src/lib/components/record-activity-card.d.ts +4 -2
  53. package/src/lib/components/record-activity-card.js +29 -11
  54. package/src/lib/components/record-activity-card.js.map +1 -1
  55. package/src/lib/components/reports/activity-card.js +18 -12
  56. package/src/lib/components/reports/activity-card.js.map +1 -1
  57. package/src/lib/components/reports/lead-funnel-card.js +47 -37
  58. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  59. package/src/lib/components/reports/pipeline-card.js +22 -16
  60. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  61. package/src/lib/components/reports/report-export.d.ts +4 -11
  62. package/src/lib/components/reports/report-export.js +16 -33
  63. package/src/lib/components/reports/report-export.js.map +1 -1
  64. package/src/lib/components/reports/source-conversion-card.js +18 -12
  65. package/src/lib/components/reports/source-conversion-card.js.map +1 -1
  66. package/src/lib/components/reports/tasks-card.js +35 -25
  67. package/src/lib/components/reports/tasks-card.js.map +1 -1
  68. package/src/lib/components/tasks-section.js +23 -23
  69. package/src/lib/components/tasks-section.js.map +1 -1
  70. package/src/lib/constants/field-list-query.d.ts +23 -0
  71. package/src/lib/constants/field-list-query.js +80 -0
  72. package/src/lib/constants/field-list-query.js.map +1 -0
  73. package/src/lib/model/lead-filters.d.ts +1 -1
  74. package/src/lib/server/capture-contact.js +7 -5
  75. package/src/lib/server/capture-contact.js.map +1 -1
  76. package/src/lib/components/contact-custom-fields-card.d.ts +0 -39
  77. package/src/lib/components/contact-custom-fields-card.js +0 -204
  78. package/src/lib/components/contact-custom-fields-card.js.map +0 -1
@@ -154,17 +154,36 @@ import { useAggregateRead, useWindowRead } from "./use-aggregate-read.js";
154
154
  contentGutterX: true,
155
155
  contentGutterY: true,
156
156
  HeaderProps: {
157
- action: /*#__PURE__*/ _jsx(Button, _extends({
158
- component: AppLink
159
- }, {
160
- componentVariant: 'naked',
161
- nativeButton: false
162
- }, {
163
- href: routes.section('tasks'),
164
- size: "small",
165
- color: "primary",
166
- children: 'Open tasks'
167
- }))
157
+ action: /*#__PURE__*/ _jsxs(Stack, {
158
+ direction: "row",
159
+ spacing: 1,
160
+ children: [
161
+ /*#__PURE__*/ _jsx(ReportExport, {
162
+ filename: reportFilename('tasks-by-assignee'),
163
+ columns: ASSIGNEE_COLUMNS,
164
+ rows: ()=>load.map((row)=>[
165
+ assigneeName(row),
166
+ row.overdue,
167
+ row.today,
168
+ row.upcoming,
169
+ row.undated,
170
+ row.open
171
+ ]),
172
+ disabled: tasksStatus !== 'success' || !load.length
173
+ }),
174
+ /*#__PURE__*/ _jsx(Button, _extends({
175
+ component: AppLink
176
+ }, {
177
+ componentVariant: 'naked',
178
+ nativeButton: false
179
+ }, {
180
+ href: routes.section('tasks'),
181
+ size: "small",
182
+ color: "primary",
183
+ children: 'Open tasks'
184
+ }))
185
+ ]
186
+ })
168
187
  },
169
188
  children: /*#__PURE__*/ _jsxs(Stack, {
170
189
  spacing: 2,
@@ -253,20 +272,11 @@ import { useAggregateRead, useWindowRead } from "./use-aggregate-read.js";
253
272
  color: "text.secondary",
254
273
  children: tasksStatus === 'loading' ? 'Reading…' : 'No open tasks.'
255
274
  }),
256
- /*#__PURE__*/ _jsx(ReportExport, {
257
- filename: reportFilename('tasks-by-assignee'),
258
- columns: ASSIGNEE_COLUMNS,
259
- rows: ()=>load.map((row)=>[
260
- assigneeName(row),
261
- row.overdue,
262
- row.today,
263
- row.upcoming,
264
- row.undated,
265
- row.open
266
- ]),
267
- disabled: tasksStatus !== 'success' || !load.length,
268
- caption: taskWindow.truncated ? `Grouped from the ${OPEN_TASK_CEILING.toLocaleString()} soonest-due open tasks; the tiles are counted on the server.` : undefined
269
- })
275
+ taskWindow.truncated ? /*#__PURE__*/ _jsx(Typography, {
276
+ variant: "caption",
277
+ color: "text.secondary",
278
+ children: `Grouped from the ${OPEN_TASK_CEILING.toLocaleString()} soonest-due open tasks; the tiles are counted on the server.`
279
+ }) : null
270
280
  ]
271
281
  })
272
282
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/tasks-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Section } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Button,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n documentId,\n getCountFromServer,\n getDocs,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ReportExport } from './report-export'\nimport { reportFilename } from './report-format'\nimport {\n type CrmReportScope,\n reportCacheKey,\n scopedCollection,\n visibleToClause,\n} from './report-scope'\nimport { ReportStatTile } from './report-stat-tile'\nimport { useAggregateRead, useWindowRead } from './use-aggregate-read'\n\n/**\n * How many open tasks the by-assignee table is grouped from.\n *\n * The tiles are counts the server takes; the table needs each task's\n * assignee and due state, so it reads the soonest-due thousand and says\n * when there were more.\n */\nconst OPEN_TASK_CEILING = 1000\n/** One `in` query's worth of member documents, for the assignee names. */\nconst ASSIGNEE_NAME_CEILING = 30\n\ntype TaskRow = Aglyn.CrmTask & { $id: string }\n\n/** The by-assignee table's columns, which are also the CSV's. */\nconst ASSIGNEE_COLUMNS = ['Assignee', 'Overdue', 'Today', 'Upcoming', 'No date', 'Open'] as const\n\ninterface AssigneeLoad {\n uid: string\n overdue: number\n today: number\n upcoming: number\n undated: number\n open: number\n}\n\nexport interface TasksCardProps {\n report: CrmReportScope\n}\n\n/**\n * The task load: open, overdue and due today, and who is carrying what\n * (AGL-2604).\n *\n * \"Today\" and \"overdue\" are decided on the LOCAL calendar day, the same day\n * `taskDueState` decides them on, so the count here and the chip on the\n * tasks list agree about the same task at 11 p.m. The counts are range\n * queries on `dueAtMs`, which the `(visibleTo, status, dueAtMs)` index\n * serves; a task with no due date is in the open count and in none of the\n * dated ones.\n *\n * The table reads open tasks ordered by due date. An ordered read admits\n * only documents that CARRY the field, so a task written without `dueAtMs`\n * at all — rather than with `null`, which sorts first — is counted in the\n * tile and absent from the table. The model types the field as optional,\n * so the table says what it is grouped from.\n */\nexport function TasksCard(props: TasksCardProps) {\n const { report } = props\n const { scope, tokens, nowMs, routes } = report\n const firestore = useFirestore()\n const day = useMemo(() => Aglyn.localDayBounds(nowMs), [nowMs])\n\n const openTasks = () =>\n query(\n scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n )\n\n const counts = useAggregateRead(\n () =>\n Promise.all([\n getCountFromServer(openTasks()),\n getCountFromServer(query(openTasks(), where('dueAtMs', '<', day.start))),\n getCountFromServer(\n query(\n openTasks(),\n where('dueAtMs', '>=', day.start),\n where('dueAtMs', '<', day.end),\n ),\n ),\n ]).then(([open, overdue, today]) => ({\n open: open.data().count,\n overdue: overdue.data().count,\n today: today.data().count,\n })),\n [firestore, scope, tokens, day],\n { cacheKey: reportCacheKey(report, 'tasks:counts') },\n )\n\n const taskWindow = useWindowRead<TaskRow>(\n () =>\n query(\n openTasks(),\n orderBy('dueAtMs', 'asc'),\n limit(OPEN_TASK_CEILING + 1),\n ),\n OPEN_TASK_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'tasks:open') },\n )\n const tasksStatus = taskWindow.status\n\n const load = useMemo(() => {\n const byAssignee = new Map<string, AssigneeLoad>()\n for (const task of taskWindow.rows) {\n const uid = task.assigneeUid || ''\n const row = byAssignee.get(uid) ?? {\n uid,\n overdue: 0,\n today: 0,\n upcoming: 0,\n undated: 0,\n open: 0,\n }\n const state = Aglyn.taskDueState(task, nowMs)\n if (state === 'overdue') row.overdue += 1\n else if (state === 'today') row.today += 1\n else if (state === 'upcoming') row.upcoming += 1\n else row.undated += 1\n row.open += 1\n byAssignee.set(uid, row)\n }\n return [...byAssignee.values()].sort(\n (a, b) => b.open - a.open || a.uid.localeCompare(b.uid),\n )\n }, [taskWindow, nowMs])\n\n /*\n * The assignees' names, from their member documents — one `in` query for\n * up to thirty, which is Firestore's cap on the operator and more assignees\n * than a tasks list has. Anyone past it, or a member document the reader\n * cannot open, shows as their id rather than blocking the table.\n */\n const uids = load.map((row) => row.uid).filter(Boolean).slice(0, ASSIGNEE_NAME_CEILING)\n const uidKey = uids.join(',')\n const names = useAggregateRead<Record<string, string>>(\n () =>\n uids.length\n ? getDocs(\n query(\n scopedCollection(firestore, scope, 'members'),\n where(documentId(), 'in', uids),\n ),\n ).then((snapshot) =>\n Object.fromEntries(\n snapshot.docs.map((member) => [\n member.id,\n String(member.get('displayName') || member.get('email') || member.id),\n ]),\n ),\n )\n : Promise.resolve({}),\n [firestore, scope, uidKey],\n { cacheKey: reportCacheKey(report, `tasks:names:${uidKey}`) },\n )\n\n const figures = counts.value\n const assigneeName = (row: AssigneeLoad): string =>\n row.uid ? names.value?.[row.uid] ?? row.uid : 'Unassigned'\n return (\n <CardDisplay\n header={'Tasks'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#tasks',\n excerpt:\n 'Open tasks, how many are overdue or due today, and the open ' +\n 'load per assignee. Today and overdue are decided on your calendar ' +\n 'day, the same way the tasks list decides them.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.section('tasks')}\n size=\"small\"\n color=\"primary\"\n >\n {'Open tasks'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Open tasks'}\n value={figures ? figures.open.toLocaleString() : null}\n note={'counted on the server'}\n href={routes.section('tasks')}\n />\n <ReportStatTile\n label={'Overdue'}\n value={figures ? figures.overdue.toLocaleString() : null}\n note={'due before today'}\n color={figures?.overdue ? 'error.main' : undefined}\n href={routes.section('tasks')}\n />\n <ReportStatTile\n label={'Due today'}\n value={figures ? figures.today.toLocaleString() : null}\n note={'on your calendar day'}\n href={routes.section('tasks')}\n />\n </Stack>\n {counts.status === 'error' ? (\n <Alert severity=\"warning\">{'The task counts could not be read.'}</Alert>\n ) : null}\n <Section title={'Open tasks by assignee'}>\n {load.length ? (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n {ASSIGNEE_COLUMNS.map((column, index) => (\n <TableCell key={column} align={index ? 'right' : 'left'}>\n {column}\n </TableCell>\n ))}\n </TableRow>\n </TableHead>\n <TableBody>\n {load.map((row) => (\n <TableRow key={row.uid || '$unassigned'}>\n <TableCell>{assigneeName(row)}</TableCell>\n <TableCell\n align=\"right\"\n sx={row.overdue ? { color: 'error.main' } : undefined}\n >\n {row.overdue.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{row.today.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.upcoming.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.undated.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.open.toLocaleString()}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {tasksStatus === 'loading' ? 'Reading…' : 'No open tasks.'}\n </Typography>\n )}\n <ReportExport\n filename={reportFilename('tasks-by-assignee')}\n columns={ASSIGNEE_COLUMNS}\n rows={() =>\n load.map((row) => [\n assigneeName(row),\n row.overdue,\n row.today,\n row.upcoming,\n row.undated,\n row.open,\n ])\n }\n disabled={tasksStatus !== 'success' || !load.length}\n caption={\n taskWindow.truncated\n ? `Grouped from the ${OPEN_TASK_CEILING.toLocaleString()} soonest-due open tasks; the tiles are counted on the server.`\n : undefined\n }\n />\n </Section>\n </Stack>\n </CardDisplay>\n )\n}\nTasksCard.displayName = 'TasksCard'\n\nexport default TasksCard\n"],"names":["Aglyn","AppLink","CardDisplay","Section","ScrollTable","Alert","Button","Stack","TableBody","TableCell","TableHead","TableRow","Typography","documentId","getCountFromServer","getDocs","limit","orderBy","query","where","useMemo","useFirestore","ReportExport","reportFilename","reportCacheKey","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","useWindowRead","OPEN_TASK_CEILING","ASSIGNEE_NAME_CEILING","ASSIGNEE_COLUMNS","TasksCard","props","report","scope","tokens","nowMs","routes","firestore","day","localDayBounds","openTasks","CRM_COLLECTIONS","tasks","counts","Promise","all","start","end","then","open","overdue","today","data","count","cacheKey","taskWindow","tasksStatus","status","load","byAssignee","Map","task","rows","uid","assigneeUid","row","get","upcoming","undated","state","taskDueState","set","values","sort","a","b","localeCompare","uids","map","filter","Boolean","slice","uidKey","join","names","length","snapshot","Object","fromEntries","docs","member","id","String","resolve","figures","value","assigneeName","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","section","size","color","spacing","direction","sx","flexWrap","label","toLocaleString","note","undefined","severity","title","column","index","align","variant","filename","columns","disabled","caption","truncated","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/components/reports/tasks-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Section } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Alert,\n Button,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n documentId,\n getCountFromServer,\n getDocs,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ReportExport } from './report-export'\nimport { reportFilename } from './report-format'\nimport {\n type CrmReportScope,\n reportCacheKey,\n scopedCollection,\n visibleToClause,\n} from './report-scope'\nimport { ReportStatTile } from './report-stat-tile'\nimport { useAggregateRead, useWindowRead } from './use-aggregate-read'\n\n/**\n * How many open tasks the by-assignee table is grouped from.\n *\n * The tiles are counts the server takes; the table needs each task's\n * assignee and due state, so it reads the soonest-due thousand and says\n * when there were more.\n */\nconst OPEN_TASK_CEILING = 1000\n/** One `in` query's worth of member documents, for the assignee names. */\nconst ASSIGNEE_NAME_CEILING = 30\n\ntype TaskRow = Aglyn.CrmTask & { $id: string }\n\n/** The by-assignee table's columns, which are also the CSV's. */\nconst ASSIGNEE_COLUMNS = ['Assignee', 'Overdue', 'Today', 'Upcoming', 'No date', 'Open'] as const\n\ninterface AssigneeLoad {\n uid: string\n overdue: number\n today: number\n upcoming: number\n undated: number\n open: number\n}\n\nexport interface TasksCardProps {\n report: CrmReportScope\n}\n\n/**\n * The task load: open, overdue and due today, and who is carrying what\n * (AGL-2604).\n *\n * \"Today\" and \"overdue\" are decided on the LOCAL calendar day, the same day\n * `taskDueState` decides them on, so the count here and the chip on the\n * tasks list agree about the same task at 11 p.m. The counts are range\n * queries on `dueAtMs`, which the `(visibleTo, status, dueAtMs)` index\n * serves; a task with no due date is in the open count and in none of the\n * dated ones.\n *\n * The table reads open tasks ordered by due date. An ordered read admits\n * only documents that CARRY the field, so a task written without `dueAtMs`\n * at all — rather than with `null`, which sorts first — is counted in the\n * tile and absent from the table. The model types the field as optional,\n * so the table says what it is grouped from.\n */\nexport function TasksCard(props: TasksCardProps) {\n const { report } = props\n const { scope, tokens, nowMs, routes } = report\n const firestore = useFirestore()\n const day = useMemo(() => Aglyn.localDayBounds(nowMs), [nowMs])\n\n const openTasks = () =>\n query(\n scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks),\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n )\n\n const counts = useAggregateRead(\n () =>\n Promise.all([\n getCountFromServer(openTasks()),\n getCountFromServer(query(openTasks(), where('dueAtMs', '<', day.start))),\n getCountFromServer(\n query(\n openTasks(),\n where('dueAtMs', '>=', day.start),\n where('dueAtMs', '<', day.end),\n ),\n ),\n ]).then(([open, overdue, today]) => ({\n open: open.data().count,\n overdue: overdue.data().count,\n today: today.data().count,\n })),\n [firestore, scope, tokens, day],\n { cacheKey: reportCacheKey(report, 'tasks:counts') },\n )\n\n const taskWindow = useWindowRead<TaskRow>(\n () =>\n query(\n openTasks(),\n orderBy('dueAtMs', 'asc'),\n limit(OPEN_TASK_CEILING + 1),\n ),\n OPEN_TASK_CEILING,\n [firestore, scope, tokens, nowMs],\n { cacheKey: reportCacheKey(report, 'tasks:open') },\n )\n const tasksStatus = taskWindow.status\n\n const load = useMemo(() => {\n const byAssignee = new Map<string, AssigneeLoad>()\n for (const task of taskWindow.rows) {\n const uid = task.assigneeUid || ''\n const row = byAssignee.get(uid) ?? {\n uid,\n overdue: 0,\n today: 0,\n upcoming: 0,\n undated: 0,\n open: 0,\n }\n const state = Aglyn.taskDueState(task, nowMs)\n if (state === 'overdue') row.overdue += 1\n else if (state === 'today') row.today += 1\n else if (state === 'upcoming') row.upcoming += 1\n else row.undated += 1\n row.open += 1\n byAssignee.set(uid, row)\n }\n return [...byAssignee.values()].sort(\n (a, b) => b.open - a.open || a.uid.localeCompare(b.uid),\n )\n }, [taskWindow, nowMs])\n\n /*\n * The assignees' names, from their member documents — one `in` query for\n * up to thirty, which is Firestore's cap on the operator and more assignees\n * than a tasks list has. Anyone past it, or a member document the reader\n * cannot open, shows as their id rather than blocking the table.\n */\n const uids = load.map((row) => row.uid).filter(Boolean).slice(0, ASSIGNEE_NAME_CEILING)\n const uidKey = uids.join(',')\n const names = useAggregateRead<Record<string, string>>(\n () =>\n uids.length\n ? getDocs(\n query(\n scopedCollection(firestore, scope, 'members'),\n where(documentId(), 'in', uids),\n ),\n ).then((snapshot) =>\n Object.fromEntries(\n snapshot.docs.map((member) => [\n member.id,\n String(member.get('displayName') || member.get('email') || member.id),\n ]),\n ),\n )\n : Promise.resolve({}),\n [firestore, scope, uidKey],\n { cacheKey: reportCacheKey(report, `tasks:names:${uidKey}`) },\n )\n\n const figures = counts.value\n const assigneeName = (row: AssigneeLoad): string =>\n row.uid ? names.value?.[row.uid] ?? row.uid : 'Unassigned'\n return (\n <CardDisplay\n header={'Tasks'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#tasks',\n excerpt:\n 'Open tasks, how many are overdue or due today, and the open ' +\n 'load per assignee. Today and overdue are decided on your calendar ' +\n 'day, the same way the tasks list decides them.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n <ReportExport\n filename={reportFilename('tasks-by-assignee')}\n columns={ASSIGNEE_COLUMNS}\n rows={() =>\n load.map((row) => [\n assigneeName(row),\n row.overdue,\n row.today,\n row.upcoming,\n row.undated,\n row.open,\n ])\n }\n disabled={tasksStatus !== 'success' || !load.length}\n />\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={routes.section('tasks')}\n size=\"small\"\n color=\"primary\"\n >\n {'Open tasks'}\n </Button>\n </Stack>\n ),\n }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Open tasks'}\n value={figures ? figures.open.toLocaleString() : null}\n note={'counted on the server'}\n href={routes.section('tasks')}\n />\n <ReportStatTile\n label={'Overdue'}\n value={figures ? figures.overdue.toLocaleString() : null}\n note={'due before today'}\n color={figures?.overdue ? 'error.main' : undefined}\n href={routes.section('tasks')}\n />\n <ReportStatTile\n label={'Due today'}\n value={figures ? figures.today.toLocaleString() : null}\n note={'on your calendar day'}\n href={routes.section('tasks')}\n />\n </Stack>\n {counts.status === 'error' ? (\n <Alert severity=\"warning\">{'The task counts could not be read.'}</Alert>\n ) : null}\n <Section title={'Open tasks by assignee'}>\n {load.length ? (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n {ASSIGNEE_COLUMNS.map((column, index) => (\n <TableCell key={column} align={index ? 'right' : 'left'}>\n {column}\n </TableCell>\n ))}\n </TableRow>\n </TableHead>\n <TableBody>\n {load.map((row) => (\n <TableRow key={row.uid || '$unassigned'}>\n <TableCell>{assigneeName(row)}</TableCell>\n <TableCell\n align=\"right\"\n sx={row.overdue ? { color: 'error.main' } : undefined}\n >\n {row.overdue.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{row.today.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.upcoming.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.undated.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.open.toLocaleString()}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {tasksStatus === 'loading' ? 'Reading…' : 'No open tasks.'}\n </Typography>\n )}\n {/* What the table, and so the export, is grouped from when that is not everything. */}\n {taskWindow.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Grouped from the ${OPEN_TASK_CEILING.toLocaleString()} soonest-due open tasks; the tiles are counted on the server.`}\n </Typography>\n ) : null}\n </Section>\n </Stack>\n </CardDisplay>\n )\n}\nTasksCard.displayName = 'TasksCard'\n\nexport default TasksCard\n"],"names":["Aglyn","AppLink","CardDisplay","Section","ScrollTable","Alert","Button","Stack","TableBody","TableCell","TableHead","TableRow","Typography","documentId","getCountFromServer","getDocs","limit","orderBy","query","where","useMemo","useFirestore","ReportExport","reportFilename","reportCacheKey","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","useWindowRead","OPEN_TASK_CEILING","ASSIGNEE_NAME_CEILING","ASSIGNEE_COLUMNS","TasksCard","props","report","scope","tokens","nowMs","routes","firestore","day","localDayBounds","openTasks","CRM_COLLECTIONS","tasks","counts","Promise","all","start","end","then","open","overdue","today","data","count","cacheKey","taskWindow","tasksStatus","status","load","byAssignee","Map","task","rows","uid","assigneeUid","row","get","upcoming","undated","state","taskDueState","set","values","sort","a","b","localeCompare","uids","map","filter","Boolean","slice","uidKey","join","names","length","snapshot","Object","fromEntries","docs","member","id","String","resolve","figures","value","assigneeName","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","direction","spacing","filename","columns","disabled","component","componentVariant","nativeButton","href","section","size","color","sx","flexWrap","label","toLocaleString","note","undefined","severity","title","column","index","align","variant","truncated","displayName"],"mappings":"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"}
@@ -45,7 +45,7 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
45
45
  import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
46
46
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
47
47
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
48
- import { CrmListToolbar } from "./crm-list-toolbar.js";
48
+ import { CrmListActions, CrmListToolbar } from "./crm-list-toolbar.js";
49
49
  import { TaskDueText, TaskKindCell, TaskPriorityChip, TaskRecordLink } from "./task-cells.js";
50
50
  import TaskEditDrawer from "./task-edit-drawer.js";
51
51
  import TasksCalendar from "./tasks-calendar.js";
@@ -510,15 +510,29 @@ const EMPTY_LABEL = {
510
510
  contentGutterX: true,
511
511
  contentGutterY: true,
512
512
  HeaderProps: {
513
- action: /*#__PURE__*/ _jsx(Button, {
514
- size: "small",
515
- variant: "contained",
516
- color: "primary",
517
- onClick: ()=>setDrawer({
518
- open: true,
519
- task: null
513
+ // The record actions, top right and never clipped (AGL-3311).
514
+ action: /*#__PURE__*/ _jsxs(CrmListActions, {
515
+ children: [
516
+ /*#__PURE__*/ _jsx(TaskImportButton, {
517
+ hostId: hostId
518
+ }),
519
+ /*#__PURE__*/ _jsx(Button, {
520
+ size: "small",
521
+ onClick: handleExport,
522
+ disabled: !tasks.length,
523
+ children: 'Export CSV'
520
524
  }),
521
- children: 'New task'
525
+ /*#__PURE__*/ _jsx(Button, {
526
+ size: "small",
527
+ variant: "contained",
528
+ color: "primary",
529
+ onClick: ()=>setDrawer({
530
+ open: true,
531
+ task: null
532
+ }),
533
+ children: 'New task'
534
+ })
535
+ ]
522
536
  })
523
537
  },
524
538
  children: /*#__PURE__*/ _jsxs(Stack, {
@@ -558,20 +572,6 @@ const EMPTY_LABEL = {
558
572
  children: 'Calendar'
559
573
  })
560
574
  ]
561
- }),
562
- /*#__PURE__*/ _jsx(Stack, {
563
- sx: {
564
- flex: 1
565
- }
566
- }),
567
- /*#__PURE__*/ _jsx(TaskImportButton, {
568
- hostId: hostId
569
- }),
570
- /*#__PURE__*/ _jsx(Button, {
571
- size: "small",
572
- onClick: handleExport,
573
- disabled: !tasks.length,
574
- children: 'Export CSV'
575
575
  })
576
576
  ]
577
577
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n CRM_COLLECTIONS,\n type CrmViewFilterClause,\n findOrgMember,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport CrmViewsControl from './crm-views-control'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n Stack,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmTaskRow, useCrmTaskList, useNowMs } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n TASK_LIST_QUERY_FIELDS,\n} from '../model/task-views'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { CrmListToolbar } from './crm-list-toolbar'\nimport {\n TaskDueText,\n TaskKindCell,\n TaskPriorityChip,\n TaskRecordLink,\n} from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TasksCalendar from './tasks-calendar'\nimport { TaskImportButton } from './task-import-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\nimport TasksBulkBar from './tasks-bulk-bar'\n\n/** What an empty view is headed, by view — \"nothing overdue\" is good news. */\n/*\n * What the tasks grid's panel offers (AGL-3313). `view` is the task view\n * the query's base answers — a hidden column, since it is no field of a\n * task — and Kind, Priority and Assignee are predicates on the same query\n * (AGL-3321).\n */\nconst TASK_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'view', kind: 'exact', path: 'view', operators: ['equals'] },\n ...TASK_LIST_QUERY_FIELDS,\n]\nconst TASK_FILTER_HEADERS: Readonly<Record<string, string>> = {\n view: 'Show',\n kind: 'Kind',\n priority: 'Priority',\n assigneeUid: 'Assignee',\n}\n/** The filter-only `view` column never shows. */\nconst TASK_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { view: false }\n/** How many tasks the month view reads: one page, sized for a month of work. */\nconst CALENDAR_PAGE_SIZE = 200\n/** No `view` clause is \"My tasks\", which the section opened on before views existed. */\nconst TASK_VIEW_DEFAULT: CrmViewFilterClause = { field: 'view', op: 'equals', value: 'mine' }\nconst EMPTY_LABEL: Record<CrmTaskView, string> = {\n mine: 'Nothing is assigned to you',\n overdue: 'Nothing is overdue',\n today: 'Nothing is due today',\n upcoming: 'Nothing is scheduled beyond today',\n open: 'No open tasks',\n done: 'No completed tasks yet',\n}\n\n/** The one sentence under it, by view. */\nconst EMPTY_COPY: Record<CrmTaskView, string> = {\n mine: 'Tasks you create are yours unless you hand them to someone.',\n overdue: 'Every dated task is on or ahead of schedule.',\n today: 'Nothing on your calendar day is owed to anyone in the CRM.',\n upcoming: 'A task with a due date past today would be listed here.',\n open: 'A task is a call, an email, a meeting or a to-do owed to a contact, a company or a deal.',\n done: 'Tasks ticked off on their record, or here, are kept for the history.',\n}\n\n/**\n * `/crm/tasks` — what the team owes the people in the CRM (AGL-2599).\n *\n * Six views over `orgs/{orgId}/crmTasks`, each a bounded listener on one of\n * the collection's indexes, chosen by a segmented control. A task hangs off\n * a contact, a company or a deal, but it is listed HERE because \"what is due\n * today\" is a question about the team's day and not about any one record;\n * the record pages carry their own `RecordTasksCard` for the other\n * direction.\n *\n * Completing goes through `crm/task-complete`, because completing fires the\n * `taskCompleted` host event and only the server can run a workflow.\n * Reopening is client-direct: undoing a tick has no side effect beyond the\n * document, and the rules gate it like any other CRM write.\n *\n * The rows are selectable, and a selection raises `TasksBulkBar` over the\n * list — Complete and Assign through their routes, the due date and the\n * delete as batched writes (AGL-2621). Export CSV beside the view control\n * writes the view through `tasksCsv()`, naming the assignee and the linked\n * records; the bar writes the same file over the selection.\n */\nexport function TasksSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath = '' } = props\n const orgRecord = org as Record<string, unknown> | undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const routes = useMemo(() => crmRoutes(basePath), [basePath])\n // The viewing group a linked contact is named through, or `null` at the\n // organization level, where each is named through its own holder.\n const { consentGroup } = useCrmScope({ hostId, org: orgRecord })\n const groupId = consentGroup?.groupId ?? null\n\n /*\n * Which of the six task views is open is the saved VIEW'S (AGL-2617): a\n * saved view of tasks holds the task view beside the columns and the\n * sort, and the toggle below writes into it. Unset reads as \"My tasks\",\n * which is what the section opened on before views existed.\n */\n const views = useCrmSavedView({ section: 'tasks', hostId, org: orgRecord, basePath })\n const view: CrmTaskView = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'view')?.value\n return CRM_TASK_VIEWS.some((option) => option.id === value)\n ? (value as CrmTaskView)\n : 'mine'\n }, [views.state.filters])\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313). \"Show\" is the task view the query's base answers — the\n * hidden `view` column, as the toggle stored it, with none meaning \"My\n * tasks\" — and Kind, Priority, Assignee and the search word are on the\n * same query (AGL-3321).\n */\n const clauses = useMemo(\n () =>\n views.state.filters.some((clause) => clause.field === 'view')\n ? views.state.filters\n : [TASK_VIEW_DEFAULT, ...views.state.filters],\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) =>\n views.setFilters(\n next.filter(\n (clause) => !(clause.field === 'view' && clause.value === TASK_VIEW_DEFAULT.value),\n ),\n ),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['view', 'kind', 'priority', 'assigneeUid'],\n })\n const queryClauses = useMemo(\n () => clauses.filter((clause) => clause.field !== 'view'),\n [clauses],\n )\n const list = useCrmTaskList({\n hostId,\n org: orgRecord,\n view,\n uid: user?.uid,\n nowMs,\n clauses: queryClauses,\n search: gridFilter.searchWords,\n })\n const { tasks, status, fromCache, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\n const filterOptions = useMemo(\n () => ({\n view: CRM_TASK_VIEWS.map((option) => ({ value: option.id, label: option.label })),\n kind: CRM_TASK_KINDS.map((kind) => ({ value: kind, label: CRM_TASK_KIND_LABELS[kind] })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: CRM_TASK_PRIORITY_LABELS[priority],\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members],\n )\n // What the query could not hold, said above the list rather than matched in memory.\n const refused = useMemo(\n () =>\n listQueryRefusals(list.plan.refused, {\n fields: TASK_FILTER_FIELDS,\n headers: TASK_FILTER_HEADERS,\n options: filterOptions,\n }),\n [list.plan.refused, filterOptions],\n )\n const filtered = queryClauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n\n /*\n * Every record a task names, not only the one its \"For\" cell shows: the\n * export writes the contact, the company and the deal each by name, and\n * a task that names two would otherwise carry an id in one column.\n */\n const linked = useMemo(\n () =>\n tasks.flatMap((task) => [\n ...(task.contactId ? [{ kind: 'contact' as const, id: task.contactId }] : []),\n ...(task.companyId ? [{ kind: 'company' as const, id: task.companyId }] : []),\n ...(task.dealId ? [{ kind: 'deal' as const, id: task.dealId }] : []),\n ]),\n [tasks],\n )\n const nameOf = useCrmRecordNames({ orgId, groupId, org: orgRecord, records: linked })\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A view, a filter or a page is a different set of rows; a selection made\n // on the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [view, list.plan, list.page])\n const csvOptions: TaskCsvOptions = useMemo(\n () => ({\n assigneeEmail: (uid) => {\n const member = findOrgMember(directory.members, uid)\n return member?.email || member?.label || uid\n },\n recordName: nameOf,\n }),\n [directory.members, nameOf],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('tasks.csv', 'text/csv', tasksCsv(tasks, csvOptions))\n }, [tasks, csvOptions])\n\n /*\n * List or month (AGL-2662). A layout choice over the SAME rows and the\n * same view — switching does not narrow anything and costs no read, so\n * it is a toggle rather than another saved view.\n */\n const [layout, setLayout] = useState<'list' | 'calendar'>('list')\n /*\n * The month draws one query page, so it asks for a month's worth: the\n * calendar's page is {@link CALENDAR_PAGE_SIZE} tasks of the same view and\n * filters, and it says when the view holds more. The list's own page size\n * comes back with the list.\n */\n const { setPage: setListPage, setPageSize: setListPageSize } = list\n useEffect(() => {\n setListPage(0)\n setListPageSize(layout === 'calendar' ? CALENDAR_PAGE_SIZE : TABLE_PAGE_SIZE_DEFAULT)\n }, [layout, setListPage, setListPageSize])\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n /*\n * Tick or untick. The two directions are two different writes on purpose\n * — see the module comment — and the row is disabled while either is in\n * flight so a double-click cannot complete a task and reopen it in the\n * same breath.\n */\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n status: 'open',\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n enqueueSnackbar('Task reopened', { variant: 'success' })\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The route runs as the mounted site, which hears the event, or\n // at the organization level as the org — the org variant, which\n // emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'status',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Checkbox\n size=\"small\"\n checked={row.status === 'done'}\n disabled={busyId === row.$id}\n onClick={(event) => {\n event.stopPropagation()\n void toggleDone(row)\n }}\n slotProps={{\n input: {\n 'aria-label':\n row.status === 'done'\n ? `Reopen \"${row.title}\"`\n : `Complete \"${row.title}\"`,\n },\n }}\n />\n ),\n },\n {\n field: 'title',\n headerName: 'Task',\n flex: 2,\n minWidth: 220,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}>\n <Typography\n variant=\"body2\"\n noWrap\n sx={{\n textDecoration: row.status === 'done' ? 'line-through' : undefined,\n color: row.status === 'done' ? 'text.secondary' : undefined,\n }}\n >\n {row.title}\n </Typography>\n {row.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.notes}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => <TaskKindCell kind={row.kind} />,\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip priority={row.priority} />\n ),\n },\n {\n field: 'dueAtMs',\n headerName: 'Due',\n width: 260,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', height: '100%' }}>\n <TaskDueText task={row} nowMs={nowMs} />\n {row.status === 'done' || !scope ? null : (\n <TaskSnoozeMenu\n dueAtMs={row.dueAtMs}\n remindAtMs={row.remindAtMs}\n target={{\n write: {\n scope,\n taskId: row.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: row,\n },\n }}\n disabled={busyId === row.$id}\n />\n )}\n </Stack>\n ),\n },\n {\n field: 'assigneeUid',\n headerName: 'Assignee',\n flex: 1,\n minWidth: 140,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n directory.nameOf(row.assigneeUid),\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography variant=\"body2\" color={row.assigneeUid ? undefined : 'text.secondary'}>\n {row.assigneeUid ? directory.nameOf(row.assigneeUid) : 'Unassigned'}\n </Typography>\n ),\n },\n {\n field: 'record',\n headerName: 'For',\n flex: 1,\n minWidth: 160,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskRecordLink task={row} routes={routes} nameOf={nameOf} />\n ),\n },\n ],\n [busyId, toggleDone, nowMs, directory, routes, nameOf, scope],\n )\n /* The column and sort models are the view's (AGL-2617). */\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, TASK_FILTER_FIELDS, filterOptions, TASK_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS)\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#the-tasks-page',\n excerpt:\n 'Calls, emails, meetings and to-dos, each linked to the contact, ' +\n 'company or deal it is for. Overdue and today are read off the ' +\n 'due date as you look.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n {/*\n The saved view, and the clauses narrowing it as chips — \"Show\"\n among them — set in the grid's own Filters panel (AGL-3313).\n */}\n <CrmListToolbar label=\"Task filters\">\n <CrmViewsControl controller={views} allLabel=\"All tasks\" />\n <ListFilterChips\n fields={TASK_FILTER_FIELDS}\n headers={TASK_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={layout}\n onChange={(_event, next) => {\n if (next) setLayout(next as 'list' | 'calendar')\n }}\n aria-label=\"Task layout\"\n >\n <ToggleButton value=\"list\">{'List'}</ToggleButton>\n <ToggleButton value=\"calendar\">{'Calendar'}</ToggleButton>\n </ToggleButtonGroup>\n <Stack sx={{ flex: 1 }} />\n <TaskImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!tasks.length}>\n {'Export CSV'}\n </Button>\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={list.plan.notices} />\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded. Reload to try again.'}\n </Typography>\n ) : (\n <>\n {/*\n The calendar draws the same rows, so there is no bulk bar\n over it: a selection is a list gesture, and a month grid\n offers nowhere to make one.\n */}\n {layout === 'calendar' ? (\n <TasksCalendar\n tasks={tasks}\n truncated={list.hasMore}\n onOpen={(task) => setDrawer({ open: true, task })}\n />\n ) : (\n <>\n <TasksBulkBar\n hostId={hostId}\n scope={scope}\n rows={tasks}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n directory={directory}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tasks}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n loading={status === 'loading'}\n onOpen={(id) => {\n const found = tasks.find((row) => row.$id === id)\n if (found) setDrawer({ open: true, task: found })\n }}\n /*\n * The panel and the search are the grid's; the section\n * answers them, the view through the query (AGL-3313). An\n * empty view is said inside the grid rather than instead of\n * it, so its toolbar stays to choose another view.\n */\n noRowsLabel={filtered ? 'No tasks match these filters' : EMPTY_LABEL[view]}\n noRowsDescription={filtered ? undefined : EMPTY_COPY[view]}\n noRowsAction={\n filtered ? undefined : (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n )\n }\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // The view orders the query (due soonest, or Done most\n // recently due), so the grid sorts nothing itself: a\n // sort over one page would reorder ten rows.\n sortingMode=\"server\"\n disableColumnSorting\n // Columns are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={list.page}\n pageSize={list.pageSize}\n rowCount={tasks.length}\n hasMore={list.hasMore}\n onPageChange={list.setPage}\n onPageSizeChange={list.setPageSize}\n />\n </>\n )}\n </>\n )}\n </Stack>\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={orgRecord}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nTasksSection.displayName = 'TasksSection'\n\nexport default TasksSection\n"],"names":["CRM_COLLECTIONS","findOrgMember","pluginDocsHelp","CardDisplay","ListTable","useCrmSavedView","useCrmScope","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CrmViewsControl","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","ToggleButton","ToggleButtonGroup","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useEffect","useMemo","useState","useCrmRecordNames","useCrmTaskList","useNowMs","useOrgMemberDirectory","downloadTextFile","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","tasksCsv","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","CRM_TASK_VIEWS","TASK_LIST_QUERY_FIELDS","listFilterGridColumns","ListPagination","TABLE_PAGE_SIZE_DEFAULT","ListQueryNotices","listQueryRefusals","useListGridFilter","ListFilterChips","CrmListToolbar","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","TaskImportButton","TaskSnoozeMenu","TasksBulkBar","TASK_FILTER_FIELDS","column","kind","path","operators","TASK_FILTER_HEADERS","view","priority","assigneeUid","TASK_HIDDEN_COLUMNS","CALENDAR_PAGE_SIZE","TASK_VIEW_DEFAULT","field","op","value","EMPTY_LABEL","mine","overdue","today","upcoming","open","done","EMPTY_COPY","TasksSection","props","hostId","org","basePath","orgRecord","firestore","data","user","enqueueSnackbar","nowMs","routes","consentGroup","groupId","views","section","state","filters","find","clause","some","option","id","clauses","setClauses","next","setFilters","filter","gridFilter","onChange","selectFields","queryClauses","list","uid","search","searchWords","tasks","status","fromCache","scope","orgId","readTokens","directory","filterOptions","map","label","members","member","refused","plan","fields","headers","options","filtered","length","word","trim","linked","flatMap","task","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","page","csvOptions","assigneeEmail","email","recordName","handleExport","layout","setLayout","setPage","setListPage","setPageSize","setListPageSize","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","variant","callScope","taskId","cause","Error","message","columns","headerName","width","sortable","filterable","renderCell","row","size","checked","disabled","onClick","event","stopPropagation","slotProps","input","title","flex","minWidth","sx","justifyContent","height","noWrap","textDecoration","undefined","color","notes","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","valueGetter","_value","filterColumns","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","marksServed","exclusive","_event","aria-label","notices","truncated","hasMore","onOpen","rows","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","found","noRowsLabel","noRowsDescription","noRowsAction","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","disableColumnSorting","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","onPageChange","onPageSizeChange","onClose","prev","seed","unreadable","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n CRM_COLLECTIONS,\n type CrmViewFilterClause,\n findOrgMember,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport CrmViewsControl from './crm-views-control'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n Stack,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmTaskRow, useCrmTaskList, useNowMs } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n TASK_LIST_QUERY_FIELDS,\n} from '../model/task-views'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport {\n TaskDueText,\n TaskKindCell,\n TaskPriorityChip,\n TaskRecordLink,\n} from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TasksCalendar from './tasks-calendar'\nimport { TaskImportButton } from './task-import-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\nimport TasksBulkBar from './tasks-bulk-bar'\n\n/** What an empty view is headed, by view — \"nothing overdue\" is good news. */\n/*\n * What the tasks grid's panel offers (AGL-3313). `view` is the task view\n * the query's base answers — a hidden column, since it is no field of a\n * task — and Kind, Priority and Assignee are predicates on the same query\n * (AGL-3321).\n */\nconst TASK_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'view', kind: 'exact', path: 'view', operators: ['equals'] },\n ...TASK_LIST_QUERY_FIELDS,\n]\nconst TASK_FILTER_HEADERS: Readonly<Record<string, string>> = {\n view: 'Show',\n kind: 'Kind',\n priority: 'Priority',\n assigneeUid: 'Assignee',\n}\n/** The filter-only `view` column never shows. */\nconst TASK_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { view: false }\n/** How many tasks the month view reads: one page, sized for a month of work. */\nconst CALENDAR_PAGE_SIZE = 200\n/** No `view` clause is \"My tasks\", which the section opened on before views existed. */\nconst TASK_VIEW_DEFAULT: CrmViewFilterClause = { field: 'view', op: 'equals', value: 'mine' }\nconst EMPTY_LABEL: Record<CrmTaskView, string> = {\n mine: 'Nothing is assigned to you',\n overdue: 'Nothing is overdue',\n today: 'Nothing is due today',\n upcoming: 'Nothing is scheduled beyond today',\n open: 'No open tasks',\n done: 'No completed tasks yet',\n}\n\n/** The one sentence under it, by view. */\nconst EMPTY_COPY: Record<CrmTaskView, string> = {\n mine: 'Tasks you create are yours unless you hand them to someone.',\n overdue: 'Every dated task is on or ahead of schedule.',\n today: 'Nothing on your calendar day is owed to anyone in the CRM.',\n upcoming: 'A task with a due date past today would be listed here.',\n open: 'A task is a call, an email, a meeting or a to-do owed to a contact, a company or a deal.',\n done: 'Tasks ticked off on their record, or here, are kept for the history.',\n}\n\n/**\n * `/crm/tasks` — what the team owes the people in the CRM (AGL-2599).\n *\n * Six views over `orgs/{orgId}/crmTasks`, each a bounded listener on one of\n * the collection's indexes, chosen by a segmented control. A task hangs off\n * a contact, a company or a deal, but it is listed HERE because \"what is due\n * today\" is a question about the team's day and not about any one record;\n * the record pages carry their own `RecordTasksCard` for the other\n * direction.\n *\n * Completing goes through `crm/task-complete`, because completing fires the\n * `taskCompleted` host event and only the server can run a workflow.\n * Reopening is client-direct: undoing a tick has no side effect beyond the\n * document, and the rules gate it like any other CRM write.\n *\n * The rows are selectable, and a selection raises `TasksBulkBar` over the\n * list — Complete and Assign through their routes, the due date and the\n * delete as batched writes (AGL-2621). Export CSV beside the view control\n * writes the view through `tasksCsv()`, naming the assignee and the linked\n * records; the bar writes the same file over the selection.\n */\nexport function TasksSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath = '' } = props\n const orgRecord = org as Record<string, unknown> | undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const routes = useMemo(() => crmRoutes(basePath), [basePath])\n // The viewing group a linked contact is named through, or `null` at the\n // organization level, where each is named through its own holder.\n const { consentGroup } = useCrmScope({ hostId, org: orgRecord })\n const groupId = consentGroup?.groupId ?? null\n\n /*\n * Which of the six task views is open is the saved VIEW'S (AGL-2617): a\n * saved view of tasks holds the task view beside the columns and the\n * sort, and the toggle below writes into it. Unset reads as \"My tasks\",\n * which is what the section opened on before views existed.\n */\n const views = useCrmSavedView({ section: 'tasks', hostId, org: orgRecord, basePath })\n const view: CrmTaskView = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'view')?.value\n return CRM_TASK_VIEWS.some((option) => option.id === value)\n ? (value as CrmTaskView)\n : 'mine'\n }, [views.state.filters])\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313). \"Show\" is the task view the query's base answers — the\n * hidden `view` column, as the toggle stored it, with none meaning \"My\n * tasks\" — and Kind, Priority, Assignee and the search word are on the\n * same query (AGL-3321).\n */\n const clauses = useMemo(\n () =>\n views.state.filters.some((clause) => clause.field === 'view')\n ? views.state.filters\n : [TASK_VIEW_DEFAULT, ...views.state.filters],\n [views.state.filters],\n )\n const setClauses = useCallback(\n (next: CrmViewFilterClause[]) =>\n views.setFilters(\n next.filter(\n (clause) => !(clause.field === 'view' && clause.value === TASK_VIEW_DEFAULT.value),\n ),\n ),\n [views.setFilters],\n )\n const gridFilter = useListGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['view', 'kind', 'priority', 'assigneeUid'],\n })\n const queryClauses = useMemo(\n () => clauses.filter((clause) => clause.field !== 'view'),\n [clauses],\n )\n const list = useCrmTaskList({\n hostId,\n org: orgRecord,\n view,\n uid: user?.uid,\n nowMs,\n clauses: queryClauses,\n search: gridFilter.searchWords,\n })\n const { tasks, status, fromCache, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\n const filterOptions = useMemo(\n () => ({\n view: CRM_TASK_VIEWS.map((option) => ({ value: option.id, label: option.label })),\n kind: CRM_TASK_KINDS.map((kind) => ({ value: kind, label: CRM_TASK_KIND_LABELS[kind] })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: CRM_TASK_PRIORITY_LABELS[priority],\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members],\n )\n // What the query could not hold, said above the list rather than matched in memory.\n const refused = useMemo(\n () =>\n listQueryRefusals(list.plan.refused, {\n fields: TASK_FILTER_FIELDS,\n headers: TASK_FILTER_HEADERS,\n options: filterOptions,\n }),\n [list.plan.refused, filterOptions],\n )\n const filtered = queryClauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n\n /*\n * Every record a task names, not only the one its \"For\" cell shows: the\n * export writes the contact, the company and the deal each by name, and\n * a task that names two would otherwise carry an id in one column.\n */\n const linked = useMemo(\n () =>\n tasks.flatMap((task) => [\n ...(task.contactId ? [{ kind: 'contact' as const, id: task.contactId }] : []),\n ...(task.companyId ? [{ kind: 'company' as const, id: task.companyId }] : []),\n ...(task.dealId ? [{ kind: 'deal' as const, id: task.dealId }] : []),\n ]),\n [tasks],\n )\n const nameOf = useCrmRecordNames({ orgId, groupId, org: orgRecord, records: linked })\n\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A view, a filter or a page is a different set of rows; a selection made\n // on the last one would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [view, list.plan, list.page])\n const csvOptions: TaskCsvOptions = useMemo(\n () => ({\n assigneeEmail: (uid) => {\n const member = findOrgMember(directory.members, uid)\n return member?.email || member?.label || uid\n },\n recordName: nameOf,\n }),\n [directory.members, nameOf],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('tasks.csv', 'text/csv', tasksCsv(tasks, csvOptions))\n }, [tasks, csvOptions])\n\n /*\n * List or month (AGL-2662). A layout choice over the SAME rows and the\n * same view — switching does not narrow anything and costs no read, so\n * it is a toggle rather than another saved view.\n */\n const [layout, setLayout] = useState<'list' | 'calendar'>('list')\n /*\n * The month draws one query page, so it asks for a month's worth: the\n * calendar's page is {@link CALENDAR_PAGE_SIZE} tasks of the same view and\n * filters, and it says when the view holds more. The list's own page size\n * comes back with the list.\n */\n const { setPage: setListPage, setPageSize: setListPageSize } = list\n useEffect(() => {\n setListPage(0)\n setListPageSize(layout === 'calendar' ? CALENDAR_PAGE_SIZE : TABLE_PAGE_SIZE_DEFAULT)\n }, [layout, setListPage, setListPageSize])\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n /*\n * Tick or untick. The two directions are two different writes on purpose\n * — see the module comment — and the row is disabled while either is in\n * flight so a double-click cannot complete a task and reopen it in the\n * same breath.\n */\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n status: 'open',\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n enqueueSnackbar('Task reopened', { variant: 'success' })\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The route runs as the mounted site, which hears the event, or\n // at the organization level as the org — the org variant, which\n // emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'status',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Checkbox\n size=\"small\"\n checked={row.status === 'done'}\n disabled={busyId === row.$id}\n onClick={(event) => {\n event.stopPropagation()\n void toggleDone(row)\n }}\n slotProps={{\n input: {\n 'aria-label':\n row.status === 'done'\n ? `Reopen \"${row.title}\"`\n : `Complete \"${row.title}\"`,\n },\n }}\n />\n ),\n },\n {\n field: 'title',\n headerName: 'Task',\n flex: 2,\n minWidth: 220,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}>\n <Typography\n variant=\"body2\"\n noWrap\n sx={{\n textDecoration: row.status === 'done' ? 'line-through' : undefined,\n color: row.status === 'done' ? 'text.secondary' : undefined,\n }}\n >\n {row.title}\n </Typography>\n {row.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.notes}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => <TaskKindCell kind={row.kind} />,\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip priority={row.priority} />\n ),\n },\n {\n field: 'dueAtMs',\n headerName: 'Due',\n width: 260,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', height: '100%' }}>\n <TaskDueText task={row} nowMs={nowMs} />\n {row.status === 'done' || !scope ? null : (\n <TaskSnoozeMenu\n dueAtMs={row.dueAtMs}\n remindAtMs={row.remindAtMs}\n target={{\n write: {\n scope,\n taskId: row.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: row,\n },\n }}\n disabled={busyId === row.$id}\n />\n )}\n </Stack>\n ),\n },\n {\n field: 'assigneeUid',\n headerName: 'Assignee',\n flex: 1,\n minWidth: 140,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n directory.nameOf(row.assigneeUid),\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography variant=\"body2\" color={row.assigneeUid ? undefined : 'text.secondary'}>\n {row.assigneeUid ? directory.nameOf(row.assigneeUid) : 'Unassigned'}\n </Typography>\n ),\n },\n {\n field: 'record',\n headerName: 'For',\n flex: 1,\n minWidth: 160,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskRecordLink task={row} routes={routes} nameOf={nameOf} />\n ),\n },\n ],\n [busyId, toggleDone, nowMs, directory, routes, nameOf, scope],\n )\n /* The column and sort models are the view's (AGL-2617). */\n const filterColumns = useMemo(\n () => listFilterGridColumns(columns, TASK_FILTER_FIELDS, filterOptions, TASK_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS)\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#the-tasks-page',\n excerpt:\n 'Calls, emails, meetings and to-dos, each linked to the contact, ' +\n 'company or deal it is for. Overdue and today are read off the ' +\n 'due date as you look.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <TaskImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!tasks.length}>\n {'Export CSV'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={2}>\n {/*\n The saved view, and the clauses narrowing it as chips — \"Show\"\n among them — set in the grid's own Filters panel (AGL-3313).\n */}\n <CrmListToolbar label=\"Task filters\">\n <CrmViewsControl controller={views} allLabel=\"All tasks\" />\n <ListFilterChips\n fields={TASK_FILTER_FIELDS}\n headers={TASK_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n marksServed={false}\n />\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={layout}\n onChange={(_event, next) => {\n if (next) setLayout(next as 'list' | 'calendar')\n }}\n aria-label=\"Task layout\"\n >\n <ToggleButton value=\"list\">{'List'}</ToggleButton>\n <ToggleButton value=\"calendar\">{'Calendar'}</ToggleButton>\n </ToggleButtonGroup>\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={list.plan.notices} />\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded. Reload to try again.'}\n </Typography>\n ) : (\n <>\n {/*\n The calendar draws the same rows, so there is no bulk bar\n over it: a selection is a list gesture, and a month grid\n offers nowhere to make one.\n */}\n {layout === 'calendar' ? (\n <TasksCalendar\n tasks={tasks}\n truncated={list.hasMore}\n onOpen={(task) => setDrawer({ open: true, task })}\n />\n ) : (\n <>\n <TasksBulkBar\n hostId={hostId}\n scope={scope}\n rows={tasks}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n directory={directory}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tasks}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n loading={status === 'loading'}\n onOpen={(id) => {\n const found = tasks.find((row) => row.$id === id)\n if (found) setDrawer({ open: true, task: found })\n }}\n /*\n * The panel and the search are the grid's; the section\n * answers them, the view through the query (AGL-3313). An\n * empty view is said inside the grid rather than instead of\n * it, so its toolbar stays to choose another view.\n */\n noRowsLabel={filtered ? 'No tasks match these filters' : EMPTY_LABEL[view]}\n noRowsDescription={filtered ? undefined : EMPTY_COPY[view]}\n noRowsAction={\n filtered ? undefined : (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n )\n }\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // The view orders the query (due soonest, or Done most\n // recently due), so the grid sorts nothing itself: a\n // sort over one page would reorder ten rows.\n sortingMode=\"server\"\n disableColumnSorting\n // Columns are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={list.page}\n pageSize={list.pageSize}\n rowCount={tasks.length}\n hasMore={list.hasMore}\n onPageChange={list.setPage}\n onPageSizeChange={list.setPageSize}\n />\n </>\n )}\n </>\n )}\n </Stack>\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={orgRecord}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nTasksSection.displayName = 'TasksSection'\n\nexport default TasksSection\n"],"names":["CRM_COLLECTIONS","findOrgMember","pluginDocsHelp","CardDisplay","ListTable","useCrmSavedView","useCrmScope","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CrmViewsControl","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","ToggleButton","ToggleButtonGroup","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useEffect","useMemo","useState","useCrmRecordNames","useCrmTaskList","useNowMs","useOrgMemberDirectory","downloadTextFile","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","tasksCsv","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","CRM_TASK_VIEWS","TASK_LIST_QUERY_FIELDS","listFilterGridColumns","ListPagination","TABLE_PAGE_SIZE_DEFAULT","ListQueryNotices","listQueryRefusals","useListGridFilter","ListFilterChips","CrmListActions","CrmListToolbar","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","TaskImportButton","TaskSnoozeMenu","TasksBulkBar","TASK_FILTER_FIELDS","column","kind","path","operators","TASK_FILTER_HEADERS","view","priority","assigneeUid","TASK_HIDDEN_COLUMNS","CALENDAR_PAGE_SIZE","TASK_VIEW_DEFAULT","field","op","value","EMPTY_LABEL","mine","overdue","today","upcoming","open","done","EMPTY_COPY","TasksSection","props","hostId","org","basePath","orgRecord","firestore","data","user","enqueueSnackbar","nowMs","routes","consentGroup","groupId","views","section","state","filters","find","clause","some","option","id","clauses","setClauses","next","setFilters","filter","gridFilter","onChange","selectFields","queryClauses","list","uid","search","searchWords","tasks","status","fromCache","scope","orgId","readTokens","directory","filterOptions","map","label","members","member","refused","plan","fields","headers","options","filtered","length","word","trim","linked","flatMap","task","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","page","csvOptions","assigneeEmail","email","recordName","handleExport","layout","setLayout","setPage","setListPage","setPageSize","setListPageSize","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","variant","callScope","taskId","cause","Error","message","columns","headerName","width","sortable","filterable","renderCell","row","size","checked","disabled","onClick","event","stopPropagation","slotProps","input","title","flex","minWidth","sx","justifyContent","height","noWrap","textDecoration","undefined","color","notes","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","valueGetter","_value","filterColumns","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","marksServed","exclusive","_event","aria-label","notices","truncated","hasMore","onOpen","rows","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","found","noRowsLabel","noRowsDescription","noRowsAction","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","disableColumnSorting","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","onPageChange","onPageSizeChange","onClose","prev","seed","unreadable","displayName"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { CrmFieldObject } from '@aglyn/aglyn';
18
+ import { type ListQueryDeclaration, type ListQueryFilter } from '@aglyn/shared-ui-jsx/const/list-query-plan';
19
+ /** The array the search box reads. */
20
+ export declare const CRM_FIELD_SEARCH_TOKENS = "searchTokens";
21
+ export declare const CRM_FIELD_LIST_QUERY: ListQueryDeclaration;
22
+ /** The scope every read of the table carries: this tab's definitions. */
23
+ export declare function crmFieldListBase(object: CrmFieldObject): ListQueryFilter[];
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { LIST_QUERY_ID_PATH } from "@aglyn/shared-ui-jsx/const/list-query-plan";
17
+ /*
18
+ * WHAT THE CRM › FIELDS TABLE ASKS FIRESTORE (AGL-3335).
19
+ *
20
+ * One query over `orgs/{orgId}/contactFields`, narrowed to the tab
21
+ * (`object ==`, the list's base) and read whole — at most
22
+ * `CONTACT_FIELDS_MAX_PER_ORG` — in document-id order. Every clause the
23
+ * Filters panel offers and the search box are predicates on it:
24
+ *
25
+ * type the stored type;
26
+ * required the stored flag, a boolean on every definition;
27
+ * search `searchTokens`: every prefix of the field's name and of its
28
+ * key, whole and split at `_`, `-` and `.`.
29
+ *
30
+ * All three are equalities beside the base, and the order is the document
31
+ * id, so Firestore answers every combination by merging the single-field
32
+ * indexes it keeps on its own: the table costs no composite. The stored
33
+ * `order`, and the grid's sorts by Field, Key and Type, are applied to the
34
+ * answer, which is every match there is — so none of them reorders a
35
+ * window. The three fields are stamped by the Fields section's writes
36
+ * (`crmFieldListFields`) and, for the definitions written before, by
37
+ * `tools/scripts/backfill-crm-list-fields.mjs`.
38
+ */ /** The array the search box reads. */ export const CRM_FIELD_SEARCH_TOKENS = 'searchTokens';
39
+ export const CRM_FIELD_LIST_QUERY = {
40
+ fields: [
41
+ {
42
+ column: 'type',
43
+ kind: 'exact',
44
+ path: 'type',
45
+ presence: 'always',
46
+ operators: [
47
+ 'equals',
48
+ 'isAnyOf'
49
+ ]
50
+ },
51
+ {
52
+ column: 'required',
53
+ kind: 'boolean',
54
+ path: 'required',
55
+ operators: [
56
+ 'equals'
57
+ ]
58
+ }
59
+ ],
60
+ sorts: [
61
+ {
62
+ path: LIST_QUERY_ID_PATH,
63
+ direction: 'asc'
64
+ }
65
+ ],
66
+ search: {
67
+ tokensPath: CRM_FIELD_SEARCH_TOKENS
68
+ }
69
+ };
70
+ /** The scope every read of the table carries: this tab's definitions. */ export function crmFieldListBase(object) {
71
+ return [
72
+ {
73
+ path: 'object',
74
+ op: '==',
75
+ value: object
76
+ }
77
+ ];
78
+ }
79
+
80
+ //# sourceMappingURL=field-list-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/field-list-query.ts"],"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\nimport type { CrmFieldObject } from '@aglyn/aglyn'\nimport {\n LIST_QUERY_ID_PATH,\n type ListQueryDeclaration,\n type ListQueryFilter,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * WHAT THE CRM › FIELDS TABLE ASKS FIRESTORE (AGL-3335).\n *\n * One query over `orgs/{orgId}/contactFields`, narrowed to the tab\n * (`object ==`, the list's base) and read whole — at most\n * `CONTACT_FIELDS_MAX_PER_ORG` — in document-id order. Every clause the\n * Filters panel offers and the search box are predicates on it:\n *\n * type the stored type;\n * required the stored flag, a boolean on every definition;\n * search `searchTokens`: every prefix of the field's name and of its\n * key, whole and split at `_`, `-` and `.`.\n *\n * All three are equalities beside the base, and the order is the document\n * id, so Firestore answers every combination by merging the single-field\n * indexes it keeps on its own: the table costs no composite. The stored\n * `order`, and the grid's sorts by Field, Key and Type, are applied to the\n * answer, which is every match there is — so none of them reorders a\n * window. The three fields are stamped by the Fields section's writes\n * (`crmFieldListFields`) and, for the definitions written before, by\n * `tools/scripts/backfill-crm-list-fields.mjs`.\n */\n\n/** The array the search box reads. */\nexport const CRM_FIELD_SEARCH_TOKENS = 'searchTokens'\n\nexport const CRM_FIELD_LIST_QUERY: ListQueryDeclaration = {\n fields: [\n {\n column: 'type',\n kind: 'exact',\n path: 'type',\n presence: 'always',\n operators: ['equals', 'isAnyOf'],\n },\n { column: 'required', kind: 'boolean', path: 'required', operators: ['equals'] },\n ],\n sorts: [{ path: LIST_QUERY_ID_PATH, direction: 'asc' }],\n search: { tokensPath: CRM_FIELD_SEARCH_TOKENS },\n}\n\n/** The scope every read of the table carries: this tab's definitions. */\nexport function crmFieldListBase(object: CrmFieldObject): ListQueryFilter[] {\n return [{ path: 'object', op: '==', value: object }]\n}\n"],"names":["LIST_QUERY_ID_PATH","CRM_FIELD_SEARCH_TOKENS","CRM_FIELD_LIST_QUERY","fields","column","kind","path","presence","operators","sorts","direction","search","tokensPath","crmFieldListBase","object","op","value"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,kBAAkB,QAGb,6CAA4C;AAEnD;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,oCAAoC,GACpC,OAAO,MAAMC,0BAA0B,eAAc;AAErD,OAAO,MAAMC,uBAA6C;IACxDC,QAAQ;QACN;YACEC,QAAQ;YACRC,MAAM;YACNC,MAAM;YACNC,UAAU;YACVC,WAAW;gBAAC;gBAAU;aAAU;QAClC;QACA;YAAEJ,QAAQ;YAAYC,MAAM;YAAWC,MAAM;YAAYE,WAAW;gBAAC;aAAS;QAAC;KAChF;IACDC,OAAO;QAAC;YAAEH,MAAMN;YAAoBU,WAAW;QAAM;KAAE;IACvDC,QAAQ;QAAEC,YAAYX;IAAwB;AAChD,EAAC;AAED,uEAAuE,GACvE,OAAO,SAASY,iBAAiBC,MAAsB;IACrD,OAAO;QAAC;YAAER,MAAM;YAAUS,IAAI;YAAMC,OAAOF;QAAO;KAAE;AACtD"}
@@ -57,7 +57,7 @@ export declare const LEAD_STATUS_FILTER_OPTIONS: {
57
57
  }[];
58
58
  /** The choices for Email: every verdict, plus the two aggregates. */
59
59
  export declare const LEAD_EMAIL_FILTER_OPTIONS: {
60
- value: "none" | "bounced" | "complained" | "ok" | "blocked" | "unsubscribed" | "do_not_contact" | "problem";
60
+ value: "none" | "bounced" | "complained" | "ok" | "undeliverable" | "blocked" | "unsubscribed" | "do_not_contact" | "problem";
61
61
  label: string;
62
62
  }[];
63
63
  /**