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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/companies-section.js +97 -54
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/contacts-section.js +51 -19
  5. package/src/lib/components/contacts-section.js.map +1 -1
  6. package/src/lib/components/crm-filter-bar.d.ts +34 -32
  7. package/src/lib/components/crm-filter-bar.js +34 -235
  8. package/src/lib/components/crm-filter-bar.js.map +1 -1
  9. package/src/lib/components/crm-next-activity-column.d.ts +13 -21
  10. package/src/lib/components/crm-next-activity-column.js +21 -32
  11. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  12. package/src/lib/components/deals-section.js +65 -54
  13. package/src/lib/components/deals-section.js.map +1 -1
  14. package/src/lib/components/leads-section.js +106 -286
  15. package/src/lib/components/leads-section.js.map +1 -1
  16. package/src/lib/components/tasks-section.js +139 -34
  17. package/src/lib/components/tasks-section.js.map +1 -1
  18. package/src/lib/hooks/use-crm-grid-filter.d.ts +77 -0
  19. package/src/lib/hooks/use-crm-grid-filter.js +143 -0
  20. package/src/lib/hooks/use-crm-grid-filter.js.map +1 -0
  21. package/src/lib/model/crm-grid-filter.d.ts +64 -0
  22. package/src/lib/model/crm-grid-filter.js +144 -0
  23. package/src/lib/model/crm-grid-filter.js.map +1 -0
  24. package/src/lib/model/lead-filters.d.ts +42 -0
  25. package/src/lib/model/lead-filters.js +182 -9
  26. package/src/lib/model/lead-filters.js.map +1 -1
  27. package/src/lib/server/email-send.js +4 -2
  28. package/src/lib/server/email-send.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 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_VIEW_LIMIT,\n CRM_TASK_VIEWS,\n type CrmTaskView,\n} from '../model/task-views'\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. */\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 const setView = useCallback(\n (next: CrmTaskView) =>\n views.setFilters([{ field: 'view', op: 'equals', value: next }]),\n [views.setFilters],\n )\n const list = useCrmTaskList({\n hostId,\n org: orgRecord,\n view,\n uid: user?.uid,\n nowMs,\n })\n const { tasks, status, fromCache, truncated, scope, orgId, readTokens } = list\n const directory = useOrgMemberDirectory(orgId)\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 grid = useCrmViewGrid(views, 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 {/* The saved view this list is showing, beside the task view it narrows to (AGL-2617). */}\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <CrmViewsControl controller={views} allLabel=\"All tasks\" />\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n color=\"primary\"\n value={view}\n onChange={(_event, next) => {\n if (next) setView(next as CrmTaskView)\n }}\n aria-label=\"Task view\"\n >\n {CRM_TASK_VIEWS.map((option) => (\n <ToggleButton key={option.id} value={option.id}>\n {option.label}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\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 </Stack>\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' && !tasks.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 disableColumnFilter\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_VIEW_LIMIT","CRM_TASK_VIEWS","TaskDueText","TaskKindCell","TaskPriorityChip","TaskRecordLink","TaskEditDrawer","TasksCalendar","TaskImportButton","TaskSnoozeMenu","TasksBulkBar","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","view","value","state","filters","find","clause","field","some","option","id","setView","next","setFilters","op","list","uid","tasks","status","fromCache","truncated","scope","orgId","readTokens","directory","linked","flatMap","task","contactId","kind","companyId","dealId","nameOf","records","selectedIds","setSelectedIds","csvOptions","assigneeEmail","member","members","email","label","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","priority","direction","spacing","alignItems","dueAtMs","remindAtMs","target","write","call","links","valueGetter","_value","assigneeUid","grid","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","flexWrap","rowGap","controller","allLabel","exclusive","onChange","_event","aria-label","map","length","description","onOpen","rows","selected","onSelectedChange","csv","columnOrder","slots","selectable","loading","found","disableColumnFilter","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 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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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { CrmViewFilterClause } from '@aglyn/aglyn';
18
+ import type { GridFilterModel } from '@mui/x-data-grid';
19
+ import { type CrmGridFilterCodec } from '../model/crm-grid-filter';
20
+ export interface CrmGridFilterOptions {
21
+ /** The clauses the list is narrowed by — the saved view's working filters. */
22
+ clauses: readonly CrmViewFilterClause[];
23
+ onChange: (clauses: CrmViewFilterClause[]) => void;
24
+ /** Fields whose values are picked, which the panel shows as a select. */
25
+ selectFields?: readonly string[];
26
+ /** A field whose stored clauses predate the panel, translated its own way. */
27
+ codecs?: Readonly<Record<string, CrmGridFilterCodec>>;
28
+ /**
29
+ * The query serves ONE clause, so the panel's replaces every other — a
30
+ * server-paged list, which has no loaded window to narrow further.
31
+ * `keepAlongside` names the clauses that do narrow the loaded page and so
32
+ * may stand beside it ("No next activity").
33
+ */
34
+ single?: boolean;
35
+ keepAlongside?: (clause: CrmViewFilterClause) => boolean;
36
+ /**
37
+ * The quick search's words, held by the list when it needs them before
38
+ * the grid's columns exist; the hook holds them itself otherwise.
39
+ */
40
+ search?: {
41
+ words: string[];
42
+ onChange: (words: string[]) => void;
43
+ };
44
+ }
45
+ /** What the grid is handed, and what the list filters its rows by. */
46
+ export interface CrmGridFilter {
47
+ /** Controlled: the panel's one item, and the quick search's words. */
48
+ filterModel: GridFilterModel;
49
+ onFilterModelChange: (model: GridFilterModel) => void;
50
+ /** The quick search's words, for the list to match its rows against. */
51
+ searchWords: string[];
52
+ }
53
+ /**
54
+ * The grid's Filters panel and quick search, bound to a list's view clauses
55
+ * (AGL-3313). See `model/crm-grid-filter.ts` for the shape both sides keep.
56
+ *
57
+ * ## One field, one clause; the panel edits one at a time
58
+ *
59
+ * The free DataGrid's panel holds a single item, while a saved view
60
+ * holds several clauses. So each FIELD holds one clause, and the panel is a
61
+ * window onto whichever field was last filtered: setting a value writes
62
+ * that field's clause, clearing it or deleting the row removes it, and
63
+ * choosing another column in the panel moves the window without touching
64
+ * the clause the reader just set. Filtering Lead source and then Status is
65
+ * therefore both, which is what a reader who does it means; the chips over
66
+ * the grid (`CrmFilterBar`) show every clause and remove any of them.
67
+ *
68
+ * ## The list answers, never the grid
69
+ *
70
+ * Every list passes `filterMode="server"`: the grid holds one page, or a
71
+ * window the query already narrowed, so a filter the grid ran itself would
72
+ * answer "no match" for a row on the next page. The list reads the clauses
73
+ * — onto its query where it can, over its loaded rows where it cannot —
74
+ * and the quick search's words, and hands the grid the rows that answer.
75
+ */
76
+ export declare function useCrmGridFilter(options: CrmGridFilterOptions): CrmGridFilter;
77
+ export default useCrmGridFilter;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { useCallback, useMemo, useState } from "react";
19
+ import { crmPlainCodec, crmSelectCodec, crmUpsertClause } from "../model/crm-grid-filter.js";
20
+ /**
21
+ * The grid's Filters panel and quick search, bound to a list's view clauses
22
+ * (AGL-3313). See `model/crm-grid-filter.ts` for the shape both sides keep.
23
+ *
24
+ * ## One field, one clause; the panel edits one at a time
25
+ *
26
+ * The free DataGrid's panel holds a single item, while a saved view
27
+ * holds several clauses. So each FIELD holds one clause, and the panel is a
28
+ * window onto whichever field was last filtered: setting a value writes
29
+ * that field's clause, clearing it or deleting the row removes it, and
30
+ * choosing another column in the panel moves the window without touching
31
+ * the clause the reader just set. Filtering Lead source and then Status is
32
+ * therefore both, which is what a reader who does it means; the chips over
33
+ * the grid (`CrmFilterBar`) show every clause and remove any of them.
34
+ *
35
+ * ## The list answers, never the grid
36
+ *
37
+ * Every list passes `filterMode="server"`: the grid holds one page, or a
38
+ * window the query already narrowed, so a filter the grid ran itself would
39
+ * answer "no match" for a row on the next page. The list reads the clauses
40
+ * — onto its query where it can, over its loaded rows where it cannot —
41
+ * and the quick search's words, and hands the grid the rows that answer.
42
+ */ export function useCrmGridFilter(options) {
43
+ var _ref, _ref1;
44
+ var _options_search, _options_search1;
45
+ const { clauses, onChange, selectFields = [], codecs = {}, single = false, keepAlongside } = options;
46
+ const codecFor = useCallback((field)=>{
47
+ var _codecs_field;
48
+ return (_codecs_field = codecs[field]) != null ? _codecs_field : selectFields.includes(field) ? crmSelectCodec : crmPlainCodec;
49
+ }, [
50
+ codecs,
51
+ selectFields
52
+ ]);
53
+ /** The field the panel shows, once the reader has pointed it anywhere. */ const [focus, setFocus] = useState(null);
54
+ /** The panel's row while it holds no value yet — a column chosen, a value not. */ const [pending, setPending] = useState(null);
55
+ const [ownWords, setOwnWords] = useState([]);
56
+ const searchWords = (_ref = (_options_search = options.search) == null ? void 0 : _options_search.words) != null ? _ref : ownWords;
57
+ const setSearchWords = (_ref1 = (_options_search1 = options.search) == null ? void 0 : _options_search1.onChange) != null ? _ref1 : setOwnWords;
58
+ /** Whatever the panel last pointed at, else the newest clause it can show. */ const shownField = useMemo(()=>{
59
+ if (focus) return focus;
60
+ for(let at = clauses.length - 1; at >= 0; at -= 1){
61
+ if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field;
62
+ }
63
+ return null;
64
+ }, [
65
+ focus,
66
+ clauses,
67
+ codecFor
68
+ ]);
69
+ const filterModel = useMemo(()=>{
70
+ const clause = shownField ? clauses.find((entry)=>entry.field === shownField) : undefined;
71
+ const shown = clause ? codecFor(clause.field).toItem(clause) : null;
72
+ const item = pending && pending.field === shownField ? pending : clause && shown ? _extends({
73
+ id: 'crm',
74
+ field: clause.field
75
+ }, shown) : null;
76
+ return {
77
+ items: item ? [
78
+ item
79
+ ] : [],
80
+ quickFilterValues: searchWords
81
+ };
82
+ }, [
83
+ shownField,
84
+ clauses,
85
+ codecFor,
86
+ pending,
87
+ searchWords
88
+ ]);
89
+ const onFilterModelChange = useCallback((model)=>{
90
+ var _model_quickFilterValues;
91
+ const words = ((_model_quickFilterValues = model.quickFilterValues) != null ? _model_quickFilterValues : []).map((word)=>String(word));
92
+ if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words);
93
+ const item = model.items[0];
94
+ // The row deleted: the field it showed is no longer filtered.
95
+ if (!item) {
96
+ if (shownField && clauses.some((clause)=>clause.field === shownField)) {
97
+ onChange(crmUpsertClause(clauses, shownField, null));
98
+ }
99
+ setPending(null);
100
+ setFocus(null);
101
+ return;
102
+ }
103
+ const field = String(item.field);
104
+ const moved = field !== shownField;
105
+ setFocus(field);
106
+ const clause = codecFor(field).toClause(item);
107
+ if (!clause) {
108
+ // No value yet. A value cleared on the field already shown removes
109
+ // its clause; a column just chosen leaves the last one standing.
110
+ setPending(_extends({}, item, {
111
+ id: 'crm'
112
+ }));
113
+ if (!moved && clauses.some((entry)=>entry.field === field)) {
114
+ onChange(crmUpsertClause(clauses, field, null));
115
+ }
116
+ return;
117
+ }
118
+ setPending(null);
119
+ // A clause that stands alongside the served one is added beside it;
120
+ // any other replaces whatever the query was serving.
121
+ const base = single && !(keepAlongside == null ? void 0 : keepAlongside(clause)) ? clauses.filter((entry)=>entry.field === field || (keepAlongside == null ? void 0 : keepAlongside(entry))) : clauses;
122
+ const next = crmUpsertClause(base, field, clause);
123
+ const same = next.length === clauses.length && next.every((entry, at)=>entry.field === clauses[at].field && entry.op === clauses[at].op && entry.value === clauses[at].value);
124
+ if (!same) onChange(next);
125
+ }, [
126
+ searchWords,
127
+ setSearchWords,
128
+ shownField,
129
+ clauses,
130
+ onChange,
131
+ codecFor,
132
+ single,
133
+ keepAlongside
134
+ ]);
135
+ return {
136
+ filterModel,
137
+ onFilterModelChange,
138
+ searchWords
139
+ };
140
+ }
141
+ export default useCrmGridFilter;
142
+
143
+ //# sourceMappingURL=use-crm-grid-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-crm-grid-filter.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\n'use client'\n\nimport type { CrmViewFilterClause } from '@aglyn/aglyn'\nimport type { GridFilterItem, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type CrmGridFilterCodec,\n crmPlainCodec,\n crmSelectCodec,\n crmUpsertClause,\n} from '../model/crm-grid-filter'\n\nexport interface CrmGridFilterOptions {\n /** The clauses the list is narrowed by — the saved view's working filters. */\n clauses: readonly CrmViewFilterClause[]\n onChange: (clauses: CrmViewFilterClause[]) => void\n /** Fields whose values are picked, which the panel shows as a select. */\n selectFields?: readonly string[]\n /** A field whose stored clauses predate the panel, translated its own way. */\n codecs?: Readonly<Record<string, CrmGridFilterCodec>>\n /**\n * The query serves ONE clause, so the panel's replaces every other — a\n * server-paged list, which has no loaded window to narrow further.\n * `keepAlongside` names the clauses that do narrow the loaded page and so\n * may stand beside it (\"No next activity\").\n */\n single?: boolean\n keepAlongside?: (clause: CrmViewFilterClause) => boolean\n /**\n * The quick search's words, held by the list when it needs them before\n * the grid's columns exist; the hook holds them itself otherwise.\n */\n search?: { words: string[]; onChange: (words: string[]) => void }\n}\n\n/** What the grid is handed, and what the list filters its rows by. */\nexport interface CrmGridFilter {\n /** Controlled: the panel's one item, and the quick search's words. */\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n /** The quick search's words, for the list to match its rows against. */\n searchWords: string[]\n}\n\n/**\n * The grid's Filters panel and quick search, bound to a list's view clauses\n * (AGL-3313). See `model/crm-grid-filter.ts` for the shape both sides keep.\n *\n * ## One field, one clause; the panel edits one at a time\n *\n * The free DataGrid's panel holds a single item, while a saved view\n * holds several clauses. So each FIELD holds one clause, and the panel is a\n * window onto whichever field was last filtered: setting a value writes\n * that field's clause, clearing it or deleting the row removes it, and\n * choosing another column in the panel moves the window without touching\n * the clause the reader just set. Filtering Lead source and then Status is\n * therefore both, which is what a reader who does it means; the chips over\n * the grid (`CrmFilterBar`) show every clause and remove any of them.\n *\n * ## The list answers, never the grid\n *\n * Every list passes `filterMode=\"server\"`: the grid holds one page, or a\n * window the query already narrowed, so a filter the grid ran itself would\n * answer \"no match\" for a row on the next page. The list reads the clauses\n * — onto its query where it can, over its loaded rows where it cannot —\n * and the quick search's words, and hands the grid the rows that answer.\n */\nexport function useCrmGridFilter(options: CrmGridFilterOptions): CrmGridFilter {\n const { clauses, onChange, selectFields = [], codecs = {}, single = false, keepAlongside } =\n options\n\n const codecFor = useCallback(\n (field: string): CrmGridFilterCodec =>\n codecs[field] ?? (selectFields.includes(field) ? crmSelectCodec : crmPlainCodec),\n [codecs, selectFields],\n )\n\n /** The field the panel shows, once the reader has pointed it anywhere. */\n const [focus, setFocus] = useState<string | null>(null)\n /** The panel's row while it holds no value yet — a column chosen, a value not. */\n const [pending, setPending] = useState<GridFilterItem | null>(null)\n const [ownWords, setOwnWords] = useState<string[]>([])\n const searchWords = options.search?.words ?? ownWords\n const setSearchWords = options.search?.onChange ?? setOwnWords\n\n /** Whatever the panel last pointed at, else the newest clause it can show. */\n const shownField = useMemo(() => {\n if (focus) return focus\n for (let at = clauses.length - 1; at >= 0; at -= 1) {\n if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field\n }\n return null\n }, [focus, clauses, codecFor])\n\n const filterModel = useMemo<GridFilterModel>(() => {\n const clause = shownField\n ? clauses.find((entry) => entry.field === shownField)\n : undefined\n const shown = clause ? codecFor(clause.field).toItem(clause) : null\n const item: GridFilterItem | null =\n pending && pending.field === shownField\n ? pending\n : clause && shown\n ? { id: 'crm', field: clause.field, ...shown }\n : null\n return { items: item ? [item] : [], quickFilterValues: searchWords }\n }, [shownField, clauses, codecFor, pending, searchWords])\n\n const onFilterModelChange = useCallback(\n (model: GridFilterModel) => {\n const words = (model.quickFilterValues ?? []).map((word) => String(word))\n if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words)\n\n const item = model.items[0]\n // The row deleted: the field it showed is no longer filtered.\n if (!item) {\n if (shownField && clauses.some((clause) => clause.field === shownField)) {\n onChange(crmUpsertClause(clauses, shownField, null))\n }\n setPending(null)\n setFocus(null)\n return\n }\n const field = String(item.field)\n const moved = field !== shownField\n setFocus(field)\n const clause = codecFor(field).toClause(item)\n if (!clause) {\n // No value yet. A value cleared on the field already shown removes\n // its clause; a column just chosen leaves the last one standing.\n setPending({ ...item, id: 'crm' })\n if (!moved && clauses.some((entry) => entry.field === field)) {\n onChange(crmUpsertClause(clauses, field, null))\n }\n return\n }\n setPending(null)\n // A clause that stands alongside the served one is added beside it;\n // any other replaces whatever the query was serving.\n const base =\n single && !keepAlongside?.(clause)\n ? clauses.filter((entry) => entry.field === field || keepAlongside?.(entry))\n : clauses\n const next = crmUpsertClause(base, field, clause)\n const same =\n next.length === clauses.length &&\n next.every(\n (entry, at) =>\n entry.field === clauses[at].field &&\n entry.op === clauses[at].op &&\n entry.value === clauses[at].value,\n )\n if (!same) onChange(next)\n },\n [searchWords, setSearchWords, shownField, clauses, onChange, codecFor, single, keepAlongside],\n )\n\n return { filterModel, onFilterModelChange, searchWords }\n}\n\nexport default useCrmGridFilter\n"],"names":["useCallback","useMemo","useState","crmPlainCodec","crmSelectCodec","crmUpsertClause","useCrmGridFilter","options","clauses","onChange","selectFields","codecs","single","keepAlongside","codecFor","field","includes","focus","setFocus","pending","setPending","ownWords","setOwnWords","searchWords","search","words","setSearchWords","shownField","at","length","toItem","filterModel","clause","find","entry","undefined","shown","item","id","items","quickFilterValues","onFilterModelChange","model","map","word","String","join","some","moved","toClause","base","filter","next","same","every","op","value"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { CrmViewFilterClause } from '@aglyn/aglyn';
18
+ import { type ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter';
19
+ import { type GridColDef, type GridFilterItem } from '@mui/x-data-grid';
20
+ /** A choice for a field whose value is picked rather than typed. */
21
+ export interface CrmFilterOption {
22
+ value: string;
23
+ label: string;
24
+ }
25
+ /**
26
+ * How one field's stored clause and the panel's item translate. A field
27
+ * with choices uses {@link crmSelectCodec}; a typed one, {@link crmPlainCodec}.
28
+ * A list whose clauses predate the panel supplies its own.
29
+ */
30
+ export interface CrmGridFilterCodec {
31
+ toItem: (clause: CrmViewFilterClause) => Pick<GridFilterItem, 'operator' | 'value'> | null;
32
+ toClause: (item: GridFilterItem) => CrmViewFilterClause | null;
33
+ }
34
+ /** A typed field: the stored operator IS the grid's. */
35
+ export declare const crmPlainCodec: CrmGridFilterCodec;
36
+ /** A picked field: `equals` shows as `is`, and `isAnyOf` holds a comma list. */
37
+ export declare const crmSelectCodec: CrmGridFilterCodec;
38
+ /** The select operators a field's allowed clause operators map to. */
39
+ export declare function crmSelectOperators(allowed: readonly string[]): import("@mui/x-data-grid").GridFilterOperator<any, any, any, import("@mui/x-data-grid").GridFilterInputValueProps>[];
40
+ /**
41
+ * The list's columns, each made filterable exactly as far as its field is
42
+ * declared, and every declared field that is not a column added as a
43
+ * permanently hidden one — the panel lists columns, so a field with none
44
+ * could not be reached (`hiddenFilterColumns`).
45
+ *
46
+ * A field with choices becomes a `singleSelect` over them, whatever the
47
+ * column rendered before; one without keeps the grammar's operators; a
48
+ * column no field declares offers no filter, because the list, not the
49
+ * grid, answers every filter and would have nothing to answer it with.
50
+ */
51
+ export declare function crmFilterColumns(columns: readonly GridColDef[], fields: readonly ListFilterField[], options?: Readonly<Record<string, readonly CrmFilterOption[]>>, headers?: Readonly<Record<string, string>>): GridColDef[];
52
+ /**
53
+ * Whether a row answers a quick search: every whitespace-separated word
54
+ * appears, case-insensitively, in SOME of the row's searched values. A
55
+ * blank search matches every row, so an emptied box is the list unfiltered.
56
+ * An array value (tags) is searched member by member.
57
+ */
58
+ export declare function crmRowMatchesSearch(row: object, paths: readonly string[], words: readonly string[]): boolean;
59
+ /**
60
+ * The clauses with one field's clause set: replaced in place where the field
61
+ * already had one, appended where it did not, removed when `next` is null.
62
+ * A field holds ONE clause, which is what the panel edits.
63
+ */
64
+ export declare function crmUpsertClause(clauses: readonly CrmViewFilterClause[], field: string, next: CrmViewFilterClause | null): CrmViewFilterClause[];