@aglyn/plugins-crm 1.0.0-beta.185 → 1.0.0-beta.187

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n CRM_COLLECTIONS,\n type CrmViewFilterClause,\n findOrgMember,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport CrmViewsControl from './crm-views-control'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n Stack,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmTaskRow, useCrmTaskList, useNowMs } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n CRM_TASK_VIEW_LIMIT,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n} from '../model/task-views'\nimport {\n type ListFilterField,\n matchListFilter,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { crmFilterColumns, crmRowMatchesSearch } from '../model/crm-grid-filter'\nimport { useCrmGridFilter } from '../hooks/use-crm-grid-filter'\nimport CrmFilterBar from './crm-filter-bar'\nimport { CrmListToolbar } from './crm-list-toolbar'\nimport {\n TaskDueText,\n TaskKindCell,\n TaskPriorityChip,\n TaskRecordLink,\n} from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TasksCalendar from './tasks-calendar'\nimport { TaskImportButton } from './task-import-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\nimport TasksBulkBar from './tasks-bulk-bar'\n\n/** What an empty view is headed, by view — \"nothing overdue\" is good news. */\n/*\n * What the tasks grid's panel offers (AGL-3313). `view` is the task view\n * the query serves — a hidden column, since it is no field of a task — and\n * the rest narrow the rows that view loaded.\n */\nconst TASK_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'view', kind: 'exact', path: 'view', operators: ['equals'] },\n { column: 'kind', kind: 'exact', path: 'kind', operators: ['equals', 'isAnyOf'] },\n { column: 'priority', kind: 'exact', path: 'priority', operators: ['equals', 'isAnyOf'] },\n { column: 'assigneeUid', kind: 'exact', path: 'assigneeUid', operators: ['equals', 'isAnyOf'] },\n]\nconst TASK_FILTER_HEADERS: Readonly<Record<string, string>> = {\n view: 'Show',\n kind: 'Kind',\n priority: 'Priority',\n assigneeUid: 'Assignee',\n}\n/** The filter-only `view` column never shows. */\nconst TASK_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { view: false }\n/** 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/** What the quick search reads on a task. */\nconst TASK_SEARCH_FIELDS = ['title', 'notes'] as const\n\nconst EMPTY_LABEL: Record<CrmTaskView, string> = {\n mine: 'Nothing is assigned to you',\n overdue: 'Nothing is overdue',\n today: 'Nothing is due today',\n upcoming: 'Nothing is scheduled beyond today',\n open: 'No open tasks',\n done: 'No completed tasks yet',\n}\n\n/** The one sentence under it, by view. */\nconst EMPTY_COPY: Record<CrmTaskView, string> = {\n mine: 'Tasks you create are yours unless you hand them to someone.',\n overdue: 'Every dated task is on or ahead of schedule.',\n today: 'Nothing on your calendar day is owed to anyone in the CRM.',\n upcoming: 'A task with a due date past today would be listed here.',\n open: 'A task is a call, an email, a meeting or a to-do owed to a contact, a company or a deal.',\n done: 'Tasks ticked off on their record, or here, are kept for the history.',\n}\n\n/**\n * `/crm/tasks` — what the team owes the people in the CRM (AGL-2599).\n *\n * Six views over `orgs/{orgId}/crmTasks`, each a bounded listener on one of\n * the collection's indexes, chosen by a segmented control. A task hangs off\n * a contact, a company or a deal, but it is listed HERE because \"what is due\n * today\" is a question about the team's day and not about any one record;\n * the record pages carry their own `RecordTasksCard` for the other\n * direction.\n *\n * Completing goes through `crm/task-complete`, because completing fires the\n * `taskCompleted` host event and only the server can run a workflow.\n * Reopening is client-direct: undoing a tick has no side effect beyond the\n * document, and the rules gate it like any other CRM write.\n *\n * The rows are selectable, and a selection raises `TasksBulkBar` over the\n * list — Complete and Assign through their routes, the due date and the\n * delete as batched writes (AGL-2621). Export CSV beside the view control\n * writes the view through `tasksCsv()`, naming the assignee and the linked\n * records; the bar writes the same file over the selection.\n */\nexport function TasksSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath = '' } = props\n const orgRecord = org as Record<string, unknown> | undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const routes = useMemo(() => crmRoutes(basePath), [basePath])\n // The viewing group a linked contact is named through, or `null` at the\n // organization level, where each is named through its own holder.\n const { consentGroup } = useCrmScope({ hostId, org: orgRecord })\n const groupId = consentGroup?.groupId ?? null\n\n /*\n * Which of the six task views is open is the saved VIEW'S (AGL-2617): a\n * saved view of tasks holds the task view beside the columns and the\n * sort, and the toggle below writes into it. Unset reads as \"My tasks\",\n * which is what the section opened on before views existed.\n */\n const views = useCrmSavedView({ section: 'tasks', hostId, org: orgRecord, basePath })\n const view: CrmTaskView = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'view')?.value\n return CRM_TASK_VIEWS.some((option) => option.id === value)\n ? (value as CrmTaskView)\n : 'mine'\n }, [views.state.filters])\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313). \"Show\" is the task view the query serves — the hidden\n * `view` column, as the toggle stored it, with none meaning \"My tasks\" —\n * and Kind, Priority and Assignee narrow the rows it loaded.\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 = useCrmGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['view', 'kind', 'priority', 'assigneeUid'],\n })\n const list = useCrmTaskList({\n hostId,\n org: orgRecord,\n view,\n uid: user?.uid,\n nowMs,\n })\n const { tasks: loaded, status, fromCache, truncated, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\n const filterOptions = useMemo(\n () => ({\n view: CRM_TASK_VIEWS.map((option) => ({ value: option.id, label: option.label })),\n kind: CRM_TASK_KINDS.map((kind) => ({ value: kind, label: CRM_TASK_KIND_LABELS[kind] })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: CRM_TASK_PRIORITY_LABELS[priority],\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members],\n )\n // Every clause but the served view, and the search, over the loaded rows.\n const searchKey = gridFilter.searchWords.join(' ')\n const tasks = useMemo(\n () =>\n loaded.filter(\n (task) =>\n clauses.every(\n (clause) =>\n clause.field === 'view' || matchListFilter(task, TASK_FILTER_FIELDS, clause),\n ) && crmRowMatchesSearch(task, TASK_SEARCH_FIELDS, gridFilter.searchWords),\n ),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [loaded, clauses, searchKey],\n )\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 is a different set of rows; a selection made on the last one\n // would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [view])\n const csvOptions: TaskCsvOptions = useMemo(\n () => ({\n assigneeEmail: (uid) => {\n const member = findOrgMember(directory.members, uid)\n return member?.email || member?.label || uid\n },\n recordName: nameOf,\n }),\n [directory.members, nameOf],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('tasks.csv', 'text/csv', tasksCsv(tasks, csvOptions))\n }, [tasks, csvOptions])\n\n /*\n * List or month (AGL-2662). A layout choice over the SAME rows and the\n * same view — switching does not narrow anything and costs no read, so\n * it is a toggle rather than another saved view.\n */\n const [layout, setLayout] = useState<'list' | 'calendar'>('list')\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n /*\n * Tick or untick. The two directions are two different writes on purpose\n * — see the module comment — and the row is disabled while either is in\n * flight so a double-click cannot complete a task and reopen it in the\n * same breath.\n */\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n status: 'open',\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n enqueueSnackbar('Task reopened', { variant: 'success' })\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The route runs as the mounted site, which hears the event, or\n // at the organization level as the org — the org variant, which\n // emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'status',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Checkbox\n size=\"small\"\n checked={row.status === 'done'}\n disabled={busyId === row.$id}\n onClick={(event) => {\n event.stopPropagation()\n void toggleDone(row)\n }}\n slotProps={{\n input: {\n 'aria-label':\n row.status === 'done'\n ? `Reopen \"${row.title}\"`\n : `Complete \"${row.title}\"`,\n },\n }}\n />\n ),\n },\n {\n field: 'title',\n headerName: 'Task',\n flex: 2,\n minWidth: 220,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}>\n <Typography\n variant=\"body2\"\n noWrap\n sx={{\n textDecoration: row.status === 'done' ? 'line-through' : undefined,\n color: row.status === 'done' ? 'text.secondary' : undefined,\n }}\n >\n {row.title}\n </Typography>\n {row.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.notes}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => <TaskKindCell kind={row.kind} />,\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip priority={row.priority} />\n ),\n },\n {\n field: 'dueAtMs',\n headerName: 'Due',\n width: 260,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', height: '100%' }}>\n <TaskDueText task={row} nowMs={nowMs} />\n {row.status === 'done' || !scope ? null : (\n <TaskSnoozeMenu\n dueAtMs={row.dueAtMs}\n remindAtMs={row.remindAtMs}\n target={{\n write: {\n scope,\n taskId: row.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: row,\n },\n }}\n disabled={busyId === row.$id}\n />\n )}\n </Stack>\n ),\n },\n {\n field: 'assigneeUid',\n headerName: 'Assignee',\n flex: 1,\n minWidth: 140,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n directory.nameOf(row.assigneeUid),\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography variant=\"body2\" color={row.assigneeUid ? undefined : 'text.secondary'}>\n {row.assigneeUid ? directory.nameOf(row.assigneeUid) : 'Unassigned'}\n </Typography>\n ),\n },\n {\n field: 'record',\n headerName: 'For',\n flex: 1,\n minWidth: 160,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskRecordLink task={row} routes={routes} nameOf={nameOf} />\n ),\n },\n ],\n [busyId, toggleDone, nowMs, directory, routes, nameOf, scope],\n )\n /* The column and sort models are the view's (AGL-2617). */\n const filterColumns = useMemo(\n () => crmFilterColumns(columns, TASK_FILTER_FIELDS, filterOptions, TASK_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS)\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#the-tasks-page',\n excerpt:\n 'Calls, emails, meetings and to-dos, each linked to the contact, ' +\n 'company or deal it is for. Overdue and today are read off the ' +\n 'due date as you look.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n {/*\n The saved view, and the clauses narrowing it as chips — \"Show\"\n among them — set in the grid's own Filters panel (AGL-3313).\n */}\n <CrmListToolbar label=\"Task filters\">\n <CrmViewsControl controller={views} allLabel=\"All tasks\" />\n <CrmFilterBar\n fields={TASK_FILTER_FIELDS}\n headers={TASK_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n servedField=\"view\"\n />\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={layout}\n onChange={(_event, next) => {\n if (next) setLayout(next as 'list' | 'calendar')\n }}\n aria-label=\"Task layout\"\n >\n <ToggleButton value=\"list\">{'List'}</ToggleButton>\n <ToggleButton value=\"calendar\">{'Calendar'}</ToggleButton>\n </ToggleButtonGroup>\n <Stack sx={{ flex: 1 }} />\n <TaskImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!tasks.length}>\n {'Export CSV'}\n </Button>\n </CrmListToolbar>\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded. Reload to try again.'}\n </Typography>\n ) : status === 'success' && !loaded.length ? (\n <EmptyStateComponent\n label={EMPTY_LABEL[view]}\n description={EMPTY_COPY[view]}\n action={\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n }\n />\n ) : (\n <>\n {truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Showing the first ${CRM_TASK_VIEW_LIMIT} — narrow the view to see the rest.`}\n </Typography>\n ) : null}\n {/*\n The calendar draws the same rows, so there is no bulk bar\n over it: a selection is a list gesture, and a month grid\n offers nowhere to make one.\n */}\n {layout === 'calendar' ? (\n <TasksCalendar\n tasks={tasks}\n truncated={truncated}\n onOpen={(task) => setDrawer({ open: true, task })}\n />\n ) : (\n <>\n <TasksBulkBar\n hostId={hostId}\n scope={scope}\n rows={tasks}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n directory={directory}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tasks}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n loading={status === 'loading'}\n onOpen={(id) => {\n const found = tasks.find((row) => row.$id === id)\n if (found) setDrawer({ open: true, task: found })\n }}\n // The panel and the search are the grid's; the section\n // answers them, the view through the query (AGL-3313).\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n />\n </CrmColumnOrderProvider>\n </>\n )}\n </>\n )}\n </Stack>\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={orgRecord}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nTasksSection.displayName = 'TasksSection'\n\nexport default TasksSection\n"],"names":["CRM_COLLECTIONS","findOrgMember","pluginDocsHelp","CardDisplay","ListTable","useCrmSavedView","useCrmScope","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CrmViewsControl","EmptyStateComponent","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","ToggleButton","ToggleButtonGroup","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useEffect","useMemo","useState","useCrmRecordNames","useCrmTaskList","useNowMs","useOrgMemberDirectory","downloadTextFile","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","tasksCsv","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","CRM_TASK_VIEW_LIMIT","CRM_TASK_VIEWS","matchListFilter","crmFilterColumns","crmRowMatchesSearch","useCrmGridFilter","CrmFilterBar","CrmListToolbar","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","TaskImportButton","TaskSnoozeMenu","TasksBulkBar","TASK_FILTER_FIELDS","column","kind","path","operators","TASK_FILTER_HEADERS","view","priority","assigneeUid","TASK_HIDDEN_COLUMNS","TASK_VIEW_DEFAULT","field","op","value","TASK_SEARCH_FIELDS","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","list","uid","tasks","loaded","status","fromCache","truncated","scope","orgId","readTokens","directory","filterOptions","map","label","members","member","searchKey","searchWords","join","task","every","linked","flatMap","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","csvOptions","assigneeEmail","email","recordName","handleExport","layout","setLayout","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","variant","callScope","taskId","cause","Error","message","columns","headerName","width","sortable","filterable","renderCell","row","size","checked","disabled","onClick","event","stopPropagation","slotProps","input","title","flex","minWidth","sx","justifyContent","height","noWrap","textDecoration","undefined","color","notes","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","valueGetter","_value","filterColumns","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","fields","headers","options","servedField","exclusive","_event","aria-label","length","description","onOpen","rows","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","found","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","onClose","prev","seed","unreadable","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type ConsolePluginPageProps,\n CRM_COLLECTIONS,\n type CrmViewFilterClause,\n findOrgMember,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport CrmViewsControl from './crm-views-control'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n Stack,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { deleteField, doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmRecordNames } from '../hooks/use-crm-record-names'\nimport { type CrmTaskRow, useCrmTaskList, useNowMs } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { completeCrmTask } from '../model/task-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n CRM_TASK_VIEW_LIMIT,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n} from '../model/task-views'\nimport {\n type ListFilterField,\n matchListFilter,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { crmFilterColumns, crmRowMatchesSearch } from '../model/crm-grid-filter'\nimport { useCrmGridFilter } from '../hooks/use-crm-grid-filter'\nimport CrmFilterBar from './crm-filter-bar'\nimport { CrmListToolbar } from './crm-list-toolbar'\nimport {\n TaskDueText,\n TaskKindCell,\n TaskPriorityChip,\n TaskRecordLink,\n} from './task-cells'\nimport TaskEditDrawer from './task-edit-drawer'\nimport TasksCalendar from './tasks-calendar'\nimport { TaskImportButton } from './task-import-drawer'\nimport TaskSnoozeMenu from './task-snooze-menu'\nimport TasksBulkBar from './tasks-bulk-bar'\n\n/** What an empty view is headed, by view — \"nothing overdue\" is good news. */\n/*\n * What the tasks grid's panel offers (AGL-3313). `view` is the task view\n * the query serves — a hidden column, since it is no field of a task — and\n * the rest narrow the rows that view loaded.\n */\nconst TASK_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'view', kind: 'exact', path: 'view', operators: ['equals'] },\n { column: 'kind', kind: 'exact', path: 'kind', operators: ['equals', 'isAnyOf'] },\n { column: 'priority', kind: 'exact', path: 'priority', operators: ['equals', 'isAnyOf'] },\n { column: 'assigneeUid', kind: 'exact', path: 'assigneeUid', operators: ['equals', 'isAnyOf'] },\n]\nconst TASK_FILTER_HEADERS: Readonly<Record<string, string>> = {\n view: 'Show',\n kind: 'Kind',\n priority: 'Priority',\n assigneeUid: 'Assignee',\n}\n/** The filter-only `view` column never shows. */\nconst TASK_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = { view: false }\n/** 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/** What the quick search reads on a task. */\nconst TASK_SEARCH_FIELDS = ['title', 'notes'] as const\n\nconst EMPTY_LABEL: Record<CrmTaskView, string> = {\n mine: 'Nothing is assigned to you',\n overdue: 'Nothing is overdue',\n today: 'Nothing is due today',\n upcoming: 'Nothing is scheduled beyond today',\n open: 'No open tasks',\n done: 'No completed tasks yet',\n}\n\n/** The one sentence under it, by view. */\nconst EMPTY_COPY: Record<CrmTaskView, string> = {\n mine: 'Tasks you create are yours unless you hand them to someone.',\n overdue: 'Every dated task is on or ahead of schedule.',\n today: 'Nothing on your calendar day is owed to anyone in the CRM.',\n upcoming: 'A task with a due date past today would be listed here.',\n open: 'A task is a call, an email, a meeting or a to-do owed to a contact, a company or a deal.',\n done: 'Tasks ticked off on their record, or here, are kept for the history.',\n}\n\n/**\n * `/crm/tasks` — what the team owes the people in the CRM (AGL-2599).\n *\n * Six views over `orgs/{orgId}/crmTasks`, each a bounded listener on one of\n * the collection's indexes, chosen by a segmented control. A task hangs off\n * a contact, a company or a deal, but it is listed HERE because \"what is due\n * today\" is a question about the team's day and not about any one record;\n * the record pages carry their own `RecordTasksCard` for the other\n * direction.\n *\n * Completing goes through `crm/task-complete`, because completing fires the\n * `taskCompleted` host event and only the server can run a workflow.\n * Reopening is client-direct: undoing a tick has no side effect beyond the\n * document, and the rules gate it like any other CRM write.\n *\n * The rows are selectable, and a selection raises `TasksBulkBar` over the\n * list — Complete and Assign through their routes, the due date and the\n * delete as batched writes (AGL-2621). Export CSV beside the view control\n * writes the view through `tasksCsv()`, naming the assignee and the linked\n * records; the bar writes the same file over the selection.\n */\nexport function TasksSection(props: ConsolePluginPageProps) {\n const { hostId, org, basePath = '' } = props\n const orgRecord = org as Record<string, unknown> | undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const nowMs = useNowMs()\n const routes = useMemo(() => crmRoutes(basePath), [basePath])\n // The viewing group a linked contact is named through, or `null` at the\n // organization level, where each is named through its own holder.\n const { consentGroup } = useCrmScope({ hostId, org: orgRecord })\n const groupId = consentGroup?.groupId ?? null\n\n /*\n * Which of the six task views is open is the saved VIEW'S (AGL-2617): a\n * saved view of tasks holds the task view beside the columns and the\n * sort, and the toggle below writes into it. Unset reads as \"My tasks\",\n * which is what the section opened on before views existed.\n */\n const views = useCrmSavedView({ section: 'tasks', hostId, org: orgRecord, basePath })\n const view: CrmTaskView = useMemo(() => {\n const value = views.state.filters.find((clause) => clause.field === 'view')?.value\n return CRM_TASK_VIEWS.some((option) => option.id === value)\n ? (value as CrmTaskView)\n : 'mine'\n }, [views.state.filters])\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313). \"Show\" is the task view the query serves — the hidden\n * `view` column, as the toggle stored it, with none meaning \"My tasks\" —\n * and Kind, Priority and Assignee narrow the rows it loaded.\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 = useCrmGridFilter({\n clauses,\n onChange: setClauses,\n selectFields: ['view', 'kind', 'priority', 'assigneeUid'],\n })\n const list = useCrmTaskList({\n hostId,\n org: orgRecord,\n view,\n uid: user?.uid,\n nowMs,\n })\n const { tasks: loaded, status, fromCache, truncated, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\n const filterOptions = useMemo(\n () => ({\n view: CRM_TASK_VIEWS.map((option) => ({ value: option.id, label: option.label })),\n kind: CRM_TASK_KINDS.map((kind) => ({ value: kind, label: CRM_TASK_KIND_LABELS[kind] })),\n priority: CRM_TASK_PRIORITIES.map((priority) => ({\n value: priority,\n label: CRM_TASK_PRIORITY_LABELS[priority],\n })),\n assigneeUid: directory.members.map((member) => ({\n value: member.uid,\n label: member.label,\n })),\n }),\n [directory.members],\n )\n // Every clause but the served view, and the search, over the loaded rows.\n const searchKey = gridFilter.searchWords.join(' ')\n const tasks = useMemo(\n () =>\n loaded.filter(\n (task) =>\n clauses.every(\n (clause) =>\n clause.field === 'view' || matchListFilter(task, TASK_FILTER_FIELDS, clause),\n ) && crmRowMatchesSearch(task, TASK_SEARCH_FIELDS, gridFilter.searchWords),\n ),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [loaded, clauses, searchKey],\n )\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 is a different set of rows; a selection made on the last one\n // would be a count over rows no longer on screen.\n useEffect(() => setSelectedIds([]), [view])\n const csvOptions: TaskCsvOptions = useMemo(\n () => ({\n assigneeEmail: (uid) => {\n const member = findOrgMember(directory.members, uid)\n return member?.email || member?.label || uid\n },\n recordName: nameOf,\n }),\n [directory.members, nameOf],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('tasks.csv', 'text/csv', tasksCsv(tasks, csvOptions))\n }, [tasks, csvOptions])\n\n /*\n * List or month (AGL-2662). A layout choice over the SAME rows and the\n * same view — switching does not narrow anything and costs no read, so\n * it is a toggle rather than another saved view.\n */\n const [layout, setLayout] = useState<'list' | 'calendar'>('list')\n const [drawer, setDrawer] = useState<{ open: boolean; task: CrmTaskRow | null }>({\n open: false,\n task: null,\n })\n const [busyId, setBusyId] = useState<string | null>(null)\n\n /*\n * Tick or untick. The two directions are two different writes on purpose\n * — see the module comment — and the row is disabled while either is in\n * flight so a double-click cannot complete a task and reopen it in the\n * same breath.\n */\n const toggleDone = useCallback(\n async (task: CrmTaskRow) => {\n if (!scope || busyId) return\n setBusyId(task.$id)\n try {\n if (task.status === 'done') {\n await updateDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n {\n status: 'open',\n completedAtMs: null,\n completedByUid: deleteField(),\n updatedAt: serverTimestamp(),\n },\n )\n enqueueSnackbar('Task reopened', { variant: 'success' })\n // A client-direct write: the records it names are told (AGL-2661).\n await refreshCrmNextActivity(user, crmTaskCallScope(hostId, orgId), [task])\n } else {\n // The route runs as the mounted site, which hears the event, or\n // at the organization level as the org — the org variant, which\n // emits on the task's own site, or nowhere for an org task.\n const callScope = crmTaskCallScope(hostId, orgId)\n if (!callScope) return\n await completeCrmTask(user, { ...callScope, taskId: task.$id })\n enqueueSnackbar('Task completed', { variant: 'success' })\n }\n } catch (cause) {\n enqueueSnackbar(\n cause instanceof Error ? cause.message : 'The task could not be updated.',\n { variant: 'warning' },\n )\n } finally {\n setBusyId(null)\n }\n },\n [scope, busyId, firestore, user, hostId, orgId, enqueueSnackbar],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'status',\n headerName: '',\n width: 56,\n sortable: false,\n filterable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Checkbox\n size=\"small\"\n checked={row.status === 'done'}\n disabled={busyId === row.$id}\n onClick={(event) => {\n event.stopPropagation()\n void toggleDone(row)\n }}\n slotProps={{\n input: {\n 'aria-label':\n row.status === 'done'\n ? `Reopen \"${row.title}\"`\n : `Complete \"${row.title}\"`,\n },\n }}\n />\n ),\n },\n {\n field: 'title',\n headerName: 'Task',\n flex: 2,\n minWidth: 220,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}>\n <Typography\n variant=\"body2\"\n noWrap\n sx={{\n textDecoration: row.status === 'done' ? 'line-through' : undefined,\n color: row.status === 'done' ? 'text.secondary' : undefined,\n }}\n >\n {row.title}\n </Typography>\n {row.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.notes}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => <TaskKindCell kind={row.kind} />,\n },\n {\n field: 'priority',\n headerName: 'Priority',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskPriorityChip priority={row.priority} />\n ),\n },\n {\n field: 'dueAtMs',\n headerName: 'Due',\n width: 260,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', height: '100%' }}>\n <TaskDueText task={row} nowMs={nowMs} />\n {row.status === 'done' || !scope ? null : (\n <TaskSnoozeMenu\n dueAtMs={row.dueAtMs}\n remindAtMs={row.remindAtMs}\n target={{\n write: {\n scope,\n taskId: row.$id,\n call: crmTaskCallScope(hostId, orgId),\n links: row,\n },\n }}\n disabled={busyId === row.$id}\n />\n )}\n </Stack>\n ),\n },\n {\n field: 'assigneeUid',\n headerName: 'Assignee',\n flex: 1,\n minWidth: 140,\n sortable: false,\n valueGetter: (_value: unknown, row: CrmTaskRow) =>\n directory.nameOf(row.assigneeUid),\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <Typography variant=\"body2\" color={row.assigneeUid ? undefined : 'text.secondary'}>\n {row.assigneeUid ? directory.nameOf(row.assigneeUid) : 'Unassigned'}\n </Typography>\n ),\n },\n {\n field: 'record',\n headerName: 'For',\n flex: 1,\n minWidth: 160,\n sortable: false,\n renderCell: ({ row }: { row: CrmTaskRow }) => (\n <TaskRecordLink task={row} routes={routes} nameOf={nameOf} />\n ),\n },\n ],\n [busyId, toggleDone, nowMs, directory, routes, nameOf, scope],\n )\n /* The column and sort models are the view's (AGL-2617). */\n const filterColumns = useMemo(\n () => crmFilterColumns(columns, TASK_FILTER_FIELDS, filterOptions, TASK_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const grid = useCrmViewGrid(views, filterColumns, TASK_HIDDEN_COLUMNS)\n\n return (\n <>\n <CardDisplay\n header={'Tasks'}\n help={pluginDocsHelp('crmTasks', {\n anchor: '#the-tasks-page',\n excerpt:\n 'Calls, emails, meetings and to-dos, each linked to the contact, ' +\n 'company or deal it is for. Overdue and today are read off the ' +\n 'due date as you look.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setDrawer({ open: true, task: null })}\n >\n {'New task'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n {/*\n The saved view, and the clauses narrowing it as chips — \"Show\"\n among them — set in the grid's own Filters panel (AGL-3313).\n */}\n <CrmListToolbar label=\"Task filters\">\n <CrmViewsControl controller={views} allLabel=\"All tasks\" />\n <CrmFilterBar\n fields={TASK_FILTER_FIELDS}\n headers={TASK_FILTER_HEADERS}\n clauses={clauses}\n onChange={setClauses}\n options={filterOptions}\n servedField=\"view\"\n />\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={layout}\n onChange={(_event, next) => {\n if (next) setLayout(next as 'list' | 'calendar')\n }}\n aria-label=\"Task layout\"\n >\n <ToggleButton value=\"list\">{'List'}</ToggleButton>\n <ToggleButton value=\"calendar\">{'Calendar'}</ToggleButton>\n </ToggleButtonGroup>\n <Stack sx={{ flex: 1 }} />\n <TaskImportButton hostId={hostId} />\n <Button size=\"small\" onClick={handleExport} disabled={!tasks.length}>\n {'Export CSV'}\n </Button>\n </CrmListToolbar>\n {status === 'error' ? (\n <Typography variant=\"body2\" color=\"error\">\n {'The tasks could not be loaded. Reload to try again.'}\n </Typography>\n ) : (\n <>\n {truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Showing the first ${CRM_TASK_VIEW_LIMIT} — narrow the view to see the rest.`}\n </Typography>\n ) : null}\n {/*\n The calendar draws the same rows, so there is no bulk bar\n over it: a selection is a list gesture, and a month grid\n offers nowhere to make one.\n */}\n {layout === 'calendar' ? (\n <TasksCalendar\n tasks={tasks}\n truncated={truncated}\n onOpen={(task) => setDrawer({ open: true, task })}\n />\n ) : (\n <>\n <TasksBulkBar\n hostId={hostId}\n scope={scope}\n rows={tasks}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n directory={directory}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rows={tasks}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n loading={status === 'loading'}\n onOpen={(id) => {\n const found = tasks.find((row) => row.$id === id)\n if (found) setDrawer({ open: true, task: found })\n }}\n /*\n * The panel and the search are the grid's; the section\n * answers them, the view through the query (AGL-3313). An\n * empty view is said inside the grid rather than instead of\n * it, so its toolbar stays to choose another view.\n */\n noRowsLabel={loaded.length ? 'No tasks match these filters' : EMPTY_LABEL[view]}\n noRowsDescription={loaded.length ? undefined : EMPTY_COPY[view]}\n noRowsAction={\n loaded.length ? 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 // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n />\n </CrmColumnOrderProvider>\n </>\n )}\n </>\n )}\n </Stack>\n </CardDisplay>\n <TaskEditDrawer\n open={drawer.open}\n onClose={() => setDrawer((prev) => ({ ...prev, open: false }))}\n hostId={hostId}\n org={orgRecord}\n orgId={orgId}\n scope={scope}\n readTokens={readTokens}\n task={drawer.task}\n seed={{ fromCache, unreadable: status === 'error' }}\n />\n </>\n )\n}\nTasksSection.displayName = 'TasksSection'\n\nexport default TasksSection\n"],"names":["CRM_COLLECTIONS","findOrgMember","pluginDocsHelp","CardDisplay","ListTable","useCrmSavedView","useCrmScope","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CrmViewsControl","useSnackbar","useFirestore","useUser","Button","Checkbox","Stack","ToggleButton","ToggleButtonGroup","Typography","deleteField","doc","serverTimestamp","updateDoc","useCallback","useEffect","useMemo","useState","useCrmRecordNames","useCrmTaskList","useNowMs","useOrgMemberDirectory","downloadTextFile","crmRoutes","refreshCrmNextActivity","completeCrmTask","crmTaskCallScope","tasksCsv","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","CRM_TASK_VIEW_LIMIT","CRM_TASK_VIEWS","matchListFilter","crmFilterColumns","crmRowMatchesSearch","useCrmGridFilter","CrmFilterBar","CrmListToolbar","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","TaskImportButton","TaskSnoozeMenu","TasksBulkBar","TASK_FILTER_FIELDS","column","kind","path","operators","TASK_FILTER_HEADERS","view","priority","assigneeUid","TASK_HIDDEN_COLUMNS","TASK_VIEW_DEFAULT","field","op","value","TASK_SEARCH_FIELDS","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","list","uid","tasks","loaded","status","fromCache","truncated","scope","orgId","readTokens","directory","filterOptions","map","label","members","member","searchKey","searchWords","join","task","every","linked","flatMap","contactId","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","csvOptions","assigneeEmail","email","recordName","handleExport","layout","setLayout","drawer","setDrawer","busyId","setBusyId","toggleDone","$id","completedAtMs","completedByUid","updatedAt","variant","callScope","taskId","cause","Error","message","columns","headerName","width","sortable","filterable","renderCell","row","size","checked","disabled","onClick","event","stopPropagation","slotProps","input","title","flex","minWidth","sx","justifyContent","height","noWrap","textDecoration","undefined","color","notes","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","valueGetter","_value","filterColumns","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","controller","allLabel","fields","headers","options","servedField","exclusive","_event","aria-label","length","onOpen","rows","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","found","noRowsLabel","noRowsDescription","noRowsAction","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","onClose","prev","seed","unreadable","displayName"],"mappings":"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"}
@@ -34,6 +34,27 @@ export declare function useDealsByStatus(orgId: string | null, readTokens: reado
34
34
  * the first shape orders on one field and needs none.
35
35
  */
36
36
  export declare function usePagedDeals(orgId: string | null, readTokens: readonly string[] | null, status: CrmDealStatus | 'all', pipelineId?: string | null): import("@aglyn/tenant-feature-instance").UsePagedCollectionResult<DealDoc>;
37
+ /**
38
+ * How many deals a table search reads (AGL-3315): the newest by last
39
+ * change, in the table's own scope, status and pipeline, plus one probe row
40
+ * so "there are more" is a fact rather than a guess at the boundary.
41
+ */
42
+ export declare const DEAL_SEARCH_WINDOW = 1000;
43
+ /**
44
+ * The deals a table SEARCH runs over (AGL-3315) — a window, read only while
45
+ * the search box holds something.
46
+ *
47
+ * A search has to find a deal on any page, and it has to find it by a word
48
+ * from the middle of its title: "Acme renewal" is looked for as "renewal".
49
+ * An indexed prefix, the way the companies list searches names, answers
50
+ * only the first word, and word tokens would need every writer and a
51
+ * backfill to agree on them. An organization's deals are counted in the
52
+ * tens or hundreds — the board already reads every open one — so the
53
+ * window is the whole collection for all but the largest, and the table
54
+ * says so when it is not. The query is the table's own, one of the four
55
+ * shapes `usePagedDeals` states, so it rides indexes that already exist.
56
+ */
57
+ export declare function useDealSearchWindow(orgId: string | null, readTokens: readonly string[] | null, status: CrmDealStatus | 'all', pipelineId?: string | null): import("@aglyn/tenant-feature-instance").UseFirestoreCollectionResult<DealDoc>;
37
58
  /**
38
59
  * The deals that name one contact or one company, for the card on that
39
60
  * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.
@@ -74,6 +74,40 @@ import { crmScopeListable, crmVisibleToClause } from "./use-crm-scope.js";
74
74
  idField: '$id'
75
75
  });
76
76
  }
77
+ /**
78
+ * How many deals a table search reads (AGL-3315): the newest by last
79
+ * change, in the table's own scope, status and pipeline, plus one probe row
80
+ * so "there are more" is a fact rather than a guess at the boundary.
81
+ */ export const DEAL_SEARCH_WINDOW = 1000;
82
+ /**
83
+ * The deals a table SEARCH runs over (AGL-3315) — a window, read only while
84
+ * the search box holds something.
85
+ *
86
+ * A search has to find a deal on any page, and it has to find it by a word
87
+ * from the middle of its title: "Acme renewal" is looked for as "renewal".
88
+ * An indexed prefix, the way the companies list searches names, answers
89
+ * only the first word, and word tokens would need every writer and a
90
+ * backfill to agree on them. An organization's deals are counted in the
91
+ * tens or hundreds — the board already reads every open one — so the
92
+ * window is the whole collection for all but the largest, and the table
93
+ * says so when it is not. The query is the table's own, one of the four
94
+ * shapes `usePagedDeals` states, so it rides indexes that already exist.
95
+ */ export function useDealSearchWindow(orgId, readTokens, status, pipelineId = null) {
96
+ const firestore = useFirestore();
97
+ return useFirestoreCollection(()=>orgId && crmScopeListable(readTokens) ? query(collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals), ...crmVisibleToClause(readTokens), ...pipelineId ? [
98
+ where('pipelineId', '==', pipelineId)
99
+ ] : [], ...status === 'all' ? [] : [
100
+ where('status', '==', status)
101
+ ], orderBy('updatedAt', 'desc'), limit(DEAL_SEARCH_WINDOW + 1)) : null, [
102
+ firestore,
103
+ orgId,
104
+ readTokens,
105
+ status,
106
+ pipelineId
107
+ ], {
108
+ idField: '$id'
109
+ });
110
+ }
77
111
  /**
78
112
  * The deals that name one contact or one company, for the card on that
79
113
  * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-deals.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'use client'\n\nimport { CRM_COLLECTIONS, type CrmDealStatus } from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n doc,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport type { DealDoc } from '../model/deal-board-model'\nimport { crmScopeListable, crmVisibleToClause } from './use-crm-scope'\n\n/**\n * The most open cards one pipeline draws. A board past this is a board\n * nobody reads column by column; the table is paged and reaches the rest.\n */\nexport const BOARD_OPEN_LIMIT = 500\n\n/** Closed deals shown per closed column when it is expanded. */\nexport const BOARD_CLOSED_LIMIT = 50\n\n/** Deals listed on a contact's or a company's page. */\nexport const LINKED_DEALS_LIMIT = 25\n\n/**\n * The deals of one status in one pipeline, newest activity first (AGL-2598).\n *\n * Every query here carries the viewer's scope tokens and an `orderBy`, and\n * each shape sits on an index the foundation declared:\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. Passing `null` for the\n * pipeline or an EMPTY token list issues nothing, which is how a column\n * stays quiet until the pipeline has loaded and a collapsed Won column\n * costs no read. A `null` token list is the ORGANIZATION level (AGL-2630):\n * the clause is dropped and the query rides the `(pipelineId, status,\n * updatedAt DESC)` twin of that index.\n */\nexport function useDealsByStatus(\n orgId: string | null,\n readTokens: readonly string[] | null,\n pipelineId: string | null,\n status: CrmDealStatus,\n max: number,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && pipelineId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where('pipelineId', '==', pipelineId),\n where('status', '==', status),\n orderBy('updatedAt', 'desc'),\n limit(max),\n )\n : null,\n [firestore, orgId, readTokens, pipelineId, status, max],\n { idField: '$id' },\n )\n}\n\n/**\n * Every deal the viewer may see, paged, optionally narrowed to one status\n * and to one pipeline — the table's read. Four shapes, four indexes:\n * `(visibleTo, updatedAt DESC)`, `(visibleTo, status, updatedAt DESC)`,\n * `(visibleTo, pipelineId, updatedAt DESC)` and\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. At the organization\n * level (AGL-2630) the scope clause is dropped and the same shapes ride\n * their twins without `visibleTo` — `(status, updatedAt DESC)`,\n * `(pipelineId, updatedAt DESC)`, `(pipelineId, status, updatedAt DESC)`;\n * the first shape orders on one field and needs none.\n */\nexport function usePagedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return usePagedCollection<DealDoc>(\n (pageLimit) =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * The deals that name one contact or one company, for the card on that\n * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.\n */\nexport function useLinkedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n link: { contactId: string } | { companyId: string },\n) {\n const firestore = useFirestore()\n const field = 'contactId' in link ? 'contactId' : 'companyId'\n const id = 'contactId' in link ? link.contactId : link.companyId\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && id && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where(field, '==', id),\n orderBy('updatedAt', 'desc'),\n limit(LINKED_DEALS_LIMIT),\n )\n : null,\n [firestore, orgId, readTokens, field, id],\n { idField: '$id' },\n )\n}\n\n/** One deal, live. `undefined` while loading, and after a delete. */\nexport function useDeal(orgId: string | null, dealId: string) {\n const firestore = useFirestore()\n return useFirestoreDoc<DealDoc>(\n () =>\n orgId && dealId\n ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, dealId)\n : null,\n [firestore, orgId, dealId],\n { idField: '$id' },\n )\n}\n"],"names":["CRM_COLLECTIONS","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","collection","doc","limit","orderBy","query","where","crmScopeListable","crmVisibleToClause","BOARD_OPEN_LIMIT","BOARD_CLOSED_LIMIT","LINKED_DEALS_LIMIT","useDealsByStatus","orgId","readTokens","pipelineId","status","max","firestore","deals","idField","usePagedDeals","pageLimit","useLinkedDeals","link","field","id","contactId","companyId","useDeal","dealId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,eAAe,QAA4B,eAAc;AAClE,SACEC,YAAY,EACZC,sBAAsB,EACtBC,eAAe,EACfC,kBAAkB,QACb,iCAAgC;AACvC,SACEC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAE3B,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,qBAAiB;AAEtE;;;CAGC,GACD,OAAO,MAAMC,mBAAmB,IAAG;AAEnC,8DAA8D,GAC9D,OAAO,MAAMC,qBAAqB,GAAE;AAEpC,qDAAqD,GACrD,OAAO,MAAMC,qBAAqB,GAAE;AAEpC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,iBACdC,KAAoB,EACpBC,UAAoC,EACpCC,UAAyB,EACzBC,MAAqB,EACrBC,GAAW;IAEX,MAAMC,YAAYrB;IAClB,OAAOC,uBACL,IACEe,SAASE,cAAcR,iBAAiBO,cACpCT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAM,cAAc,MAAMS,aAC1BT,MAAM,UAAU,MAAMU,SACtBZ,QAAQ,aAAa,SACrBD,MAAMc,QAER,MACN;QAACC;QAAWL;QAAOC;QAAYC;QAAYC;QAAQC;KAAI,EACvD;QAAEG,SAAS;IAAM;AAErB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,cACdR,KAAoB,EACpBC,UAAoC,EACpCE,MAA6B,EAC7BD,aAA4B,IAAI;IAEhC,MAAMG,YAAYrB;IAClB,OAAOG,mBACL,CAACsB,YACCT,SAASN,iBAAiBO,cACtBT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,gBAClBC,aAAa;YAACT,MAAM,cAAc,MAAMS;SAAY,GAAG,EAAE,KACzDC,WAAW,QAAQ,EAAE,GAAG;YAACV,MAAM,UAAU,MAAMU;SAAQ,EAC3DZ,QAAQ,aAAa,SACrBD,MAAMmB,cAER,MACN;QAACJ;QAAWL;QAAOC;QAAYE;QAAQD;KAAW,EAClD;QAAEK,SAAS;IAAM;AAErB;AAEA;;;CAGC,GACD,OAAO,SAASG,eACdV,KAAoB,EACpBC,UAAoC,EACpCU,IAAmD;IAEnD,MAAMN,YAAYrB;IAClB,MAAM4B,QAAQ,eAAeD,OAAO,cAAc;IAClD,MAAME,KAAK,eAAeF,OAAOA,KAAKG,SAAS,GAAGH,KAAKI,SAAS;IAChE,OAAO9B,uBACL,IACEe,SAASa,MAAMnB,iBAAiBO,cAC5BT,MACEJ,WAAWiB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,MACvDX,mBAAmBM,aACtBR,MAAMmB,OAAO,MAAMC,KACnBtB,QAAQ,aAAa,SACrBD,MAAMQ,uBAER,MACN;QAACO;QAAWL;QAAOC;QAAYW;QAAOC;KAAG,EACzC;QAAEN,SAAS;IAAM;AAErB;AAEA,mEAAmE,GACnE,OAAO,SAASS,QAAQhB,KAAoB,EAAEiB,MAAc;IAC1D,MAAMZ,YAAYrB;IAClB,OAAOE,gBACL,IACEc,SAASiB,SACL5B,IAAIgB,WAAW,QAAQL,OAAOjB,gBAAgBuB,KAAK,EAAEW,UACrD,MACN;QAACZ;QAAWL;QAAOiB;KAAO,EAC1B;QAAEV,SAAS;IAAM;AAErB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-deals.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'use client'\n\nimport { CRM_COLLECTIONS, type CrmDealStatus } from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n doc,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport type { DealDoc } from '../model/deal-board-model'\nimport { crmScopeListable, crmVisibleToClause } from './use-crm-scope'\n\n/**\n * The most open cards one pipeline draws. A board past this is a board\n * nobody reads column by column; the table is paged and reaches the rest.\n */\nexport const BOARD_OPEN_LIMIT = 500\n\n/** Closed deals shown per closed column when it is expanded. */\nexport const BOARD_CLOSED_LIMIT = 50\n\n/** Deals listed on a contact's or a company's page. */\nexport const LINKED_DEALS_LIMIT = 25\n\n/**\n * The deals of one status in one pipeline, newest activity first (AGL-2598).\n *\n * Every query here carries the viewer's scope tokens and an `orderBy`, and\n * each shape sits on an index the foundation declared:\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. Passing `null` for the\n * pipeline or an EMPTY token list issues nothing, which is how a column\n * stays quiet until the pipeline has loaded and a collapsed Won column\n * costs no read. A `null` token list is the ORGANIZATION level (AGL-2630):\n * the clause is dropped and the query rides the `(pipelineId, status,\n * updatedAt DESC)` twin of that index.\n */\nexport function useDealsByStatus(\n orgId: string | null,\n readTokens: readonly string[] | null,\n pipelineId: string | null,\n status: CrmDealStatus,\n max: number,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && pipelineId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where('pipelineId', '==', pipelineId),\n where('status', '==', status),\n orderBy('updatedAt', 'desc'),\n limit(max),\n )\n : null,\n [firestore, orgId, readTokens, pipelineId, status, max],\n { idField: '$id' },\n )\n}\n\n/**\n * Every deal the viewer may see, paged, optionally narrowed to one status\n * and to one pipeline — the table's read. Four shapes, four indexes:\n * `(visibleTo, updatedAt DESC)`, `(visibleTo, status, updatedAt DESC)`,\n * `(visibleTo, pipelineId, updatedAt DESC)` and\n * `(visibleTo, pipelineId, status, updatedAt DESC)`. At the organization\n * level (AGL-2630) the scope clause is dropped and the same shapes ride\n * their twins without `visibleTo` — `(status, updatedAt DESC)`,\n * `(pipelineId, updatedAt DESC)`, `(pipelineId, status, updatedAt DESC)`;\n * the first shape orders on one field and needs none.\n */\nexport function usePagedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return usePagedCollection<DealDoc>(\n (pageLimit) =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(pageLimit),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * How many deals a table search reads (AGL-3315): the newest by last\n * change, in the table's own scope, status and pipeline, plus one probe row\n * so \"there are more\" is a fact rather than a guess at the boundary.\n */\nexport const DEAL_SEARCH_WINDOW = 1000\n\n/**\n * The deals a table SEARCH runs over (AGL-3315) — a window, read only while\n * the search box holds something.\n *\n * A search has to find a deal on any page, and it has to find it by a word\n * from the middle of its title: \"Acme renewal\" is looked for as \"renewal\".\n * An indexed prefix, the way the companies list searches names, answers\n * only the first word, and word tokens would need every writer and a\n * backfill to agree on them. An organization's deals are counted in the\n * tens or hundreds — the board already reads every open one — so the\n * window is the whole collection for all but the largest, and the table\n * says so when it is not. The query is the table's own, one of the four\n * shapes `usePagedDeals` states, so it rides indexes that already exist.\n */\nexport function useDealSearchWindow(\n orgId: string | null,\n readTokens: readonly string[] | null,\n status: CrmDealStatus | 'all',\n pipelineId: string | null = null,\n) {\n const firestore = useFirestore()\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n ...(pipelineId ? [where('pipelineId', '==', pipelineId)] : []),\n ...(status === 'all' ? [] : [where('status', '==', status)]),\n orderBy('updatedAt', 'desc'),\n limit(DEAL_SEARCH_WINDOW + 1),\n )\n : null,\n [firestore, orgId, readTokens, status, pipelineId],\n { idField: '$id' },\n )\n}\n\n/**\n * The deals that name one contact or one company, for the card on that\n * record's page. `(visibleTo, contactId|companyId, updatedAt DESC)`.\n */\nexport function useLinkedDeals(\n orgId: string | null,\n readTokens: readonly string[] | null,\n link: { contactId: string } | { companyId: string },\n) {\n const firestore = useFirestore()\n const field = 'contactId' in link ? 'contactId' : 'companyId'\n const id = 'contactId' in link ? link.contactId : link.companyId\n return useFirestoreCollection<DealDoc>(\n () =>\n orgId && id && crmScopeListable(readTokens)\n ? query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals),\n ...crmVisibleToClause(readTokens),\n where(field, '==', id),\n orderBy('updatedAt', 'desc'),\n limit(LINKED_DEALS_LIMIT),\n )\n : null,\n [firestore, orgId, readTokens, field, id],\n { idField: '$id' },\n )\n}\n\n/** One deal, live. `undefined` while loading, and after a delete. */\nexport function useDeal(orgId: string | null, dealId: string) {\n const firestore = useFirestore()\n return useFirestoreDoc<DealDoc>(\n () =>\n orgId && dealId\n ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.deals, dealId)\n : null,\n [firestore, orgId, dealId],\n { idField: '$id' },\n )\n}\n"],"names":["CRM_COLLECTIONS","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","collection","doc","limit","orderBy","query","where","crmScopeListable","crmVisibleToClause","BOARD_OPEN_LIMIT","BOARD_CLOSED_LIMIT","LINKED_DEALS_LIMIT","useDealsByStatus","orgId","readTokens","pipelineId","status","max","firestore","deals","idField","usePagedDeals","pageLimit","DEAL_SEARCH_WINDOW","useDealSearchWindow","useLinkedDeals","link","field","id","contactId","companyId","useDeal","dealId"],"mappings":"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"}