@aglyn/plugins-crm 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.
- package/package.json +14 -14
- package/src/lib/components/companies-section.js +43 -8
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +3 -1
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +6 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contacts-section.js +37 -8
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-email-check.d.ts +14 -0
- package/src/lib/components/crm-email-check.js +38 -1
- package/src/lib/components/crm-email-check.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.d.ts +5 -4
- package/src/lib/components/crm-next-activity-column.js +5 -4
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +3 -1
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-section.js +50 -6
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +9 -3
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/leads-section.js +61 -11
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/tasks-section.js +61 -9
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/company-filters.js +40 -2
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +10 -1
- package/src/lib/constants/contact-filters.js +62 -2
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.js +78 -0
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
- package/src/lib/hooks/use-crm-column-sort.js +156 -0
- package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
- package/src/lib/hooks/use-crm-tasks.js +10 -8
- package/src/lib/hooks/use-crm-tasks.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +6 -0
- package/src/lib/model/crm-list-query.js +13 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +19 -2
- package/src/lib/model/lead-filters.js +112 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/native-vocabulary.d.ts +41 -0
- package/src/lib/model/native-vocabulary.js +59 -0
- package/src/lib/model/native-vocabulary.js.map +1 -0
- package/src/lib/model/task-views.d.ts +15 -2
- package/src/lib/model/task-views.js +50 -4
- package/src/lib/model/task-views.js.map +1 -1
- package/src/lib/server/email-check.d.ts +4 -1
- package/src/lib/server/email-check.js +18 -6
- package/src/lib/server/email-check.js.map +1 -1
- package/src/lib/transfer/tasks.js +2 -1
- package/src/lib/transfer/tasks.js.map +1 -1
|
@@ -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 crmPicklistShownLabel,\n crmTaskPicklistLabels,\n crmTaskStatusWrite,\n type CrmViewFilterClause,\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 { useCrmTaskPicklists } from '../hooks/use-crm-task-picklists'\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 {\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\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 { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_TASKS_RESOURCE } from '../transfer/fields'\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: 'Type',\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). Import and Export open the console's\n * wizard and dialog on `crm.tasks` (AGL-3528); the export reads the view's\n * own query, and the bar opens it on 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 picklists = useCrmTaskPicklists(orgId)\n /*\n * Type and Priority filter on the MEANING the query holds (AGL-3517), so\n * each option is a meaning, named by the org's label for it.\n */\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) => ({\n value: kind,\n label: crmPicklistShownLabel(picklists.type, kind, null),\n })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: crmPicklistShownLabel(picklists.priority, priority, null),\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members, picklists.type, picklists.priority],\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 // The view and its filters, for the export to read the same tasks the list does (AGL-3528).\n const exportFilter = useMemo(() => ({ label: 'what the list shows', plan: list.plan }), [list.plan])\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 // Open again, with the label a new task starts with (AGL-3517).\n ...crmTaskStatusWrite(picklists.status, false),\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, picklists.status],\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: 'Type',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskKindCell kind={row.kind} label={crmTaskPicklistLabels(row, picklists).type} />\n ),\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip\n priority={row.priority}\n label={crmTaskPicklistLabels(row, picklists).priority}\n />\n ),\n },\n {\n // The org's status label (AGL-3517); the checkbox is the open/done half.\n field: 'statusLabel',\n headerName: 'Status',\n width: 150,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n crmTaskPicklistLabels(row, picklists).status,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography\n variant=\"body2\"\n color={row.status === 'done' ? 'text.secondary' : undefined}\n noWrap\n >\n {crmTaskPicklistLabels(row, picklists).status}\n </Typography>\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, picklists],\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 <CrmImportButton resource={CRM_TASKS_RESOURCE} noun=\"tasks\" hostId={hostId} />\n <CrmExportButton resource={CRM_TASKS_RESOURCE} hostId={hostId} filter={exportFilter} />\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 picklists={picklists}\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 />\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","crmPicklistShownLabel","crmTaskPicklistLabels","crmTaskStatusWrite","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","useCrmTaskPicklists","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","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","CrmExportButton","CrmImportButton","CRM_TASKS_RESOURCE","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","picklists","filterOptions","map","label","type","members","member","refused","plan","fields","headers","options","filtered","length","word","trim","linked","flatMap","task","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","page","exportFilter","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","valueGetter","_value","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","filterColumns","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","controller","allLabel","marksServed","exclusive","_event","aria-label","notices","truncated","hasMore","onOpen","rows","selected","onSelectedChange","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 crmPicklistRank,\n crmPicklistShownLabel,\n crmTaskPicklistLabels,\n crmTaskStatusWrite,\n type CrmViewFilterClause,\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 { useCrmTaskPicklists } from '../hooks/use-crm-task-picklists'\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 {\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n crmTaskViewPlan,\n crmTaskViewSort,\n TASK_LIST_QUERY_FIELDS,\n TASK_LIST_SORTS,\n taskRecordLink,\n} from '../model/task-views'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\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 { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { CRM_TASKS_RESOURCE } from '../transfer/fields'\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: 'Type',\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' }\n\n/** The row controls no header sorts (AGL-3680): the done box, an action like Complete. */\nconst TASK_ROW_CONTROLS = ['status'] as const\n\n/** A label's place in its org list, for a page sort; `null` for none (sorted last). */\nconst picklistRank = (picklist: Parameters<typeof crmPicklistRank>[0], label: string) =>\n label ? crmPicklistRank(picklist, label) : null\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). Import and Export open the console's\n * wizard and dialog on `crm.tasks` (AGL-3528); the export reads the view's\n * own query, and the bar opens it on 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 /*\n * The header order the view asks (AGL-3680), read before the query it\n * orders; with none, the task view's own Due order.\n */\n const viewDirection = crmTaskViewPlan(view, { nowMs, uid: user?.uid }).direction\n const taskSort = useMemo(\n () =>\n crmViewQuerySort(TASK_LIST_SORTS, views.state.sort, crmTaskViewSort({ direction: viewDirection })),\n [views.state.sort, viewDirection],\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 sort: taskSort.sort,\n })\n const { tasks, status, fromCache, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\n const picklists = useCrmTaskPicklists(orgId)\n /*\n * Type and Priority filter on the MEANING the query holds (AGL-3517), so\n * each option is a meaning, named by the org's label for it.\n */\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) => ({\n value: kind,\n label: crmPicklistShownLabel(picklists.type, kind, null),\n })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: crmPicklistShownLabel(picklists.priority, priority, null),\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members, picklists.type, picklists.priority],\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 // The view and its filters, for the export to read the same tasks the list does (AGL-3528).\n const exportFilter = useMemo(() => ({ label: 'what the list shows', plan: list.plan }), [list.plan])\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 // Open again, with the label a new task starts with (AGL-3517).\n ...crmTaskStatusWrite(picklists.status, false),\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, picklists.status],\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: 'Type',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskKindCell kind={row.kind} label={crmTaskPicklistLabels(row, picklists).type} />\n ),\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip\n priority={row.priority}\n label={crmTaskPicklistLabels(row, picklists).priority}\n />\n ),\n },\n {\n // The org's status label (AGL-3517); the checkbox is the open/done half.\n field: 'statusLabel',\n headerName: 'Status',\n width: 150,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n crmTaskPicklistLabels(row, picklists).status,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography\n variant=\"body2\"\n color={row.status === 'done' ? 'text.secondary' : undefined}\n noWrap\n >\n {crmTaskPicklistLabels(row, picklists).status}\n </Typography>\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, picklists],\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 * EVERY HEADER SORTS (AGL-3680): Due, Task and Priority on the query\n * (`TASK_LIST_SORTS`), the rest over the page, saying so — Type and Status\n * in the order the org keeps their values, For by the record's name. The\n * done box is a row control, like an actions column, and sorts nothing.\n */\n const pageSorts = useMemo(\n () => ({\n kind: (row: CrmTaskRow) =>\n picklistRank(picklists.type, crmTaskPicklistLabels(row, picklists).type),\n statusLabel: (row: CrmTaskRow) =>\n picklistRank(picklists.status, crmTaskPicklistLabels(row, picklists).status),\n record: (row: CrmTaskRow) => {\n const link = taskRecordLink(row, routes)\n return link ? (nameOf(link.kind, link.id) ?? null) : null\n },\n }),\n [picklists, routes, nameOf],\n )\n const columnSort = useCrmColumnSort<CrmTaskRow>({\n views,\n sorts: TASK_LIST_SORTS,\n defaultSort: crmTaskViewSort({ direction: viewDirection }),\n asked: taskSort,\n orderBy: list.plan.orderBy,\n rows: tasks,\n columns: grid.columns,\n pageSorts,\n unsortable: TASK_ROW_CONTROLS,\n })\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 <CrmImportButton resource={CRM_TASKS_RESOURCE} noun=\"tasks\" hostId={hostId} />\n <CrmExportButton resource={CRM_TASKS_RESOURCE} hostId={hostId} filter={exportFilter} />\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\n refused={refused}\n notices={[...list.plan.notices, ...columnSort.notices]}\n />\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 picklists={picklists}\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 />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={columnSort.rows}\n columnSort={columnSort}\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) until a header asks otherwise: the headers\n // sort through `columnSort` (AGL-3680).\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","crmPicklistRank","crmPicklistShownLabel","crmTaskPicklistLabels","crmTaskStatusWrite","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","useCrmTaskPicklists","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_VIEWS","crmTaskViewPlan","crmTaskViewSort","TASK_LIST_QUERY_FIELDS","TASK_LIST_SORTS","taskRecordLink","crmViewQuerySort","useCrmColumnSort","listFilterGridColumns","ListPagination","TABLE_PAGE_SIZE_DEFAULT","ListQueryNotices","listQueryRefusals","useListGridFilter","ListFilterChips","CrmListActions","CrmListToolbar","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","CrmExportButton","CrmImportButton","CRM_TASKS_RESOURCE","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","TASK_ROW_CONTROLS","picklistRank","picklist","label","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","viewDirection","uid","direction","taskSort","sort","list","search","searchWords","tasks","status","fromCache","scope","orgId","readTokens","directory","picklists","filterOptions","map","type","members","member","refused","plan","fields","headers","options","filtered","length","word","trim","linked","flatMap","task","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","page","exportFilter","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","valueGetter","_value","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","filterColumns","grid","pageSorts","statusLabel","record","link","columnSort","sorts","defaultSort","asked","orderBy","rows","unsortable","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","resource","noun","controller","allLabel","marksServed","exclusive","_event","aria-label","notices","truncated","hasMore","onOpen","selected","onSelectedChange","columnOrder","slots","selectable","loading","found","noRowsLabel","noRowsDescription","noRowsAction","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","pageSize","rowCount","onPageChange","onPageSizeChange","onClose","prev","seed","unreadable","displayName"],"mappings":"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"}
|
|
@@ -115,11 +115,49 @@ import { CRM_LIST_SEARCH } from "../model/crm-list-query.js";
|
|
|
115
115
|
/** The list's orders: newest change first, and by name under a prefix. */ export const COMPANY_LIST_SORTS = [
|
|
116
116
|
{
|
|
117
117
|
path: 'updatedAt',
|
|
118
|
-
direction: 'desc'
|
|
118
|
+
direction: 'desc',
|
|
119
|
+
column: 'updatedAt',
|
|
120
|
+
label: 'Updated'
|
|
119
121
|
},
|
|
120
122
|
{
|
|
121
123
|
path: 'nameLower',
|
|
122
|
-
direction: 'asc'
|
|
124
|
+
direction: 'asc',
|
|
125
|
+
column: 'name',
|
|
126
|
+
label: 'Company'
|
|
127
|
+
},
|
|
128
|
+
/*
|
|
129
|
+
* The other header orders (AGL-3680), each `alone` — served with no filter
|
|
130
|
+
* or search on, by one `(visibleTo, field)` composite per direction. The
|
|
131
|
+
* columns not here sort the page: the owner's name, the tags, the picklist
|
|
132
|
+
* labels in the org's order, the contact count (a counter the links move)
|
|
133
|
+
* and the optional Account fields, which a company may not carry.
|
|
134
|
+
*/ {
|
|
135
|
+
path: 'nameLower',
|
|
136
|
+
direction: 'desc',
|
|
137
|
+
column: 'name',
|
|
138
|
+
label: 'Company',
|
|
139
|
+
alone: true
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
path: 'updatedAt',
|
|
143
|
+
direction: 'asc',
|
|
144
|
+
column: 'updatedAt',
|
|
145
|
+
label: 'Updated',
|
|
146
|
+
alone: true
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: 'nextTaskAtMs',
|
|
150
|
+
direction: 'asc',
|
|
151
|
+
column: 'nextTaskAtMs',
|
|
152
|
+
label: 'Next activity',
|
|
153
|
+
alone: true
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
path: 'nextTaskAtMs',
|
|
157
|
+
direction: 'desc',
|
|
158
|
+
column: 'nextTaskAtMs',
|
|
159
|
+
label: 'Next activity',
|
|
160
|
+
alone: true
|
|
123
161
|
}
|
|
124
162
|
];
|
|
125
163
|
export const COMPANY_LIST_DECLARATION = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/company-filters.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 { ListFilterField } from '@aglyn/shared-util-tools/list-query/list-filter'\n\nimport { CRM_NEXT_ACTIVITY_FILTER_FIELD } from '../model/crm-next-activity'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport { CRM_LIST_SEARCH } from '../model/crm-list-query'\n\n/*\n * Companies (`orgs/{orgId}/companies`, read scoped to a host) — every clause\n * and the search word on the list's one query, newest change first\n * (AGL-3321).\n *\n * The search box reads `searchTokens`, the word prefixes of the name and\n * the domain every company writer stamps (`crmCompanyListFields`). Under a\n * site the scope clause (`visibleTo array-contains-any`) is the query's one\n * array clause, and the search folds into it (`scopedSearchTokens`) for a\n * reader who may drop it.\n *\n * The name is also an exact match and a PREFIX range over `nameLower` — the\n * list's one range, which then orders the list by name. The owner is a\n * choice from the roster, and \"No next activity\" asks `nextTaskAtMs ==\n * null`. Each equality rides one `(field, updatedAt DESC)` composite, and\n * one `(field, nameLower ASC)` beside it for the prefix.\n *\n * Type, Industry and Rating (AGL-3514) are choices from the org's lists,\n * asked of the KEY each writer stores beside the label (`typeKey`,\n * `industryKey`, `ratingKey` — see `crmCompanyListFields`): a choice's\n * value is the key, its caption the label, so the query compares what\n * the record keyed rather than how the label happens to be cased.\n */\nexport const COMPANY_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n },\n {\n column: 'ownerUid',\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf'],\n },\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n { column: 'type', kind: 'exact', path: 'typeKey', operators: ['equals', 'isAnyOf'] },\n { column: 'industry', kind: 'exact', path: 'industryKey', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'ratingKey', operators: ['equals', 'isAnyOf'] },\n]\n\n/** The picklist columns the list filters by, each with the list its choices come from. */\nexport const COMPANY_PICKLIST_FILTERS = [\n { column: 'type', picklistId: 'accountType', header: 'Type' },\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the name, beside the scope clause — the prefix range the\n * `(field, nameLower)` composites already serve.\n */\nexport const COMPANY_PREFIX_SEARCH = {\n field: COMPANY_LIST_FILTER_FIELDS[0],\n notice: 'Search matches the start of a company’s name for access limited to specific sites.',\n}\n\n/** The list's orders: newest change first, and by name under a prefix. */\nexport const COMPANY_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n { path: 'nameLower', direction: 'asc' },\n]\n\nexport const COMPANY_LIST_DECLARATION: ListQueryDeclaration = {\n fields: COMPANY_LIST_FILTER_FIELDS,\n sorts: COMPANY_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n"],"names":["CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_LIST_SEARCH","COMPANY_LIST_FILTER_FIELDS","column","kind","path","lowerPath","presence","operators","COMPANY_PICKLIST_FILTERS","picklistId","header","COMPANY_PREFIX_SEARCH","field","notice","COMPANY_LIST_SORTS","direction","COMPANY_LIST_DECLARATION","fields","sorts","search"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID,SAASA,8BAA8B,QAAQ,gCAA4B;AAE3E,SAASC,eAAe,QAAQ,6BAAyB;AAEzD;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,6BAAyD;IACpE;QACEC,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNC,WAAW;QACXC,UAAU;QACVC,WAAW;YAAC;YAAU;SAAa;IACrC;IACA;QACEL,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNG,WAAW;YAAC;YAAU;SAAU;IAClC;IACAR;IACA;QAAEG,QAAQ;QAAQC,MAAM;QAASC,MAAM;QAAWG,WAAW;YAAC;YAAU;SAAU;IAAC;IACnF;QAAEL,QAAQ;QAAYC,MAAM;QAASC,MAAM;QAAeG,WAAW;YAAC;YAAU;SAAU;IAAC;IAC3F;QAAEL,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAaG,WAAW;YAAC;YAAU;SAAU;IAAC;CACxF,CAAA;AAED,wFAAwF,GACxF,OAAO,MAAMC,2BAA2B;IACtC;QAAEN,QAAQ;QAAQO,YAAY;QAAeC,QAAQ;IAAO;IAC5D;QAAER,QAAQ;QAAYO,YAAY;QAAYC,QAAQ;IAAW;IACjE;QAAER,QAAQ;QAAUO,YAAY;QAAUC,QAAQ;IAAS;CAC5D,CAAS;AAEV;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB;IACnCC,OAAOX,0BAA0B,CAAC,EAAE;IACpCY,QAAQ;AACV,EAAC;AAED,wEAAwE,GACxE,OAAO,MAAMC,qBAA+C;IAC1D;QAAEV,MAAM;QAAaW,WAAW;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/company-filters.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 { ListFilterField } from '@aglyn/shared-util-tools/list-query/list-filter'\n\nimport { CRM_NEXT_ACTIVITY_FILTER_FIELD } from '../model/crm-next-activity'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport { CRM_LIST_SEARCH } from '../model/crm-list-query'\n\n/*\n * Companies (`orgs/{orgId}/companies`, read scoped to a host) — every clause\n * and the search word on the list's one query, newest change first\n * (AGL-3321).\n *\n * The search box reads `searchTokens`, the word prefixes of the name and\n * the domain every company writer stamps (`crmCompanyListFields`). Under a\n * site the scope clause (`visibleTo array-contains-any`) is the query's one\n * array clause, and the search folds into it (`scopedSearchTokens`) for a\n * reader who may drop it.\n *\n * The name is also an exact match and a PREFIX range over `nameLower` — the\n * list's one range, which then orders the list by name. The owner is a\n * choice from the roster, and \"No next activity\" asks `nextTaskAtMs ==\n * null`. Each equality rides one `(field, updatedAt DESC)` composite, and\n * one `(field, nameLower ASC)` beside it for the prefix.\n *\n * Type, Industry and Rating (AGL-3514) are choices from the org's lists,\n * asked of the KEY each writer stores beside the label (`typeKey`,\n * `industryKey`, `ratingKey` — see `crmCompanyListFields`): a choice's\n * value is the key, its caption the label, so the query compares what\n * the record keyed rather than how the label happens to be cased.\n */\nexport const COMPANY_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n },\n {\n column: 'ownerUid',\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf'],\n },\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n { column: 'type', kind: 'exact', path: 'typeKey', operators: ['equals', 'isAnyOf'] },\n { column: 'industry', kind: 'exact', path: 'industryKey', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'ratingKey', operators: ['equals', 'isAnyOf'] },\n]\n\n/** The picklist columns the list filters by, each with the list its choices come from. */\nexport const COMPANY_PICKLIST_FILTERS = [\n { column: 'type', picklistId: 'accountType', header: 'Type' },\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the name, beside the scope clause — the prefix range the\n * `(field, nameLower)` composites already serve.\n */\nexport const COMPANY_PREFIX_SEARCH = {\n field: COMPANY_LIST_FILTER_FIELDS[0],\n notice: 'Search matches the start of a company’s name for access limited to specific sites.',\n}\n\n/** The list's orders: newest change first, and by name under a prefix. */\nexport const COMPANY_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc', column: 'updatedAt', label: 'Updated' },\n { path: 'nameLower', direction: 'asc', column: 'name', label: 'Company' },\n /*\n * The other header orders (AGL-3680), each `alone` — served with no filter\n * or search on, by one `(visibleTo, field)` composite per direction. The\n * columns not here sort the page: the owner's name, the tags, the picklist\n * labels in the org's order, the contact count (a counter the links move)\n * and the optional Account fields, which a company may not carry.\n */\n { path: 'nameLower', direction: 'desc', column: 'name', label: 'Company', alone: true },\n { path: 'updatedAt', direction: 'asc', column: 'updatedAt', label: 'Updated', alone: true },\n { path: 'nextTaskAtMs', direction: 'asc', column: 'nextTaskAtMs', label: 'Next activity', alone: true },\n { path: 'nextTaskAtMs', direction: 'desc', column: 'nextTaskAtMs', label: 'Next activity', alone: true },\n]\n\nexport const COMPANY_LIST_DECLARATION: ListQueryDeclaration = {\n fields: COMPANY_LIST_FILTER_FIELDS,\n sorts: COMPANY_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n"],"names":["CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_LIST_SEARCH","COMPANY_LIST_FILTER_FIELDS","column","kind","path","lowerPath","presence","operators","COMPANY_PICKLIST_FILTERS","picklistId","header","COMPANY_PREFIX_SEARCH","field","notice","COMPANY_LIST_SORTS","direction","label","alone","COMPANY_LIST_DECLARATION","fields","sorts","search"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID,SAASA,8BAA8B,QAAQ,gCAA4B;AAE3E,SAASC,eAAe,QAAQ,6BAAyB;AAEzD;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,6BAAyD;IACpE;QACEC,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNC,WAAW;QACXC,UAAU;QACVC,WAAW;YAAC;YAAU;SAAa;IACrC;IACA;QACEL,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNG,WAAW;YAAC;YAAU;SAAU;IAClC;IACAR;IACA;QAAEG,QAAQ;QAAQC,MAAM;QAASC,MAAM;QAAWG,WAAW;YAAC;YAAU;SAAU;IAAC;IACnF;QAAEL,QAAQ;QAAYC,MAAM;QAASC,MAAM;QAAeG,WAAW;YAAC;YAAU;SAAU;IAAC;IAC3F;QAAEL,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAaG,WAAW;YAAC;YAAU;SAAU;IAAC;CACxF,CAAA;AAED,wFAAwF,GACxF,OAAO,MAAMC,2BAA2B;IACtC;QAAEN,QAAQ;QAAQO,YAAY;QAAeC,QAAQ;IAAO;IAC5D;QAAER,QAAQ;QAAYO,YAAY;QAAYC,QAAQ;IAAW;IACjE;QAAER,QAAQ;QAAUO,YAAY;QAAUC,QAAQ;IAAS;CAC5D,CAAS;AAEV;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB;IACnCC,OAAOX,0BAA0B,CAAC,EAAE;IACpCY,QAAQ;AACV,EAAC;AAED,wEAAwE,GACxE,OAAO,MAAMC,qBAA+C;IAC1D;QAAEV,MAAM;QAAaW,WAAW;QAAQb,QAAQ;QAAac,OAAO;IAAU;IAC9E;QAAEZ,MAAM;QAAaW,WAAW;QAAOb,QAAQ;QAAQc,OAAO;IAAU;IACxE;;;;;;GAMC,GACD;QAAEZ,MAAM;QAAaW,WAAW;QAAQb,QAAQ;QAAQc,OAAO;QAAWC,OAAO;IAAK;IACtF;QAAEb,MAAM;QAAaW,WAAW;QAAOb,QAAQ;QAAac,OAAO;QAAWC,OAAO;IAAK;IAC1F;QAAEb,MAAM;QAAgBW,WAAW;QAAOb,QAAQ;QAAgBc,OAAO;QAAiBC,OAAO;IAAK;IACtG;QAAEb,MAAM;QAAgBW,WAAW;QAAQb,QAAQ;QAAgBc,OAAO;QAAiBC,OAAO;IAAK;CACxG,CAAA;AAED,OAAO,MAAMC,2BAAiD;IAC5DC,QAAQlB;IACRmB,OAAON;IACPO,QAAQrB;AACV,EAAC"}
|
|
@@ -37,7 +37,16 @@ export declare function contactCustomFilterHeaders(definitions: readonly Pick<Co
|
|
|
37
37
|
* picker, and as the header of a filter-only hidden column.
|
|
38
38
|
*/
|
|
39
39
|
export declare const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>>;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The list's orders: newest change first, then each header the query can
|
|
42
|
+
* order by (AGL-3680) — the person's name or address (`nameSortKey`), the
|
|
43
|
+
* Last activity column's `updatedAt` the other way, the next activity and
|
|
44
|
+
* the address verdict, every one stored on every contact. Each is `alone`:
|
|
45
|
+
* served with no filter or search on, beside the scope clause by one
|
|
46
|
+
* `(visibleTo, field)` composite per direction. The other columns — a
|
|
47
|
+
* holder's owner, stage, sources, tags, last engagement and custom values,
|
|
48
|
+
* which live on a facet, and the sites that know the person — sort the page.
|
|
49
|
+
*/
|
|
41
50
|
export declare const CONTACT_LIST_SORTS: readonly ListQuerySort[];
|
|
42
51
|
/** The stored array a facet clause is asked of. */
|
|
43
52
|
export declare const CONTACT_FACET_KEYS_PATH = "facetKeys";
|
|
@@ -346,10 +346,70 @@ import { CRM_LIST_SEARCH, crmAnyOf, crmClauseValues } from "../model/crm-list-qu
|
|
|
346
346
|
};
|
|
347
347
|
/*------------------------------------------
|
|
348
348
|
* THE QUERY THE CONTACTS LIST RUNS.
|
|
349
|
-
*-----------------------------------------*/ /**
|
|
349
|
+
*-----------------------------------------*/ /**
|
|
350
|
+
* The list's orders: newest change first, then each header the query can
|
|
351
|
+
* order by (AGL-3680) — the person's name or address (`nameSortKey`), the
|
|
352
|
+
* Last activity column's `updatedAt` the other way, the next activity and
|
|
353
|
+
* the address verdict, every one stored on every contact. Each is `alone`:
|
|
354
|
+
* served with no filter or search on, beside the scope clause by one
|
|
355
|
+
* `(visibleTo, field)` composite per direction. The other columns — a
|
|
356
|
+
* holder's owner, stage, sources, tags, last engagement and custom values,
|
|
357
|
+
* which live on a facet, and the sites that know the person — sort the page.
|
|
358
|
+
*/ export const CONTACT_LIST_SORTS = [
|
|
350
359
|
{
|
|
351
360
|
path: 'updatedAt',
|
|
352
|
-
direction: 'desc'
|
|
361
|
+
direction: 'desc',
|
|
362
|
+
column: 'updatedAt',
|
|
363
|
+
label: 'Last activity'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
path: 'updatedAt',
|
|
367
|
+
direction: 'asc',
|
|
368
|
+
column: 'updatedAt',
|
|
369
|
+
label: 'Last activity',
|
|
370
|
+
alone: true
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
path: 'nameSortKey',
|
|
374
|
+
direction: 'asc',
|
|
375
|
+
column: 'name',
|
|
376
|
+
label: 'Contact',
|
|
377
|
+
alone: true
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
path: 'nameSortKey',
|
|
381
|
+
direction: 'desc',
|
|
382
|
+
column: 'name',
|
|
383
|
+
label: 'Contact',
|
|
384
|
+
alone: true
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
path: 'nextTaskAtMs',
|
|
388
|
+
direction: 'asc',
|
|
389
|
+
column: 'nextTaskAtMs',
|
|
390
|
+
label: 'Next activity',
|
|
391
|
+
alone: true
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
path: 'nextTaskAtMs',
|
|
395
|
+
direction: 'desc',
|
|
396
|
+
column: 'nextTaskAtMs',
|
|
397
|
+
label: 'Next activity',
|
|
398
|
+
alone: true
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
path: 'emailStatus',
|
|
402
|
+
direction: 'asc',
|
|
403
|
+
column: 'emailState',
|
|
404
|
+
label: 'Email',
|
|
405
|
+
alone: true
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
path: 'emailStatus',
|
|
409
|
+
direction: 'desc',
|
|
410
|
+
column: 'emailState',
|
|
411
|
+
label: 'Email',
|
|
412
|
+
alone: true
|
|
353
413
|
}
|
|
354
414
|
];
|
|
355
415
|
/** The stored array a facet clause is asked of. */ export const CONTACT_FACET_KEYS_PATH = 'facetKeys';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/contact-filters.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 {\n type ContactFieldDefinition,\n CRM_CONTACT_VIEW_FIELDS,\n CRM_FACET_KEY_ANY_GROUP,\n type CrmFacetKeyField,\n crmContactCustomColumn,\n crmFacetKey,\n crmFacetKeyValue,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { EMAIL_STATE_STATUSES } from '@aglyn/aglyn/app-utils/email-state'\nimport type {\n ListFilterField,\n ListFilterRequest,\n} from '@aglyn/shared-util-tools/list-query/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n} from '../model/crm-list-query'\n\n/*==========================================\n * CONTACTS (`orgs/{orgId}/contacts`, read scoped to a host) — WHAT THE\n * PANEL OFFERS, AND WHAT THE QUERY ASKS (AGL-3321).\n *\n * Every clause and the search word are on the list's one Firestore query,\n * newest change first, paged by it. A contact is one row shared by every\n * holder, and most of what a holder filters by — the owner, the stage, the\n * sources, the company, the tags, a custom value, whether the person has\n * bought — lives on that holder's FACET, a path per holder no index can\n * reach. So every writer stamps `facetKeys` (`crmContactFacetKeys` in\n * `@aglyn/aglyn`): `{group}:{field}={value}` for each holder's value,\n * `{group}:{field}` for \"has one\", and the same under `*` for any holder.\n * Under a site a facet clause asks the viewing holder's key; at the\n * organization level, any holder's.\n *\n * What one query holds decides the rest:\n *\n * - ONE array clause. The scope clause (`visibleTo array-contains-any`),\n * the search (`searchTokens`), a name word (`nameTokens`), a form\n * (`formIds`) and a facet clause (`facetKeys`) all are one. The search\n * folds into the scope clause for a reader who may drop it; a facet or\n * form clause stands in the scope clause's place for that reader, since\n * only a record the viewing holder keeps carries its key. Two of them\n * at once is refused by name.\n * - ONE range. `updatedAt`, the field the list is ordered by, composes\n * with anything; Created and a name or address prefix order the list by\n * their own field and so stand alone beside the scope clause\n * (`contactSoloClause`), served by the `(visibleTo, field)` composites.\n * - equalities (the address, the site, the verdict, \"No next activity\")\n * compose freely, one `(field, updatedAt DESC)` composite each.\n *\n * The per-holder FIGURES (orders, lifetime value) live on each holder's\n * facet, where no range reaches — a top-level `ordersCount` was never\n * written — so they offer \"is not empty\" alone: \"has bought\".\n *\n * A collaborator's search is the start of the name (`CONTACT_PREFIX_SEARCH`).\n *=========================================*/\n\n/**\n * A word of the canonical name (`nameTokens`), the whole of it, or its start\n * or end — `nameLower`, which every contact carries, and `nameReversed`.\n */\nconst NAME_FIELD: ListFilterField = {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n tokensPath: 'nameTokens',\n reversedPath: 'nameReversed',\n operators: ['contains', 'equals', 'startsWith', 'endsWith'],\n}\n\n/**\n * The address. `normalizeContactEmail` lower-cases before every write, so the\n * stored value IS its own normalized key.\n */\nconst EMAIL_FIELD: ListFilterField = {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n}\n\n/** When the person was first captured — a range the list is then ordered by. */\nconst CREATED_FIELD: ListFilterField = {\n column: CRM_CONTACT_VIEW_FIELDS.createdAt,\n kind: 'date',\n path: 'createdAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n}\n\n/**\n * The forms a person came in through — the top-level `formIds` mirror of the\n * interactions' `formId` (AGL-2612), matched whole and as typed because a\n * form id is minted with mixed case. The form's own page links here with its\n * id; nobody types one.\n */\nconst FORM_FIELD: ListFilterField = {\n column: 'formIds',\n kind: 'text',\n path: 'formIds',\n tokensPath: 'formIds',\n verbatimTokens: true,\n operators: ['contains'],\n}\n\n/** The column a contact's lead source filters as (AGL-3511). */\nexport const CONTACT_LEAD_SOURCE_COLUMN = 'leadSource'\n\n/** What the Filters panel offers on a contact, in the grid's grammar. */\nexport const CONTACT_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n {\n // A holder's tags, matched whole and case-insensitively.\n column: CRM_CONTACT_VIEW_FIELDS.tags,\n kind: 'exact',\n path: 'tags',\n operators: ['contains', 'isAnyOf', 'isNotEmpty'],\n },\n FORM_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n // Per-holder figures: \"has bought\", never a range across holders.\n {\n column: CRM_CONTACT_VIEW_FIELDS.orders,\n kind: 'number',\n path: 'ordersCount',\n operators: ['isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.ltv,\n kind: 'number',\n path: 'ltvCents',\n operators: ['isNotEmpty'],\n },\n CREATED_FIELD,\n {\n column: CRM_CONTACT_VIEW_FIELDS.updatedAt,\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n /*\n * \"No next activity\" (AGL-2661): `nextTaskAtMs == null`, which every\n * contact carries from its creation until a task is scheduled against it.\n */\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n /*\n * THE FACET FIELDS (AGL-2617). The values are picked, not typed — a uid\n * from the roster, a stage from the fixed list, a source from its labels,\n * a company from the picker — so `equals` and `isAnyOf` are what they\n * offer; the section supplies the choices. The column names are the\n * contract `CRM_CONTACT_VIEW_FIELDS` states, because the dynamic-list\n * translator reads a view by them. The paths are the flattened ROW'S.\n */\n {\n column: CRM_CONTACT_VIEW_FIELDS.owner,\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.stage,\n kind: 'exact',\n path: 'lifecycleStage',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n // The `sources` presence map, matched on its keys — see `keysOf`.\n column: CRM_CONTACT_VIEW_FIELDS.source,\n kind: 'exact',\n path: 'sources',\n keysOf: true,\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.company,\n kind: 'exact',\n path: 'companyId',\n operators: ['equals', 'isNotEmpty'],\n },\n /*\n * A holder's lead source (AGL-3511), picked from the org's Lead source\n * values and asked through its `leadSource` facet key — the label as the\n * picklist compares it.\n */\n {\n column: CONTACT_LEAD_SOURCE_COLUMN,\n kind: 'exact',\n path: 'leadSource',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n /*\n * The verdict on the address (AGL-3245), asked through `emailStatus` —\n * `none` for a person nothing has been said about, which is what \"is\n * empty\" asks.\n */\n {\n column: 'emailState',\n kind: 'exact',\n path: 'emailState.status',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty', 'isNotEmpty'],\n },\n]\n\n/**\n * One filter field per active custom contact field (AGL-2617). A custom\n * value lives under the holder's `custom` map, so it is asked through the\n * holder's facet key: equality on a picked, typed or ticked value, and\n * \"is not empty\". Its column is the one the table shows it under —\n * `crmContactCustomColumn(key)` — so a clause and a column agree on the name.\n */\nexport function contactCustomFilterFields(\n definitions: readonly Pick<\n ContactFieldDefinition,\n 'key' | 'type' | 'retiredAt'\n >[],\n): ListFilterField[] {\n return definitions\n .filter((definition) => !definition.retiredAt)\n .map((definition): ListFilterField => {\n const column = crmContactCustomColumn(definition.key)\n const path = `custom.${definition.key}`\n switch (definition.type) {\n case 'number':\n return {\n column,\n kind: 'number',\n path,\n operators: ['=', 'isNotEmpty'],\n }\n case 'date':\n return { column, kind: 'date', path, operators: ['isNotEmpty'] }\n case 'checkbox':\n return { column, kind: 'boolean', path }\n case 'select':\n return {\n column,\n kind: 'exact',\n path,\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n }\n default:\n return {\n column,\n kind: 'text',\n path,\n lowerPath: path,\n operators: ['equals', 'isNotEmpty'],\n }\n }\n })\n}\n\n/** The custom fields' headers, keyed by the column each filters as. */\nexport function contactCustomFilterHeaders(\n definitions: readonly Pick<ContactFieldDefinition, 'key' | 'label'>[],\n): Record<string, string> {\n return Object.fromEntries(\n definitions.map((definition) => [\n crmContactCustomColumn(definition.key),\n definition.label || definition.key,\n ]),\n )\n}\n\n/**\n * How every filterable contact field reads — on a chip, in the add-filter\n * picker, and as the header of a filter-only hidden column.\n */\nexport const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Contact',\n email: 'Email',\n tags: 'Tags',\n formIds: 'Form ID',\n hostId: 'Site ID',\n ordersCount: 'Orders',\n ltvCents: 'Lifetime value (cents)',\n createdAt: 'Created',\n updatedAt: 'Updated',\n nextTaskAtMs: 'Next activity',\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'Owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'Stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'Source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'Company',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'Lead source',\n emailState: 'Email',\n}\n\n/*------------------------------------------\n * THE QUERY THE CONTACTS LIST RUNS.\n *-----------------------------------------*/\n\n/** The list's one order: newest change first. */\nexport const CONTACT_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n]\n\n/** The stored array a facet clause is asked of. */\nexport const CONTACT_FACET_KEYS_PATH = 'facetKeys'\n\n/** The fields the Contacts query asks — each a stored field, as named. */\nexport const CONTACT_QUERY_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n FORM_FIELD,\n CREATED_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n {\n column: 'updatedAt',\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n {\n column: 'emailStatus',\n kind: 'exact',\n path: 'emailStatus',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CONTACT_FACET_KEYS_PATH,\n kind: 'exact',\n path: CONTACT_FACET_KEYS_PATH,\n tokensPath: CONTACT_FACET_KEYS_PATH,\n operators: ['contains', 'isAnyOf'],\n },\n]\n\nexport const CONTACT_LIST_DECLARATION: ListQueryDeclaration = {\n fields: CONTACT_QUERY_FIELDS,\n sorts: CONTACT_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The grid fields a stored clause passes to the query unchanged. */\nconst AS_STORED = new Set([\n 'name',\n 'email',\n 'formIds',\n 'hostId',\n 'createdAt',\n 'updatedAt',\n 'nextTaskAtMs',\n])\n\n/**\n * The ranges that stand ALONE beside the scope clause: Created and a name\n * or address prefix each order the list by their own field, which the\n * `(visibleTo, field)` composites serve and nothing beside them. Updated is\n * not one: the list is ordered by it already, so it composes freely.\n */\nexport function contactSoloClause(clause: ListFilterRequest): boolean {\n if (clause.field === 'createdAt') return true\n if (clause.field === 'name') return clause.op === 'startsWith' || clause.op === 'endsWith'\n return clause.field === 'email' && clause.op === 'startsWith'\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the contact's name, beside the scope clause.\n */\nexport const CONTACT_PREFIX_SEARCH = {\n field: NAME_FIELD,\n notice: 'Search matches the start of a contact’s name for access limited to specific sites.',\n}\n\n/** The facet field each grid column is keyed under. */\nconst FACET_FIELDS: Readonly<Record<string, CrmFacetKeyField>> = {\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'company',\n [CRM_CONTACT_VIEW_FIELDS.tags]: 'tag',\n [CRM_CONTACT_VIEW_FIELDS.orders]: 'orders',\n [CRM_CONTACT_VIEW_FIELDS.ltv]: 'ltv',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'leadSource',\n}\n\n/** Who is asking: the viewing holder, and whether their query may drop the scope clause. */\nexport interface ContactQueryReader {\n /** The viewing holder's group id under a site; `null` at the organization level. */\n groupId: string | null\n /**\n * Whether the reader's query may stand a facet or form clause in the\n * scope clause's place (`useCrmFoldsScope`) — always at the organization\n * level, where there is no scope clause.\n */\n foldsScope: boolean\n}\n\n/** Why a reader whose access is some sites cannot filter by a holder's field. */\nconst LIMITED_ACCESS =\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it'\n\n/** The facet field a grid column names, or null for one that is not a facet field. */\nfunction facetFieldOf(column: string): CrmFacetKeyField | null {\n if (FACET_FIELDS[column]) return FACET_FIELDS[column]\n if (column.startsWith(CRM_CONTACT_VIEW_FIELDS.custom)) {\n return `custom.${column.slice(CRM_CONTACT_VIEW_FIELDS.custom.length)}`\n }\n return null\n}\n\n/**\n * One grid clause as the clause the Contacts query asks — see the block at\n * the top. A clause a query cannot serve for this reader is refused by\n * name, never matched over the rows a page loaded.\n */\nexport function contactQueryClause(\n clause: ListFilterRequest,\n reader: ContactQueryReader,\n): CrmClauseAsked {\n if (AS_STORED.has(clause.field)) {\n if (clause.field === 'formIds' && !reader.foldsScope)\n return { refused: LIMITED_ACCESS }\n return { field: clause.field, op: clause.op, value: clause.value }\n }\n if (clause.field === 'emailState') {\n if (clause.op === 'isEmpty')\n return { field: 'emailStatus', op: 'equals', value: 'none' }\n if (clause.op === 'isNotEmpty')\n return crmAnyOf('emailStatus', EMAIL_STATE_STATUSES)\n const values = crmClauseValues(clause)\n if (\n values.some(\n (value) => !(EMAIL_STATE_STATUSES as readonly string[]).includes(value),\n )\n ) {\n return { refused: 'pick one of the choices' }\n }\n return crmAnyOf('emailStatus', values)\n }\n const facet = facetFieldOf(clause.field)\n if (!facet) return { refused: 'this list does not filter by that' }\n if (!reader.foldsScope) return { refused: LIMITED_ACCESS }\n const group = reader.groupId ?? CRM_FACET_KEY_ANY_GROUP\n if (clause.op === 'isNotEmpty') {\n return {\n field: CONTACT_FACET_KEYS_PATH,\n op: 'contains',\n value: crmFacetKey(group, facet),\n }\n }\n const keys: string[] = []\n for (const value of crmClauseValues(clause)) {\n const key = crmFacetKeyValue(clause.op === '=' ? Number(value) : value)\n if (key === null) return { refused: 'no value yet' }\n keys.push(crmFacetKey(group, facet, key))\n }\n const asked = crmAnyOf(CONTACT_FACET_KEYS_PATH, keys)\n // One key is an `array-contains`; several, an `array-contains-any`.\n return 'refused' in asked || asked.op === 'isAnyOf'\n ? asked\n : { ...asked, op: 'contains' }\n}\n\n/**\n * Whether an asked clause stands in for the scope clause: a facet key of\n * the viewing holder, which only a record that holder keeps carries, or a\n * form, which only its site's captures carry.\n */\nexport function contactClauseImpliesScope(groupId: string | null) {\n return (clause: ListFilterRequest): boolean => {\n if (clause.field === 'formIds') return true\n if (clause.field !== CONTACT_FACET_KEYS_PATH || !groupId) return false\n return crmClauseValues(clause).every((key) => key.startsWith(`${groupId}:`))\n }\n}\n\n/**\n * The query shapes the Contacts list can send, as declarations the index\n * pin enumerates (AGL-3321): the list's own, without its solo ranges, whose\n * every equality rides `(field, updatedAt DESC)`; and each solo range beside\n * the scope clause alone.\n */\nexport function contactIndexShapes(): ListQueryDeclaration[] {\n const soloOps = new Set(['startsWith', 'endsWith'])\n const main: ListQueryDeclaration = {\n ...CONTACT_LIST_DECLARATION,\n fields: CONTACT_QUERY_FIELDS.filter((field) => field !== CREATED_FIELD).map((field) =>\n field.operators\n ? { ...field, operators: field.operators.filter((op) => !soloOps.has(op)) }\n : field,\n ),\n }\n const solo: ListQueryDeclaration = {\n fields: [\n { ...NAME_FIELD, operators: ['startsWith', 'endsWith'] },\n { ...EMAIL_FIELD, operators: ['startsWith'] },\n CREATED_FIELD,\n ],\n sorts: [],\n }\n return [main, solo]\n}\n"],"names":["CRM_CONTACT_VIEW_FIELDS","CRM_FACET_KEY_ANY_GROUP","crmContactCustomColumn","crmFacetKey","crmFacetKeyValue","EMAIL_STATE_STATUSES","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","NAME_FIELD","column","kind","path","lowerPath","tokensPath","reversedPath","operators","EMAIL_FIELD","presence","CREATED_FIELD","createdAt","FORM_FIELD","verbatimTokens","CONTACT_LEAD_SOURCE_COLUMN","CONTACT_LIST_FILTER_FIELDS","tags","orders","ltv","updatedAt","storedAs","owner","stage","source","keysOf","company","contactCustomFilterFields","definitions","filter","definition","retiredAt","map","key","type","contactCustomFilterHeaders","Object","fromEntries","label","CONTACT_LIST_FILTER_HEADERS","name","email","formIds","hostId","ordersCount","ltvCents","nextTaskAtMs","emailState","CONTACT_LIST_SORTS","direction","CONTACT_FACET_KEYS_PATH","CONTACT_QUERY_FIELDS","CONTACT_LIST_DECLARATION","fields","sorts","search","AS_STORED","Set","contactSoloClause","clause","field","op","CONTACT_PREFIX_SEARCH","notice","FACET_FIELDS","LIMITED_ACCESS","facetFieldOf","startsWith","custom","slice","length","contactQueryClause","reader","has","foldsScope","refused","value","values","some","includes","facet","group","groupId","keys","Number","push","asked","contactClauseImpliesScope","every","contactIndexShapes","soloOps","main","solo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/contact-filters.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 {\n type ContactFieldDefinition,\n CRM_CONTACT_VIEW_FIELDS,\n CRM_FACET_KEY_ANY_GROUP,\n type CrmFacetKeyField,\n crmContactCustomColumn,\n crmFacetKey,\n crmFacetKeyValue,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { EMAIL_STATE_STATUSES } from '@aglyn/aglyn/app-utils/email-state'\nimport type {\n ListFilterField,\n ListFilterRequest,\n} from '@aglyn/shared-util-tools/list-query/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n} from '../model/crm-list-query'\n\n/*==========================================\n * CONTACTS (`orgs/{orgId}/contacts`, read scoped to a host) — WHAT THE\n * PANEL OFFERS, AND WHAT THE QUERY ASKS (AGL-3321).\n *\n * Every clause and the search word are on the list's one Firestore query,\n * newest change first, paged by it. A contact is one row shared by every\n * holder, and most of what a holder filters by — the owner, the stage, the\n * sources, the company, the tags, a custom value, whether the person has\n * bought — lives on that holder's FACET, a path per holder no index can\n * reach. So every writer stamps `facetKeys` (`crmContactFacetKeys` in\n * `@aglyn/aglyn`): `{group}:{field}={value}` for each holder's value,\n * `{group}:{field}` for \"has one\", and the same under `*` for any holder.\n * Under a site a facet clause asks the viewing holder's key; at the\n * organization level, any holder's.\n *\n * What one query holds decides the rest:\n *\n * - ONE array clause. The scope clause (`visibleTo array-contains-any`),\n * the search (`searchTokens`), a name word (`nameTokens`), a form\n * (`formIds`) and a facet clause (`facetKeys`) all are one. The search\n * folds into the scope clause for a reader who may drop it; a facet or\n * form clause stands in the scope clause's place for that reader, since\n * only a record the viewing holder keeps carries its key. Two of them\n * at once is refused by name.\n * - ONE range. `updatedAt`, the field the list is ordered by, composes\n * with anything; Created and a name or address prefix order the list by\n * their own field and so stand alone beside the scope clause\n * (`contactSoloClause`), served by the `(visibleTo, field)` composites.\n * - equalities (the address, the site, the verdict, \"No next activity\")\n * compose freely, one `(field, updatedAt DESC)` composite each.\n *\n * The per-holder FIGURES (orders, lifetime value) live on each holder's\n * facet, where no range reaches — a top-level `ordersCount` was never\n * written — so they offer \"is not empty\" alone: \"has bought\".\n *\n * A collaborator's search is the start of the name (`CONTACT_PREFIX_SEARCH`).\n *=========================================*/\n\n/**\n * A word of the canonical name (`nameTokens`), the whole of it, or its start\n * or end — `nameLower`, which every contact carries, and `nameReversed`.\n */\nconst NAME_FIELD: ListFilterField = {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n tokensPath: 'nameTokens',\n reversedPath: 'nameReversed',\n operators: ['contains', 'equals', 'startsWith', 'endsWith'],\n}\n\n/**\n * The address. `normalizeContactEmail` lower-cases before every write, so the\n * stored value IS its own normalized key.\n */\nconst EMAIL_FIELD: ListFilterField = {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n}\n\n/** When the person was first captured — a range the list is then ordered by. */\nconst CREATED_FIELD: ListFilterField = {\n column: CRM_CONTACT_VIEW_FIELDS.createdAt,\n kind: 'date',\n path: 'createdAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n}\n\n/**\n * The forms a person came in through — the top-level `formIds` mirror of the\n * interactions' `formId` (AGL-2612), matched whole and as typed because a\n * form id is minted with mixed case. The form's own page links here with its\n * id; nobody types one.\n */\nconst FORM_FIELD: ListFilterField = {\n column: 'formIds',\n kind: 'text',\n path: 'formIds',\n tokensPath: 'formIds',\n verbatimTokens: true,\n operators: ['contains'],\n}\n\n/** The column a contact's lead source filters as (AGL-3511). */\nexport const CONTACT_LEAD_SOURCE_COLUMN = 'leadSource'\n\n/** What the Filters panel offers on a contact, in the grid's grammar. */\nexport const CONTACT_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n {\n // A holder's tags, matched whole and case-insensitively.\n column: CRM_CONTACT_VIEW_FIELDS.tags,\n kind: 'exact',\n path: 'tags',\n operators: ['contains', 'isAnyOf', 'isNotEmpty'],\n },\n FORM_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n // Per-holder figures: \"has bought\", never a range across holders.\n {\n column: CRM_CONTACT_VIEW_FIELDS.orders,\n kind: 'number',\n path: 'ordersCount',\n operators: ['isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.ltv,\n kind: 'number',\n path: 'ltvCents',\n operators: ['isNotEmpty'],\n },\n CREATED_FIELD,\n {\n column: CRM_CONTACT_VIEW_FIELDS.updatedAt,\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n /*\n * \"No next activity\" (AGL-2661): `nextTaskAtMs == null`, which every\n * contact carries from its creation until a task is scheduled against it.\n */\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n /*\n * THE FACET FIELDS (AGL-2617). The values are picked, not typed — a uid\n * from the roster, a stage from the fixed list, a source from its labels,\n * a company from the picker — so `equals` and `isAnyOf` are what they\n * offer; the section supplies the choices. The column names are the\n * contract `CRM_CONTACT_VIEW_FIELDS` states, because the dynamic-list\n * translator reads a view by them. The paths are the flattened ROW'S.\n */\n {\n column: CRM_CONTACT_VIEW_FIELDS.owner,\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.stage,\n kind: 'exact',\n path: 'lifecycleStage',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n // The `sources` presence map, matched on its keys — see `keysOf`.\n column: CRM_CONTACT_VIEW_FIELDS.source,\n kind: 'exact',\n path: 'sources',\n keysOf: true,\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.company,\n kind: 'exact',\n path: 'companyId',\n operators: ['equals', 'isNotEmpty'],\n },\n /*\n * A holder's lead source (AGL-3511), picked from the org's Lead source\n * values and asked through its `leadSource` facet key — the label as the\n * picklist compares it.\n */\n {\n column: CONTACT_LEAD_SOURCE_COLUMN,\n kind: 'exact',\n path: 'leadSource',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n /*\n * The verdict on the address (AGL-3245), asked through `emailStatus` —\n * `none` for a person nothing has been said about, which is what \"is\n * empty\" asks.\n */\n {\n column: 'emailState',\n kind: 'exact',\n path: 'emailState.status',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty', 'isNotEmpty'],\n },\n]\n\n/**\n * One filter field per active custom contact field (AGL-2617). A custom\n * value lives under the holder's `custom` map, so it is asked through the\n * holder's facet key: equality on a picked, typed or ticked value, and\n * \"is not empty\". Its column is the one the table shows it under —\n * `crmContactCustomColumn(key)` — so a clause and a column agree on the name.\n */\nexport function contactCustomFilterFields(\n definitions: readonly Pick<\n ContactFieldDefinition,\n 'key' | 'type' | 'retiredAt'\n >[],\n): ListFilterField[] {\n return definitions\n .filter((definition) => !definition.retiredAt)\n .map((definition): ListFilterField => {\n const column = crmContactCustomColumn(definition.key)\n const path = `custom.${definition.key}`\n switch (definition.type) {\n case 'number':\n return {\n column,\n kind: 'number',\n path,\n operators: ['=', 'isNotEmpty'],\n }\n case 'date':\n return { column, kind: 'date', path, operators: ['isNotEmpty'] }\n case 'checkbox':\n return { column, kind: 'boolean', path }\n case 'select':\n return {\n column,\n kind: 'exact',\n path,\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n }\n default:\n return {\n column,\n kind: 'text',\n path,\n lowerPath: path,\n operators: ['equals', 'isNotEmpty'],\n }\n }\n })\n}\n\n/** The custom fields' headers, keyed by the column each filters as. */\nexport function contactCustomFilterHeaders(\n definitions: readonly Pick<ContactFieldDefinition, 'key' | 'label'>[],\n): Record<string, string> {\n return Object.fromEntries(\n definitions.map((definition) => [\n crmContactCustomColumn(definition.key),\n definition.label || definition.key,\n ]),\n )\n}\n\n/**\n * How every filterable contact field reads — on a chip, in the add-filter\n * picker, and as the header of a filter-only hidden column.\n */\nexport const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Contact',\n email: 'Email',\n tags: 'Tags',\n formIds: 'Form ID',\n hostId: 'Site ID',\n ordersCount: 'Orders',\n ltvCents: 'Lifetime value (cents)',\n createdAt: 'Created',\n updatedAt: 'Updated',\n nextTaskAtMs: 'Next activity',\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'Owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'Stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'Source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'Company',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'Lead source',\n emailState: 'Email',\n}\n\n/*------------------------------------------\n * THE QUERY THE CONTACTS LIST RUNS.\n *-----------------------------------------*/\n\n/**\n * The list's orders: newest change first, then each header the query can\n * order by (AGL-3680) — the person's name or address (`nameSortKey`), the\n * Last activity column's `updatedAt` the other way, the next activity and\n * the address verdict, every one stored on every contact. Each is `alone`:\n * served with no filter or search on, beside the scope clause by one\n * `(visibleTo, field)` composite per direction. The other columns — a\n * holder's owner, stage, sources, tags, last engagement and custom values,\n * which live on a facet, and the sites that know the person — sort the page.\n */\nexport const CONTACT_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc', column: 'updatedAt', label: 'Last activity' },\n { path: 'updatedAt', direction: 'asc', column: 'updatedAt', label: 'Last activity', alone: true },\n { path: 'nameSortKey', direction: 'asc', column: 'name', label: 'Contact', alone: true },\n { path: 'nameSortKey', direction: 'desc', column: 'name', label: 'Contact', alone: true },\n { path: 'nextTaskAtMs', direction: 'asc', column: 'nextTaskAtMs', label: 'Next activity', alone: true },\n { path: 'nextTaskAtMs', direction: 'desc', column: 'nextTaskAtMs', label: 'Next activity', alone: true },\n { path: 'emailStatus', direction: 'asc', column: 'emailState', label: 'Email', alone: true },\n { path: 'emailStatus', direction: 'desc', column: 'emailState', label: 'Email', alone: true },\n]\n\n/** The stored array a facet clause is asked of. */\nexport const CONTACT_FACET_KEYS_PATH = 'facetKeys'\n\n/** The fields the Contacts query asks — each a stored field, as named. */\nexport const CONTACT_QUERY_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n FORM_FIELD,\n CREATED_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n {\n column: 'updatedAt',\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n {\n column: 'emailStatus',\n kind: 'exact',\n path: 'emailStatus',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CONTACT_FACET_KEYS_PATH,\n kind: 'exact',\n path: CONTACT_FACET_KEYS_PATH,\n tokensPath: CONTACT_FACET_KEYS_PATH,\n operators: ['contains', 'isAnyOf'],\n },\n]\n\nexport const CONTACT_LIST_DECLARATION: ListQueryDeclaration = {\n fields: CONTACT_QUERY_FIELDS,\n sorts: CONTACT_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The grid fields a stored clause passes to the query unchanged. */\nconst AS_STORED = new Set([\n 'name',\n 'email',\n 'formIds',\n 'hostId',\n 'createdAt',\n 'updatedAt',\n 'nextTaskAtMs',\n])\n\n/**\n * The ranges that stand ALONE beside the scope clause: Created and a name\n * or address prefix each order the list by their own field, which the\n * `(visibleTo, field)` composites serve and nothing beside them. Updated is\n * not one: the list is ordered by it already, so it composes freely.\n */\nexport function contactSoloClause(clause: ListFilterRequest): boolean {\n if (clause.field === 'createdAt') return true\n if (clause.field === 'name') return clause.op === 'startsWith' || clause.op === 'endsWith'\n return clause.field === 'email' && clause.op === 'startsWith'\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the contact's name, beside the scope clause.\n */\nexport const CONTACT_PREFIX_SEARCH = {\n field: NAME_FIELD,\n notice: 'Search matches the start of a contact’s name for access limited to specific sites.',\n}\n\n/** The facet field each grid column is keyed under. */\nconst FACET_FIELDS: Readonly<Record<string, CrmFacetKeyField>> = {\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'company',\n [CRM_CONTACT_VIEW_FIELDS.tags]: 'tag',\n [CRM_CONTACT_VIEW_FIELDS.orders]: 'orders',\n [CRM_CONTACT_VIEW_FIELDS.ltv]: 'ltv',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'leadSource',\n}\n\n/** Who is asking: the viewing holder, and whether their query may drop the scope clause. */\nexport interface ContactQueryReader {\n /** The viewing holder's group id under a site; `null` at the organization level. */\n groupId: string | null\n /**\n * Whether the reader's query may stand a facet or form clause in the\n * scope clause's place (`useCrmFoldsScope`) — always at the organization\n * level, where there is no scope clause.\n */\n foldsScope: boolean\n}\n\n/** Why a reader whose access is some sites cannot filter by a holder's field. */\nconst LIMITED_ACCESS =\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it'\n\n/** The facet field a grid column names, or null for one that is not a facet field. */\nfunction facetFieldOf(column: string): CrmFacetKeyField | null {\n if (FACET_FIELDS[column]) return FACET_FIELDS[column]\n if (column.startsWith(CRM_CONTACT_VIEW_FIELDS.custom)) {\n return `custom.${column.slice(CRM_CONTACT_VIEW_FIELDS.custom.length)}`\n }\n return null\n}\n\n/**\n * One grid clause as the clause the Contacts query asks — see the block at\n * the top. A clause a query cannot serve for this reader is refused by\n * name, never matched over the rows a page loaded.\n */\nexport function contactQueryClause(\n clause: ListFilterRequest,\n reader: ContactQueryReader,\n): CrmClauseAsked {\n if (AS_STORED.has(clause.field)) {\n if (clause.field === 'formIds' && !reader.foldsScope)\n return { refused: LIMITED_ACCESS }\n return { field: clause.field, op: clause.op, value: clause.value }\n }\n if (clause.field === 'emailState') {\n if (clause.op === 'isEmpty')\n return { field: 'emailStatus', op: 'equals', value: 'none' }\n if (clause.op === 'isNotEmpty')\n return crmAnyOf('emailStatus', EMAIL_STATE_STATUSES)\n const values = crmClauseValues(clause)\n if (\n values.some(\n (value) => !(EMAIL_STATE_STATUSES as readonly string[]).includes(value),\n )\n ) {\n return { refused: 'pick one of the choices' }\n }\n return crmAnyOf('emailStatus', values)\n }\n const facet = facetFieldOf(clause.field)\n if (!facet) return { refused: 'this list does not filter by that' }\n if (!reader.foldsScope) return { refused: LIMITED_ACCESS }\n const group = reader.groupId ?? CRM_FACET_KEY_ANY_GROUP\n if (clause.op === 'isNotEmpty') {\n return {\n field: CONTACT_FACET_KEYS_PATH,\n op: 'contains',\n value: crmFacetKey(group, facet),\n }\n }\n const keys: string[] = []\n for (const value of crmClauseValues(clause)) {\n const key = crmFacetKeyValue(clause.op === '=' ? Number(value) : value)\n if (key === null) return { refused: 'no value yet' }\n keys.push(crmFacetKey(group, facet, key))\n }\n const asked = crmAnyOf(CONTACT_FACET_KEYS_PATH, keys)\n // One key is an `array-contains`; several, an `array-contains-any`.\n return 'refused' in asked || asked.op === 'isAnyOf'\n ? asked\n : { ...asked, op: 'contains' }\n}\n\n/**\n * Whether an asked clause stands in for the scope clause: a facet key of\n * the viewing holder, which only a record that holder keeps carries, or a\n * form, which only its site's captures carry.\n */\nexport function contactClauseImpliesScope(groupId: string | null) {\n return (clause: ListFilterRequest): boolean => {\n if (clause.field === 'formIds') return true\n if (clause.field !== CONTACT_FACET_KEYS_PATH || !groupId) return false\n return crmClauseValues(clause).every((key) => key.startsWith(`${groupId}:`))\n }\n}\n\n/**\n * The query shapes the Contacts list can send, as declarations the index\n * pin enumerates (AGL-3321): the list's own, without its solo ranges, whose\n * every equality rides `(field, updatedAt DESC)`; and each solo range beside\n * the scope clause alone.\n */\nexport function contactIndexShapes(): ListQueryDeclaration[] {\n const soloOps = new Set(['startsWith', 'endsWith'])\n const main: ListQueryDeclaration = {\n ...CONTACT_LIST_DECLARATION,\n fields: CONTACT_QUERY_FIELDS.filter((field) => field !== CREATED_FIELD).map((field) =>\n field.operators\n ? { ...field, operators: field.operators.filter((op) => !soloOps.has(op)) }\n : field,\n ),\n }\n const solo: ListQueryDeclaration = {\n fields: [\n { ...NAME_FIELD, operators: ['startsWith', 'endsWith'] },\n { ...EMAIL_FIELD, operators: ['startsWith'] },\n CREATED_FIELD,\n ],\n sorts: [],\n }\n return [main, solo]\n}\n"],"names":["CRM_CONTACT_VIEW_FIELDS","CRM_FACET_KEY_ANY_GROUP","crmContactCustomColumn","crmFacetKey","crmFacetKeyValue","EMAIL_STATE_STATUSES","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","NAME_FIELD","column","kind","path","lowerPath","tokensPath","reversedPath","operators","EMAIL_FIELD","presence","CREATED_FIELD","createdAt","FORM_FIELD","verbatimTokens","CONTACT_LEAD_SOURCE_COLUMN","CONTACT_LIST_FILTER_FIELDS","tags","orders","ltv","updatedAt","storedAs","owner","stage","source","keysOf","company","contactCustomFilterFields","definitions","filter","definition","retiredAt","map","key","type","contactCustomFilterHeaders","Object","fromEntries","label","CONTACT_LIST_FILTER_HEADERS","name","email","formIds","hostId","ordersCount","ltvCents","nextTaskAtMs","emailState","CONTACT_LIST_SORTS","direction","alone","CONTACT_FACET_KEYS_PATH","CONTACT_QUERY_FIELDS","CONTACT_LIST_DECLARATION","fields","sorts","search","AS_STORED","Set","contactSoloClause","clause","field","op","CONTACT_PREFIX_SEARCH","notice","FACET_FIELDS","LIMITED_ACCESS","facetFieldOf","startsWith","custom","slice","length","contactQueryClause","reader","has","foldsScope","refused","value","values","some","includes","facet","group","groupId","keys","Number","push","asked","contactClauseImpliesScope","every","contactIndexShapes","soloOps","main","solo"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAEEA,uBAAuB,EACvBC,uBAAuB,EAEvBC,sBAAsB,EACtBC,WAAW,EACXC,gBAAgB,QACX,6BAA4B;AACnC,SAASC,oBAAoB,QAAQ,qCAAoC;AASzE,SACEC,eAAe,EAEfC,QAAQ,EACRC,eAAe,QACV,6BAAyB;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAoC2C,GAE3C;;;CAGC,GACD,MAAMC,aAA8B;IAClCC,QAAQ;IACRC,MAAM;IACNC,MAAM;IACNC,WAAW;IACXC,YAAY;IACZC,cAAc;IACdC,WAAW;QAAC;QAAY;QAAU;QAAc;KAAW;AAC7D;AAEA;;;CAGC,GACD,MAAMC,cAA+B;IACnCP,QAAQ;IACRC,MAAM;IACNC,MAAM;IACNC,WAAW;IACXK,UAAU;IACVF,WAAW;QAAC;QAAU;KAAa;AACrC;AAEA,8EAA8E,GAC9E,MAAMG,gBAAiC;IACrCT,QAAQV,wBAAwBoB,SAAS;IACzCT,MAAM;IACNC,MAAM;IACNM,UAAU;IACVF,WAAW;QAAC;QAAM;QAAS;QAAa;QAAU;KAAa;AACjE;AAEA;;;;;CAKC,GACD,MAAMK,aAA8B;IAClCX,QAAQ;IACRC,MAAM;IACNC,MAAM;IACNE,YAAY;IACZQ,gBAAgB;IAChBN,WAAW;QAAC;KAAW;AACzB;AAEA,8DAA8D,GAC9D,OAAO,MAAMO,6BAA6B,aAAY;AAEtD,uEAAuE,GACvE,OAAO,MAAMC,6BAAyD;IACpEf;IACAQ;IACA;QACE,yDAAyD;QACzDP,QAAQV,wBAAwByB,IAAI;QACpCd,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAY;YAAW;SAAa;IAClD;IACAK;IACA;QAAEX,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAUM,UAAU;IAAS;IACtE,kEAAkE;IAClE;QACER,QAAQV,wBAAwB0B,MAAM;QACtCf,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;SAAa;IAC3B;IACA;QACEN,QAAQV,wBAAwB2B,GAAG;QACnChB,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;SAAa;IAC3B;IACAG;IACA;QACET,QAAQV,wBAAwB4B,SAAS;QACzCjB,MAAM;QACNC,MAAM;QACNM,UAAU;QACVF,WAAW;YAAC;YAAM;YAAS;YAAa;YAAU;SAAa;IACjE;IACA;;;GAGC,GACD;QACEN,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNM,UAAU;QACVW,UAAU;QACVb,WAAW;YAAC;SAAU;IACxB;IACA;;;;;;;GAOC,GACD;QACEN,QAAQV,wBAAwB8B,KAAK;QACrCnB,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAU;YAAW;SAAa;IAChD;IACA;QACEN,QAAQV,wBAAwB+B,KAAK;QACrCpB,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAU;YAAW;SAAa;IAChD;IACA;QACE,kEAAkE;QAClEN,QAAQV,wBAAwBgC,MAAM;QACtCrB,MAAM;QACNC,MAAM;QACNqB,QAAQ;QACRjB,WAAW;YAAC;YAAU;SAAU;IAClC;IACA;QACEN,QAAQV,wBAAwBkC,OAAO;QACvCvB,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAU;SAAa;IACrC;IACA;;;;GAIC,GACD;QACEN,QAAQa;QACRZ,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAU;YAAW;SAAa;IAChD;IACA;;;;GAIC,GACD;QACEN,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNM,UAAU;QACVF,WAAW;YAAC;YAAU;YAAW;YAAW;SAAa;IAC3D;CACD,CAAA;AAED;;;;;;CAMC,GACD,OAAO,SAASmB,0BACdC,WAGG;IAEH,OAAOA,YACJC,MAAM,CAAC,CAACC,aAAe,CAACA,WAAWC,SAAS,EAC5CC,GAAG,CAAC,CAACF;QACJ,MAAM5B,SAASR,uBAAuBoC,WAAWG,GAAG;QACpD,MAAM7B,OAAO,CAAC,OAAO,EAAE0B,WAAWG,GAAG,EAAE;QACvC,OAAQH,WAAWI,IAAI;YACrB,KAAK;gBACH,OAAO;oBACLhC;oBACAC,MAAM;oBACNC;oBACAI,WAAW;wBAAC;wBAAK;qBAAa;gBAChC;YACF,KAAK;gBACH,OAAO;oBAAEN;oBAAQC,MAAM;oBAAQC;oBAAMI,WAAW;wBAAC;qBAAa;gBAAC;YACjE,KAAK;gBACH,OAAO;oBAAEN;oBAAQC,MAAM;oBAAWC;gBAAK;YACzC,KAAK;gBACH,OAAO;oBACLF;oBACAC,MAAM;oBACNC;oBACAI,WAAW;wBAAC;wBAAU;wBAAW;qBAAa;gBAChD;YACF;gBACE,OAAO;oBACLN;oBACAC,MAAM;oBACNC;oBACAC,WAAWD;oBACXI,WAAW;wBAAC;wBAAU;qBAAa;gBACrC;QACJ;IACF;AACJ;AAEA,qEAAqE,GACrE,OAAO,SAAS2B,2BACdP,WAAqE;IAErE,OAAOQ,OAAOC,WAAW,CACvBT,YAAYI,GAAG,CAAC,CAACF,aAAe;YAC9BpC,uBAAuBoC,WAAWG,GAAG;YACrCH,WAAWQ,KAAK,IAAIR,WAAWG,GAAG;SACnC;AAEL;AAEA;;;CAGC,GACD,OAAO,MAAMM,8BAAgE;IAC3EC,MAAM;IACNC,OAAO;IACPxB,MAAM;IACNyB,SAAS;IACTC,QAAQ;IACRC,aAAa;IACbC,UAAU;IACVjC,WAAW;IACXQ,WAAW;IACX0B,cAAc;IACd,CAACtD,wBAAwB8B,KAAK,CAAC,EAAE;IACjC,CAAC9B,wBAAwB+B,KAAK,CAAC,EAAE;IACjC,CAAC/B,wBAAwBgC,MAAM,CAAC,EAAE;IAClC,CAAChC,wBAAwBkC,OAAO,CAAC,EAAE;IACnC,CAACX,2BAA2B,EAAE;IAC9BgC,YAAY;AACd,EAAC;AAED;;2CAE2C,GAE3C;;;;;;;;;CASC,GACD,OAAO,MAAMC,qBAA+C;IAC1D;QAAE5C,MAAM;QAAa6C,WAAW;QAAQ/C,QAAQ;QAAaoC,OAAO;IAAgB;IACpF;QAAElC,MAAM;QAAa6C,WAAW;QAAO/C,QAAQ;QAAaoC,OAAO;QAAiBY,OAAO;IAAK;IAChG;QAAE9C,MAAM;QAAe6C,WAAW;QAAO/C,QAAQ;QAAQoC,OAAO;QAAWY,OAAO;IAAK;IACvF;QAAE9C,MAAM;QAAe6C,WAAW;QAAQ/C,QAAQ;QAAQoC,OAAO;QAAWY,OAAO;IAAK;IACxF;QAAE9C,MAAM;QAAgB6C,WAAW;QAAO/C,QAAQ;QAAgBoC,OAAO;QAAiBY,OAAO;IAAK;IACtG;QAAE9C,MAAM;QAAgB6C,WAAW;QAAQ/C,QAAQ;QAAgBoC,OAAO;QAAiBY,OAAO;IAAK;IACvG;QAAE9C,MAAM;QAAe6C,WAAW;QAAO/C,QAAQ;QAAcoC,OAAO;QAASY,OAAO;IAAK;IAC3F;QAAE9C,MAAM;QAAe6C,WAAW;QAAQ/C,QAAQ;QAAcoC,OAAO;QAASY,OAAO;IAAK;CAC7F,CAAA;AAED,iDAAiD,GACjD,OAAO,MAAMC,0BAA0B,YAAW;AAElD,wEAAwE,GACxE,OAAO,MAAMC,uBAAmD;IAC9DnD;IACAQ;IACAI;IACAF;IACA;QAAET,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAUM,UAAU;IAAS;IACtE;QACER,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNM,UAAU;QACVF,WAAW;YAAC;YAAM;YAAS;YAAa;YAAU;SAAa;IACjE;IACA;QACEN,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNM,UAAU;QACVW,UAAU;QACVb,WAAW;YAAC;SAAU;IACxB;IACA;QACEN,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNI,WAAW;YAAC;YAAU;SAAU;IAClC;IACA;QACEN,QAAQiD;QACRhD,MAAM;QACNC,MAAM+C;QACN7C,YAAY6C;QACZ3C,WAAW;YAAC;YAAY;SAAU;IACpC;CACD,CAAA;AAED,OAAO,MAAM6C,2BAAiD;IAC5DC,QAAQF;IACRG,OAAOP;IACPQ,QAAQ1D;AACV,EAAC;AAED,mEAAmE,GACnE,MAAM2D,YAAY,IAAIC,IAAI;IACxB;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED;;;;;CAKC,GACD,OAAO,SAASC,kBAAkBC,MAAyB;IACzD,IAAIA,OAAOC,KAAK,KAAK,aAAa,OAAO;IACzC,IAAID,OAAOC,KAAK,KAAK,QAAQ,OAAOD,OAAOE,EAAE,KAAK,gBAAgBF,OAAOE,EAAE,KAAK;IAChF,OAAOF,OAAOC,KAAK,KAAK,WAAWD,OAAOE,EAAE,KAAK;AACnD;AAEA;;;CAGC,GACD,OAAO,MAAMC,wBAAwB;IACnCF,OAAO5D;IACP+D,QAAQ;AACV,EAAC;AAED,qDAAqD,GACrD,MAAMC,eAA2D;IAC/D,CAACzE,wBAAwB8B,KAAK,CAAC,EAAE;IACjC,CAAC9B,wBAAwB+B,KAAK,CAAC,EAAE;IACjC,CAAC/B,wBAAwBgC,MAAM,CAAC,EAAE;IAClC,CAAChC,wBAAwBkC,OAAO,CAAC,EAAE;IACnC,CAAClC,wBAAwByB,IAAI,CAAC,EAAE;IAChC,CAACzB,wBAAwB0B,MAAM,CAAC,EAAE;IAClC,CAAC1B,wBAAwB2B,GAAG,CAAC,EAAE;IAC/B,CAACJ,2BAA2B,EAAE;AAChC;AAcA,+EAA+E,GAC/E,MAAMmD,iBACJ;AAEF,oFAAoF,GACpF,SAASC,aAAajE,MAAc;IAClC,IAAI+D,YAAY,CAAC/D,OAAO,EAAE,OAAO+D,YAAY,CAAC/D,OAAO;IACrD,IAAIA,OAAOkE,UAAU,CAAC5E,wBAAwB6E,MAAM,GAAG;QACrD,OAAO,CAAC,OAAO,EAAEnE,OAAOoE,KAAK,CAAC9E,wBAAwB6E,MAAM,CAACE,MAAM,GAAG;IACxE;IACA,OAAO;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASC,mBACdZ,MAAyB,EACzBa,MAA0B;QAyBZA;IAvBd,IAAIhB,UAAUiB,GAAG,CAACd,OAAOC,KAAK,GAAG;QAC/B,IAAID,OAAOC,KAAK,KAAK,aAAa,CAACY,OAAOE,UAAU,EAClD,OAAO;YAAEC,SAASV;QAAe;QACnC,OAAO;YAAEL,OAAOD,OAAOC,KAAK;YAAEC,IAAIF,OAAOE,EAAE;YAAEe,OAAOjB,OAAOiB,KAAK;QAAC;IACnE;IACA,IAAIjB,OAAOC,KAAK,KAAK,cAAc;QACjC,IAAID,OAAOE,EAAE,KAAK,WAChB,OAAO;YAAED,OAAO;YAAeC,IAAI;YAAUe,OAAO;QAAO;QAC7D,IAAIjB,OAAOE,EAAE,KAAK,cAChB,OAAO/D,SAAS,eAAeF;QACjC,MAAMiF,SAAS9E,gBAAgB4D;QAC/B,IACEkB,OAAOC,IAAI,CACT,CAACF,QAAU,CAAC,AAAChF,qBAA2CmF,QAAQ,CAACH,SAEnE;YACA,OAAO;gBAAED,SAAS;YAA0B;QAC9C;QACA,OAAO7E,SAAS,eAAe+E;IACjC;IACA,MAAMG,QAAQd,aAAaP,OAAOC,KAAK;IACvC,IAAI,CAACoB,OAAO,OAAO;QAAEL,SAAS;IAAoC;IAClE,IAAI,CAACH,OAAOE,UAAU,EAAE,OAAO;QAAEC,SAASV;IAAe;IACzD,MAAMgB,SAAQT,kBAAAA,OAAOU,OAAO,YAAdV,kBAAkBhF;IAChC,IAAImE,OAAOE,EAAE,KAAK,cAAc;QAC9B,OAAO;YACLD,OAAOV;YACPW,IAAI;YACJe,OAAOlF,YAAYuF,OAAOD;QAC5B;IACF;IACA,MAAMG,OAAiB,EAAE;IACzB,KAAK,MAAMP,SAAS7E,gBAAgB4D,QAAS;QAC3C,MAAM3B,MAAMrC,iBAAiBgE,OAAOE,EAAE,KAAK,MAAMuB,OAAOR,SAASA;QACjE,IAAI5C,QAAQ,MAAM,OAAO;YAAE2C,SAAS;QAAe;QACnDQ,KAAKE,IAAI,CAAC3F,YAAYuF,OAAOD,OAAOhD;IACtC;IACA,MAAMsD,QAAQxF,SAASoD,yBAAyBiC;IAChD,oEAAoE;IACpE,OAAO,aAAaG,SAASA,MAAMzB,EAAE,KAAK,YACtCyB,QACA,aAAKA;QAAOzB,IAAI;;AACtB;AAEA;;;;CAIC,GACD,OAAO,SAAS0B,0BAA0BL,OAAsB;IAC9D,OAAO,CAACvB;QACN,IAAIA,OAAOC,KAAK,KAAK,WAAW,OAAO;QACvC,IAAID,OAAOC,KAAK,KAAKV,2BAA2B,CAACgC,SAAS,OAAO;QACjE,OAAOnF,gBAAgB4D,QAAQ6B,KAAK,CAAC,CAACxD,MAAQA,IAAImC,UAAU,CAAC,GAAGe,QAAQ,CAAC,CAAC;IAC5E;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASO;IACd,MAAMC,UAAU,IAAIjC,IAAI;QAAC;QAAc;KAAW;IAClD,MAAMkC,OAA6B,aAC9BvC;QACHC,QAAQF,qBAAqBvB,MAAM,CAAC,CAACgC,QAAUA,UAAUlD,eAAeqB,GAAG,CAAC,CAAC6B,QAC3EA,MAAMrD,SAAS,GACX,aAAKqD;gBAAOrD,WAAWqD,MAAMrD,SAAS,CAACqB,MAAM,CAAC,CAACiC,KAAO,CAAC6B,QAAQjB,GAAG,CAACZ;iBACnED;;IAGR,MAAMgC,OAA6B;QACjCvC,QAAQ;YACN,aAAKrD;gBAAYO,WAAW;oBAAC;oBAAc;iBAAW;;YACtD,aAAKC;gBAAaD,WAAW;oBAAC;iBAAa;;YAC3CG;SACD;QACD4C,OAAO,EAAE;IACX;IACA,OAAO;QAACqC;QAAMC;KAAK;AACrB"}
|