@aglyn/plugins-crm 1.0.0-beta.231 → 1.0.0-beta.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/components/activity-timeline.js +1 -0
- package/src/lib/components/activity-timeline.js.map +1 -1
- package/src/lib/components/assignment-rule-drawer.js +1 -0
- package/src/lib/components/assignment-rule-drawer.js.map +1 -1
- package/src/lib/components/companies-section.js +43 -8
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +3 -1
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/contact-associations-card.js +1 -0
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +6 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-duplicates-card.js +1 -0
- package/src/lib/components/contact-duplicates-card.js.map +1 -1
- package/src/lib/components/contact-field-drawer.js +1 -0
- package/src/lib/components/contact-field-drawer.js.map +1 -1
- package/src/lib/components/contact-known-by-card.js +1 -0
- package/src/lib/components/contact-known-by-card.js.map +1 -1
- package/src/lib/components/contact-timeline-card.js +1 -0
- package/src/lib/components/contact-timeline-card.js.map +1 -1
- package/src/lib/components/contacts-section.js +37 -8
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-email-check.d.ts +14 -0
- package/src/lib/components/crm-email-check.js +38 -1
- package/src/lib/components/crm-email-check.js.map +1 -1
- package/src/lib/components/crm-glance-card.js +1 -0
- package/src/lib/components/crm-glance-card.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.d.ts +5 -4
- package/src/lib/components/crm-next-activity-column.js +5 -4
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/crm-record-header.js +2 -0
- package/src/lib/components/crm-record-header.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +3 -1
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-section.js +51 -6
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +4 -0
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-history-card.js +1 -0
- package/src/lib/components/lead-history-card.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +9 -3
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-surfaces-note.js +1 -0
- package/src/lib/components/lead-surfaces-note.js.map +1 -1
- package/src/lib/components/leads-section.js +61 -11
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.js +1 -0
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/picklist-values-card.js +1 -0
- package/src/lib/components/picklist-values-card.js.map +1 -1
- package/src/lib/components/recipes-card.js +1 -0
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/reports/activity-card.js +1 -0
- package/src/lib/components/reports/activity-card.js.map +1 -1
- package/src/lib/components/reports/closed-deals-card.js +1 -0
- package/src/lib/components/reports/closed-deals-card.js.map +1 -1
- package/src/lib/components/reports/contacts-trend-card.js +1 -0
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.js +1 -0
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.js +1 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -1
- package/src/lib/components/reports/pipeline-card.js +1 -0
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/source-conversion-card.js +1 -0
- package/src/lib/components/reports/source-conversion-card.js.map +1 -1
- package/src/lib/components/reports/tasks-card.js +1 -0
- package/src/lib/components/reports/tasks-card.js.map +1 -1
- package/src/lib/components/reports-section.js +2 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/tasks-calendar.js +1 -0
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.js +61 -9
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/company-filters.js +40 -2
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +10 -1
- package/src/lib/constants/contact-filters.js +62 -2
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.js +78 -0
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
- package/src/lib/hooks/use-crm-column-sort.js +156 -0
- package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
- package/src/lib/hooks/use-crm-tasks.js +10 -8
- package/src/lib/hooks/use-crm-tasks.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +6 -0
- package/src/lib/model/crm-list-query.js +13 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +19 -2
- package/src/lib/model/lead-filters.js +112 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/native-vocabulary.d.ts +41 -0
- package/src/lib/model/native-vocabulary.js +59 -0
- package/src/lib/model/native-vocabulary.js.map +1 -0
- package/src/lib/model/task-views.d.ts +15 -2
- package/src/lib/model/task-views.js +50 -4
- package/src/lib/model/task-views.js.map +1 -1
- package/src/lib/server/email-check.d.ts +4 -1
- package/src/lib/server/email-check.js +18 -6
- package/src/lib/server/email-check.js.map +1 -1
- package/src/lib/transfer/tasks.js +2 -1
- package/src/lib/transfer/tasks.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-crm",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.233",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.233",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.233",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.233",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.233",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.233",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.233",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.233",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.233",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.233",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.233",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.233",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.233",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.233",
|
|
41
41
|
"@dnd-kit/core": "^6.3.1",
|
|
42
42
|
"@dnd-kit/utilities": "^3.2.2",
|
|
43
43
|
"@swc/helpers": "0.5.23",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/activity-timeline.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 mdiChevronDown,\n mdiChevronRight,\n mdiUnfoldLessHorizontal,\n mdiUnfoldMoreHorizontal,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport type { ListPaginationProps } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { Box, Button, Collapse, IconButton, Stack, Typography } from '@mui/material'\nimport { type ReactNode, useCallback, useEffect, useId, useMemo, useState } from 'react'\n\n/*\n * THE ROW GRAMMAR OF EVERY CRM HISTORY: the Timeline on a contact, the\n * Activity card on a lead, a company and a deal, and the Recent activity\n * feed under the contacts list.\n *\n * Salesforce's activity timeline is the model. Each entry is a collapsed\n * row — a chevron, the kind, a one-line heading and the who/whom/when under\n * it — that opens in place to what it holds: an email's body, a note's whole\n * text. The card header carries \"Expand all\", the list is paged by the\n * console's own footer, and nothing a row showed before is dropped: what\n * does not fit on the collapsed row is in the opened one.\n */\n\n/** Which entries of one history are open. */\nexport interface TimelineExpansion {\n isExpanded: (key: string) => boolean\n toggle: (key: string) => void\n /** Every entry is open and none has been closed since. */\n allExpanded: boolean\n setAllExpanded: (expanded: boolean) => void\n}\n\n/**\n * The open/closed state of a history's entries, held by the CARD so that\n * the header's \"Expand all\" and each row's chevron act on one state.\n *\n * Kept as a default plus the keys that differ from it, so \"Expand all\"\n * reaches the entries of pages the reader has not turned to yet, and a row\n * closed after it stays closed.\n */\nexport function useTimelineExpansion(): TimelineExpansion {\n const [state, setState] = useState<{ all: boolean; flipped: ReadonlySet<string> }>(\n () => ({ all: false, flipped: new Set<string>() }),\n )\n const isExpanded = useCallback(\n (key: string) => state.all !== state.flipped.has(key),\n [state],\n )\n const toggle = useCallback((key: string) => {\n setState((current) => {\n const flipped = new Set(current.flipped)\n if (flipped.has(key)) flipped.delete(key)\n else flipped.add(key)\n return { all: current.all, flipped }\n })\n }, [])\n const setAllExpanded = useCallback((all: boolean) => {\n setState({ all, flipped: new Set<string>() })\n }, [])\n return useMemo(\n () => ({\n isExpanded,\n toggle,\n allExpanded: state.all && state.flipped.size === 0,\n setAllExpanded,\n }),\n [isExpanded, toggle, state, setAllExpanded],\n )\n}\n\n/** The card header's \"Expand all\" / \"Collapse all\". */\nexport function TimelineExpandAllButton(props: {\n expansion: TimelineExpansion\n disabled?: boolean\n}) {\n const { expansion, disabled } = props\n const open = expansion.allExpanded\n return (\n <Button\n size=\"small\"\n disabled={disabled}\n startIcon={\n <MdiIcon\n path={(open ? mdiUnfoldLessHorizontal : mdiUnfoldMoreHorizontal).path}\n size={0.8}\n />\n }\n onClick={() => expansion.setAllExpanded(!open)}\n >\n {open ? 'Collapse all' : 'Expand all'}\n </Button>\n )\n}\nTimelineExpandAllButton.displayName = 'TimelineExpandAllButton'\n\n/** The listener window a history pages over. */\nexport interface TimelineWindow {\n /** A further page exists past the rows the listener holds. */\n hasMore?: boolean\n /** Widens the listener by one step. */\n showMore?: () => void\n /** The listener is reading; the page asked for is held rather than pulled back. */\n loading?: boolean\n}\n\n/**\n * Newest-first pages of a history, over rows a listener window holds.\n *\n * The window is the query's bound — a hundred activities on a record, a\n * page's worth on the feed — and a page the reader turns to past what it\n * holds widens it, so the footer's Next is live while the probe row says\n * more exists and the count stays \"of more than\" until it does not. Paged\n * in memory otherwise: the contact timeline merges three sources that share\n * no cursor, so only a slice of the merged stream can be turned.\n */\nexport function useTimelinePages(total: number, reader: TimelineWindow = {}) {\n const { hasMore = false, showMore, loading = false } = reader\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const lastPage = Math.max(0, Math.ceil(total / pageSize) - 1)\n // A page left empty by a delete shows the last one with rows; while the\n // window is reading, or holds less than exists, the page asked for stands.\n const shown = hasMore || loading ? page : Math.min(page, lastPage)\n const start = shown * pageSize\n const short = start + pageSize > total\n useEffect(() => {\n if (short && hasMore && !loading && showMore) showMore()\n }, [short, hasMore, loading, showMore])\n const slice = useCallback(\n <T,>(rows: readonly T[]): T[] => rows.slice(start, start + pageSize),\n [start, pageSize],\n )\n const footer: ListPaginationProps = {\n page: shown,\n pageSize,\n rowCount: Math.max(0, Math.min(pageSize, total - start)),\n count: hasMore ? undefined : total,\n hasMore,\n onPageChange: setPage,\n onPageSizeChange: setPageSize,\n }\n return { page: shown, pageSize, slice, footer }\n}\n\nexport interface TimelineEntryProps {\n /** The glyph for the entry's kind. */\n icon: ReactNode\n /** The chips that say what the entry is: its kind, its direction, its delivery state. */\n chips?: ReactNode\n /** The one line a collapsed entry reads as: a subject, a summary, a note's first line. */\n title?: ReactNode\n /** A subject reads as one; a note's own words as body text. */\n titleVariant?: 'subtitle2' | 'body2'\n /** The line under it: who, to or from whom, when. */\n meta?: ReactNode\n /** What the entry opens to. Absent, there is nothing more to show and no chevron. */\n body?: ReactNode\n /** Controls on this one entry — edit, delete. */\n actions?: ReactNode\n expanded?: boolean\n onToggle?: () => void\n /** Names the chevron, \"Expand {label}\", so a screen reader knows which entry. */\n label?: string\n}\n\n/**\n * One entry of a history: collapsed to its heading and caption, opened in\n * place to its body.\n *\n * An entry with no body has no chevron — a spacer keeps its glyph in line\n * with the rows that have one — and its heading wraps rather than\n * truncating, since there is nowhere else to read the rest of it.\n */\nexport function TimelineEntry(props: TimelineEntryProps) {\n const {\n icon,\n chips,\n title,\n titleVariant = 'body2',\n meta,\n body,\n actions,\n expanded = false,\n onToggle,\n label,\n } = props\n const bodyId = useId()\n const expandable = Boolean(body) && Boolean(onToggle)\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n data-testid=\"timeline-entry\"\n data-expanded={expandable ? String(expanded) : undefined}\n >\n {expandable ? (\n <IconButton\n size=\"small\"\n aria-expanded={expanded}\n aria-controls={bodyId}\n aria-label={`${expanded ? 'Collapse' : 'Expand'} ${label ?? 'entry'}`}\n onClick={onToggle}\n sx={{ mt: -0.25 }}\n >\n <MdiIcon path={(expanded ? mdiChevronDown : mdiChevronRight).path} size={0.8} />\n </IconButton>\n ) : (\n <Box aria-hidden sx={{ width: 30, flexShrink: 0 }} />\n )}\n <Stack\n sx={{\n color: 'text.secondary',\n pt: 0.25,\n fontSize: (theme) => theme.typography.h6.fontSize,\n }}\n >\n {icon}\n </Stack>\n <Stack spacing={0.5} sx={{ flex: 1, minWidth: 0 }}>\n {chips ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {chips}\n </Stack>\n ) : null}\n {title ? (\n <Typography\n variant={titleVariant}\n component=\"div\"\n noWrap={expandable && !expanded}\n sx={{ overflowWrap: 'anywhere' }}\n >\n {title}\n </Typography>\n ) : null}\n {meta}\n {expandable ? (\n <Collapse in={expanded} unmountOnExit>\n <Box id={bodyId}>{body}</Box>\n </Collapse>\n ) : (\n body\n )}\n </Stack>\n {actions ? (\n <Stack direction=\"row\" spacing={0.5}>\n {actions}\n </Stack>\n ) : null}\n </Stack>\n )\n}\nTimelineEntry.displayName = 'TimelineEntry'\n"],"names":["mdiChevronDown","mdiChevronRight","mdiUnfoldLessHorizontal","mdiUnfoldMoreHorizontal","MdiIcon","TABLE_PAGE_SIZE_DEFAULT","Box","Button","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useId","useMemo","useState","useTimelineExpansion","state","setState","all","flipped","Set","isExpanded","key","has","toggle","current","delete","add","setAllExpanded","allExpanded","size","TimelineExpandAllButton","props","expansion","disabled","open","startIcon","path","onClick","displayName","useTimelinePages","total","reader","hasMore","showMore","loading","page","setPage","pageSize","setPageSize","lastPage","Math","max","ceil","shown","min","start","short","slice","rows","footer","rowCount","count","undefined","onPageChange","onPageSizeChange","TimelineEntry","icon","chips","title","titleVariant","meta","body","actions","expanded","onToggle","label","bodyId","expandable","Boolean","direction","spacing","sx","alignItems","data-testid","data-expanded","String","aria-expanded","aria-controls","aria-label","mt","aria-hidden","width","flexShrink","color","pt","fontSize","theme","typography","h6","flex","minWidth","flexWrap","rowGap","variant","component","noWrap","overflowWrap","in","unmountOnExit","id"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/activity-timeline.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 mdiChevronDown,\n mdiChevronRight,\n mdiUnfoldLessHorizontal,\n mdiUnfoldMoreHorizontal,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport type { ListPaginationProps } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { Box, Button, Collapse, IconButton, Stack, Typography } from '@mui/material'\nimport { type ReactNode, useCallback, useEffect, useId, useMemo, useState } from 'react'\n\n/*\n * THE ROW GRAMMAR OF EVERY CRM HISTORY: the Timeline on a contact, the\n * Activity card on a lead, a company and a deal, and the Recent activity\n * feed under the contacts list.\n *\n * Salesforce's activity timeline is the model. Each entry is a collapsed\n * row — a chevron, the kind, a one-line heading and the who/whom/when under\n * it — that opens in place to what it holds: an email's body, a note's whole\n * text. The card header carries \"Expand all\", the list is paged by the\n * console's own footer, and nothing a row showed before is dropped: what\n * does not fit on the collapsed row is in the opened one.\n */\n\n/** Which entries of one history are open. */\nexport interface TimelineExpansion {\n isExpanded: (key: string) => boolean\n toggle: (key: string) => void\n /** Every entry is open and none has been closed since. */\n allExpanded: boolean\n setAllExpanded: (expanded: boolean) => void\n}\n\n/**\n * The open/closed state of a history's entries, held by the CARD so that\n * the header's \"Expand all\" and each row's chevron act on one state.\n *\n * Kept as a default plus the keys that differ from it, so \"Expand all\"\n * reaches the entries of pages the reader has not turned to yet, and a row\n * closed after it stays closed.\n */\nexport function useTimelineExpansion(): TimelineExpansion {\n const [state, setState] = useState<{ all: boolean; flipped: ReadonlySet<string> }>(\n () => ({ all: false, flipped: new Set<string>() }),\n )\n const isExpanded = useCallback(\n (key: string) => state.all !== state.flipped.has(key),\n [state],\n )\n const toggle = useCallback((key: string) => {\n setState((current) => {\n const flipped = new Set(current.flipped)\n if (flipped.has(key)) flipped.delete(key)\n else flipped.add(key)\n return { all: current.all, flipped }\n })\n }, [])\n const setAllExpanded = useCallback((all: boolean) => {\n setState({ all, flipped: new Set<string>() })\n }, [])\n return useMemo(\n () => ({\n isExpanded,\n toggle,\n allExpanded: state.all && state.flipped.size === 0,\n setAllExpanded,\n }),\n [isExpanded, toggle, state, setAllExpanded],\n )\n}\n\n/** The card header's \"Expand all\" / \"Collapse all\". */\nexport function TimelineExpandAllButton(props: {\n expansion: TimelineExpansion\n disabled?: boolean\n}) {\n const { expansion, disabled } = props\n const open = expansion.allExpanded\n return (\n <Button\n size=\"small\"\n disabled={disabled}\n startIcon={\n <MdiIcon\n path={(open ? mdiUnfoldLessHorizontal : mdiUnfoldMoreHorizontal).path}\n size={0.8}\n />\n }\n onClick={() => expansion.setAllExpanded(!open)}\n >\n {open ? 'Collapse all' : 'Expand all'}\n </Button>\n )\n}\nTimelineExpandAllButton.displayName = 'TimelineExpandAllButton'\n\n/** The listener window a history pages over. */\nexport interface TimelineWindow {\n /** A further page exists past the rows the listener holds. */\n hasMore?: boolean\n /** Widens the listener by one step. */\n showMore?: () => void\n /** The listener is reading; the page asked for is held rather than pulled back. */\n loading?: boolean\n}\n\n/**\n * Newest-first pages of a history, over rows a listener window holds.\n *\n * The window is the query's bound — a hundred activities on a record, a\n * page's worth on the feed — and a page the reader turns to past what it\n * holds widens it, so the footer's Next is live while the probe row says\n * more exists and the count stays \"of more than\" until it does not. Paged\n * in memory otherwise: the contact timeline merges three sources that share\n * no cursor, so only a slice of the merged stream can be turned.\n */\nexport function useTimelinePages(total: number, reader: TimelineWindow = {}) {\n const { hasMore = false, showMore, loading = false } = reader\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const lastPage = Math.max(0, Math.ceil(total / pageSize) - 1)\n // A page left empty by a delete shows the last one with rows; while the\n // window is reading, or holds less than exists, the page asked for stands.\n const shown = hasMore || loading ? page : Math.min(page, lastPage)\n const start = shown * pageSize\n const short = start + pageSize > total\n useEffect(() => {\n if (short && hasMore && !loading && showMore) showMore()\n }, [short, hasMore, loading, showMore])\n const slice = useCallback(\n <T,>(rows: readonly T[]): T[] => rows.slice(start, start + pageSize),\n [start, pageSize],\n )\n const footer: ListPaginationProps = {\n page: shown,\n pageSize,\n rowCount: Math.max(0, Math.min(pageSize, total - start)),\n count: hasMore ? undefined : total,\n hasMore,\n onPageChange: setPage,\n onPageSizeChange: setPageSize,\n }\n return { page: shown, pageSize, slice, footer }\n}\n\nexport interface TimelineEntryProps {\n /** The glyph for the entry's kind. */\n icon: ReactNode\n /** The chips that say what the entry is: its kind, its direction, its delivery state. */\n chips?: ReactNode\n /** The one line a collapsed entry reads as: a subject, a summary, a note's first line. */\n title?: ReactNode\n /** A subject reads as one; a note's own words as body text. */\n titleVariant?: 'subtitle2' | 'body2'\n /** The line under it: who, to or from whom, when. */\n meta?: ReactNode\n /** What the entry opens to. Absent, there is nothing more to show and no chevron. */\n body?: ReactNode\n /** Controls on this one entry — edit, delete. */\n actions?: ReactNode\n expanded?: boolean\n onToggle?: () => void\n /** Names the chevron, \"Expand {label}\", so a screen reader knows which entry. */\n label?: string\n}\n\n/**\n * One entry of a history: collapsed to its heading and caption, opened in\n * place to its body.\n *\n * An entry with no body has no chevron — a spacer keeps its glyph in line\n * with the rows that have one — and its heading wraps rather than\n * truncating, since there is nowhere else to read the rest of it.\n */\nexport function TimelineEntry(props: TimelineEntryProps) {\n const {\n icon,\n chips,\n title,\n titleVariant = 'body2',\n meta,\n body,\n actions,\n expanded = false,\n onToggle,\n label,\n } = props\n const bodyId = useId()\n const expandable = Boolean(body) && Boolean(onToggle)\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n data-testid=\"timeline-entry\"\n data-expanded={expandable ? String(expanded) : undefined}\n >\n {expandable ? (\n <IconButton\n size=\"small\"\n aria-expanded={expanded}\n aria-controls={bodyId}\n aria-label={`${expanded ? 'Collapse' : 'Expand'} ${label ?? 'entry'}`}\n onClick={onToggle}\n sx={{ mt: -0.25 }}\n >\n <MdiIcon path={(expanded ? mdiChevronDown : mdiChevronRight).path} size={0.8} />\n </IconButton>\n ) : (\n <Box aria-hidden sx={{ width: 30, flexShrink: 0 }} />\n )}\n <Stack\n sx={{\n color: 'text.secondary',\n pt: 0.25,\n fontSize: (theme) => theme.typography.h6.fontSize,\n }}\n >\n {icon}\n </Stack>\n <Stack spacing={0.5} sx={{ flex: 1, minWidth: 0 }}>\n {chips ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {chips}\n </Stack>\n ) : null}\n {title ? (\n <Typography\n variant={titleVariant}\n component=\"div\"\n noWrap={expandable && !expanded}\n sx={{ overflowWrap: 'anywhere' }}\n >\n {title}\n </Typography>\n ) : null}\n {meta}\n {expandable ? (\n <Collapse in={expanded} unmountOnExit>\n <Box id={bodyId}>{body}</Box>\n </Collapse>\n ) : (\n body\n )}\n </Stack>\n {actions ? (\n <Stack direction=\"row\" spacing={0.5}>\n {actions}\n </Stack>\n ) : null}\n </Stack>\n )\n}\nTimelineEntry.displayName = 'TimelineEntry'\n"],"names":["mdiChevronDown","mdiChevronRight","mdiUnfoldLessHorizontal","mdiUnfoldMoreHorizontal","MdiIcon","TABLE_PAGE_SIZE_DEFAULT","Box","Button","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useId","useMemo","useState","useTimelineExpansion","state","setState","all","flipped","Set","isExpanded","key","has","toggle","current","delete","add","setAllExpanded","allExpanded","size","TimelineExpandAllButton","props","expansion","disabled","open","startIcon","path","onClick","displayName","useTimelinePages","total","reader","hasMore","showMore","loading","page","setPage","pageSize","setPageSize","lastPage","Math","max","ceil","shown","min","start","short","slice","rows","footer","rowCount","count","undefined","onPageChange","onPageSizeChange","TimelineEntry","icon","chips","title","titleVariant","meta","body","actions","expanded","onToggle","label","bodyId","expandable","Boolean","direction","spacing","sx","alignItems","data-testid","data-expanded","String","aria-expanded","aria-controls","aria-label","mt","aria-hidden","width","flexShrink","color","pt","fontSize","theme","typography","h6","flex","minWidth","useFlexGap","flexWrap","rowGap","variant","component","noWrap","overflowWrap","in","unmountOnExit","id"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/assignment-rule-drawer.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 CONTACT_SOURCE_LABELS,\n type ContactSource,\n type CrmAssignmentRule,\n newAssignmentRuleId,\n readCrmAssignmentRule,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport {\n Button,\n Drawer,\n FormControl,\n FormControlLabel,\n FormHelperText,\n FormLabel,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { OrgMemberOption } from '../hooks/use-org-member-directory'\n\n/** The `value` of the source picker's \"any door\" entry. */\nconst ANY_SOURCE = ''\n\nexport interface AssignmentRuleDrawerProps {\n open: boolean\n onClose: () => void\n /** The roster the member target is picked from. */\n members: readonly OrgMemberOption[]\n membersLoading: boolean\n /** How many people the round-robin pool holds, for the target's helper text. */\n poolSize: number\n /** The ids the org already holds, so the new one is minted apart from them. */\n existingIds: readonly string[]\n /** Receives the rule; closing on success is the caller's job. */\n onSubmit: (rule: CrmAssignmentRule) => Promise<void> | void\n}\n\n/**\n * The one form an assignment rule is written through (AGL-2618).\n *\n * ## Every condition is optional, and none is a form above the list\n *\n * A rule names the captures it claims — a source, a form, an email domain,\n * a tag — and a field left blank is not a condition, so a rule with every\n * field blank is the catch-all, which is a rule a team writes on purpose\n * (\"everything else goes to Sam\"). The drawer says so rather than refusing\n * it. The email domain is typed the way a person sees one in an address,\n * `@acme.com` or `acme.com`, and the reader strips the `@`; what it cannot\n * read as a domain is refused here, with the field, rather than stored as\n * a condition nothing will ever match.\n *\n * ## The target is a member, or the rotation\n *\n * A member is picked from the roster by name, never typed as an address —\n * an address is resolved at run time and a rule must not depend on a\n * resolution that can change. The rotation is offered whatever the pool\n * holds, with the pool's size beside it: an empty pool is a rule the pass\n * steps over until somebody fills the pool, which the card below explains,\n * and refusing the rule here would make the two cards order-dependent.\n */\nexport function AssignmentRuleDrawer(props: AssignmentRuleDrawerProps) {\n const { open, onClose, members, membersLoading, poolSize, existingIds, onSubmit } =\n props\n const [source, setSource] = useState<string>(ANY_SOURCE)\n const [formId, setFormId] = useState('')\n const [emailDomain, setEmailDomain] = useState('')\n const [tag, setTag] = useState('')\n const [target, setTarget] = useState<'member' | 'roundRobin'>('member')\n const [memberUid, setMemberUid] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Seeded on every open, so a drawer that was cancelled halfway does not\n // offer the abandoned draft as the next rule.\n useEffect(() => {\n if (!open) return\n setSource(ANY_SOURCE)\n setFormId('')\n setEmailDomain('')\n setTag('')\n setTarget('member')\n setMemberUid('')\n setBusy(false)\n setError(null)\n }, [open])\n\n const domainTyped = emailDomain.trim().length > 0\n const domainReadable =\n !domainTyped ||\n readCrmAssignmentRule({\n id: 'probe',\n when: { emailDomain },\n assign: { roundRobin: true },\n })?.when.emailDomain !== undefined\n const canSubmit =\n !busy && domainReadable && (target === 'roundRobin' || memberUid.length > 0)\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return\n const rule = readCrmAssignmentRule({\n id: newAssignmentRuleId(existingIds),\n when: {\n ...(source ? { source } : {}),\n ...(formId.trim() ? { formId: formId.trim() } : {}),\n ...(emailDomain.trim() ? { emailDomain: emailDomain.trim() } : {}),\n ...(tag.trim() ? { tag: tag.trim() } : {}),\n },\n assign: target === 'roundRobin' ? { roundRobin: true } : { memberUid },\n })\n if (!rule) {\n setError('The rule could not be read back. Check each field.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n await onSubmit(rule)\n } catch (caught) {\n console.error(caught)\n setError('Could not save the rule. Try again.')\n } finally {\n setBusy(false)\n }\n }, [canSubmit, existingIds, source, formId, emailDomain, tag, target, memberUid, onSubmit])\n\n const conditions = [source, formId.trim(), emailDomain.trim(), tag.trim()].filter(Boolean)\n\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{'New assignment rule'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'When a new contact matches every condition below, it is assigned ' +\n 'as the rule says. Leave every condition blank for a rule that ' +\n 'catches everything the rules above it did not.'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Source\"\n value={source}\n onChange={(event) => setSource(event.target.value)}\n fullWidth\n >\n <MenuItem value={ANY_SOURCE}>{'Any source'}</MenuItem>\n {(Object.keys(CONTACT_SOURCE_LABELS) as ContactSource[]).map((option) => (\n <MenuItem key={option} value={option}>\n {CONTACT_SOURCE_LABELS[option]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n helperText=\"The form's id, from its address in Forms. Blank for any form.\"\n slotProps={{ htmlInput: { maxLength: 128, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Email domain\"\n value={emailDomain}\n onChange={(event) => setEmailDomain(event.target.value)}\n error={!domainReadable}\n helperText={\n domainReadable\n ? 'acme.com — the domain of the captured address. Public mailboxes such as gmail.com count.'\n : 'That does not look like a domain.'\n }\n slotProps={{ htmlInput: { maxLength: 253, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tag\"\n value={tag}\n onChange={(event) => setTag(event.target.value)}\n helperText=\"A tag the capture carries, or the contact already has.\"\n slotProps={{ htmlInput: { maxLength: 60 } }}\n fullWidth\n />\n <FormControl>\n <FormLabel id=\"assignment-rule-target\">{'Assign to'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"assignment-rule-target\"\n value={target}\n onChange={(event) =>\n setTarget(event.target.value === 'roundRobin' ? 'roundRobin' : 'member')\n }\n >\n <FormControlLabel value=\"member\" control={<Radio size=\"small\" />} label=\"A team member\" />\n <FormControlLabel\n value=\"roundRobin\"\n control={<Radio size=\"small\" />}\n label=\"Round robin — the next member of the pool\"\n />\n </RadioGroup>\n {target === 'roundRobin' ? (\n <FormHelperText>\n {poolSize > 0\n ? `The pool has ${poolSize} ${poolSize === 1 ? 'member' : 'members'}; each new contact goes to the next in turn.`\n : 'The pool is empty. This rule is skipped until somebody is added to it below.'}\n </FormHelperText>\n ) : null}\n </FormControl>\n {target === 'member' ? (\n <TextField\n select\n size=\"small\"\n label=\"Member\"\n value={memberUid}\n onChange={(event) => setMemberUid(event.target.value)}\n disabled={membersLoading}\n helperText={\n membersLoading\n ? 'Loading the team…'\n : members.length\n ? undefined\n : 'The team could not be listed.'\n }\n fullWidth\n >\n {members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {conditions.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No conditions: this rule matches every new contact the rules above it did not claim.'}\n </Typography>\n ) : null}\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSubmit} onClick={handleSubmit}>\n {'Add rule'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Drawer>\n )\n}\nAssignmentRuleDrawer.displayName = 'AssignmentRuleDrawer'\n\nexport default AssignmentRuleDrawer\n"],"names":["CONTACT_SOURCE_LABELS","newAssignmentRuleId","readCrmAssignmentRule","crmMemberPickerLabel","Button","Drawer","FormControl","FormControlLabel","FormHelperText","FormLabel","MenuItem","Radio","RadioGroup","Stack","TextField","Typography","useCallback","useEffect","useState","ANY_SOURCE","AssignmentRuleDrawer","props","open","onClose","members","membersLoading","poolSize","existingIds","onSubmit","source","setSource","formId","setFormId","emailDomain","setEmailDomain","tag","setTag","target","setTarget","memberUid","setMemberUid","busy","setBusy","error","setError","domainTyped","trim","length","domainReadable","id","when","assign","roundRobin","undefined","canSubmit","handleSubmit","rule","caught","console","conditions","filter","Boolean","anchor","spacing","sx","width","p","variant","color","select","size","label","value","onChange","event","fullWidth","Object","keys","map","option","helperText","slotProps","htmlInput","maxLength","spellCheck","aria-labelledby","control","disabled","member","uid","direction","onClick","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/assignment-rule-drawer.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 CONTACT_SOURCE_LABELS,\n type ContactSource,\n type CrmAssignmentRule,\n newAssignmentRuleId,\n readCrmAssignmentRule,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport {\n Button,\n Drawer,\n FormControl,\n FormControlLabel,\n FormHelperText,\n FormLabel,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { OrgMemberOption } from '../hooks/use-org-member-directory'\n\n/** The `value` of the source picker's \"any door\" entry. */\nconst ANY_SOURCE = ''\n\nexport interface AssignmentRuleDrawerProps {\n open: boolean\n onClose: () => void\n /** The roster the member target is picked from. */\n members: readonly OrgMemberOption[]\n membersLoading: boolean\n /** How many people the round-robin pool holds, for the target's helper text. */\n poolSize: number\n /** The ids the org already holds, so the new one is minted apart from them. */\n existingIds: readonly string[]\n /** Receives the rule; closing on success is the caller's job. */\n onSubmit: (rule: CrmAssignmentRule) => Promise<void> | void\n}\n\n/**\n * The one form an assignment rule is written through (AGL-2618).\n *\n * ## Every condition is optional, and none is a form above the list\n *\n * A rule names the captures it claims — a source, a form, an email domain,\n * a tag — and a field left blank is not a condition, so a rule with every\n * field blank is the catch-all, which is a rule a team writes on purpose\n * (\"everything else goes to Sam\"). The drawer says so rather than refusing\n * it. The email domain is typed the way a person sees one in an address,\n * `@acme.com` or `acme.com`, and the reader strips the `@`; what it cannot\n * read as a domain is refused here, with the field, rather than stored as\n * a condition nothing will ever match.\n *\n * ## The target is a member, or the rotation\n *\n * A member is picked from the roster by name, never typed as an address —\n * an address is resolved at run time and a rule must not depend on a\n * resolution that can change. The rotation is offered whatever the pool\n * holds, with the pool's size beside it: an empty pool is a rule the pass\n * steps over until somebody fills the pool, which the card below explains,\n * and refusing the rule here would make the two cards order-dependent.\n */\nexport function AssignmentRuleDrawer(props: AssignmentRuleDrawerProps) {\n const { open, onClose, members, membersLoading, poolSize, existingIds, onSubmit } =\n props\n const [source, setSource] = useState<string>(ANY_SOURCE)\n const [formId, setFormId] = useState('')\n const [emailDomain, setEmailDomain] = useState('')\n const [tag, setTag] = useState('')\n const [target, setTarget] = useState<'member' | 'roundRobin'>('member')\n const [memberUid, setMemberUid] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Seeded on every open, so a drawer that was cancelled halfway does not\n // offer the abandoned draft as the next rule.\n useEffect(() => {\n if (!open) return\n setSource(ANY_SOURCE)\n setFormId('')\n setEmailDomain('')\n setTag('')\n setTarget('member')\n setMemberUid('')\n setBusy(false)\n setError(null)\n }, [open])\n\n const domainTyped = emailDomain.trim().length > 0\n const domainReadable =\n !domainTyped ||\n readCrmAssignmentRule({\n id: 'probe',\n when: { emailDomain },\n assign: { roundRobin: true },\n })?.when.emailDomain !== undefined\n const canSubmit =\n !busy && domainReadable && (target === 'roundRobin' || memberUid.length > 0)\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return\n const rule = readCrmAssignmentRule({\n id: newAssignmentRuleId(existingIds),\n when: {\n ...(source ? { source } : {}),\n ...(formId.trim() ? { formId: formId.trim() } : {}),\n ...(emailDomain.trim() ? { emailDomain: emailDomain.trim() } : {}),\n ...(tag.trim() ? { tag: tag.trim() } : {}),\n },\n assign: target === 'roundRobin' ? { roundRobin: true } : { memberUid },\n })\n if (!rule) {\n setError('The rule could not be read back. Check each field.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n await onSubmit(rule)\n } catch (caught) {\n console.error(caught)\n setError('Could not save the rule. Try again.')\n } finally {\n setBusy(false)\n }\n }, [canSubmit, existingIds, source, formId, emailDomain, tag, target, memberUid, onSubmit])\n\n const conditions = [source, formId.trim(), emailDomain.trim(), tag.trim()].filter(Boolean)\n\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n <Stack spacing={2} sx={{ width: 380, maxWidth: '100vw', p: 3 }}>\n <Typography variant=\"h6\">{'New assignment rule'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'When a new contact matches every condition below, it is assigned ' +\n 'as the rule says. Leave every condition blank for a rule that ' +\n 'catches everything the rules above it did not.'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Source\"\n value={source}\n onChange={(event) => setSource(event.target.value)}\n fullWidth\n >\n <MenuItem value={ANY_SOURCE}>{'Any source'}</MenuItem>\n {(Object.keys(CONTACT_SOURCE_LABELS) as ContactSource[]).map((option) => (\n <MenuItem key={option} value={option}>\n {CONTACT_SOURCE_LABELS[option]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n helperText=\"The form's id, from its address in Forms. Blank for any form.\"\n slotProps={{ htmlInput: { maxLength: 128, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Email domain\"\n value={emailDomain}\n onChange={(event) => setEmailDomain(event.target.value)}\n error={!domainReadable}\n helperText={\n domainReadable\n ? 'acme.com — the domain of the captured address. Public mailboxes such as gmail.com count.'\n : 'That does not look like a domain.'\n }\n slotProps={{ htmlInput: { maxLength: 253, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tag\"\n value={tag}\n onChange={(event) => setTag(event.target.value)}\n helperText=\"A tag the capture carries, or the contact already has.\"\n slotProps={{ htmlInput: { maxLength: 60 } }}\n fullWidth\n />\n <FormControl>\n <FormLabel id=\"assignment-rule-target\">{'Assign to'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"assignment-rule-target\"\n value={target}\n onChange={(event) =>\n setTarget(event.target.value === 'roundRobin' ? 'roundRobin' : 'member')\n }\n >\n <FormControlLabel value=\"member\" control={<Radio size=\"small\" />} label=\"A team member\" />\n <FormControlLabel\n value=\"roundRobin\"\n control={<Radio size=\"small\" />}\n label=\"Round robin — the next member of the pool\"\n />\n </RadioGroup>\n {target === 'roundRobin' ? (\n <FormHelperText>\n {poolSize > 0\n ? `The pool has ${poolSize} ${poolSize === 1 ? 'member' : 'members'}; each new contact goes to the next in turn.`\n : 'The pool is empty. This rule is skipped until somebody is added to it below.'}\n </FormHelperText>\n ) : null}\n </FormControl>\n {target === 'member' ? (\n <TextField\n select\n size=\"small\"\n label=\"Member\"\n value={memberUid}\n onChange={(event) => setMemberUid(event.target.value)}\n disabled={membersLoading}\n helperText={\n membersLoading\n ? 'Loading the team…'\n : members.length\n ? undefined\n : 'The team could not be listed.'\n }\n fullWidth\n >\n {members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {conditions.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No conditions: this rule matches every new contact the rules above it did not claim.'}\n </Typography>\n ) : null}\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSubmit} onClick={handleSubmit}>\n {'Add rule'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Drawer>\n )\n}\nAssignmentRuleDrawer.displayName = 'AssignmentRuleDrawer'\n\nexport default AssignmentRuleDrawer\n"],"names":["CONTACT_SOURCE_LABELS","newAssignmentRuleId","readCrmAssignmentRule","crmMemberPickerLabel","Button","Drawer","FormControl","FormControlLabel","FormHelperText","FormLabel","MenuItem","Radio","RadioGroup","Stack","TextField","Typography","useCallback","useEffect","useState","ANY_SOURCE","AssignmentRuleDrawer","props","open","onClose","members","membersLoading","poolSize","existingIds","onSubmit","source","setSource","formId","setFormId","emailDomain","setEmailDomain","tag","setTag","target","setTarget","memberUid","setMemberUid","busy","setBusy","error","setError","domainTyped","trim","length","domainReadable","id","when","assign","roundRobin","undefined","canSubmit","handleSubmit","rule","caught","console","conditions","filter","Boolean","anchor","spacing","sx","width","maxWidth","p","variant","color","select","size","label","value","onChange","event","fullWidth","Object","keys","map","option","helperText","slotProps","htmlInput","maxLength","spellCheck","aria-labelledby","control","disabled","member","uid","direction","onClick","displayName"],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
-
import { CRM_COLLECTIONS, crmPicklistKey, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
|
|
19
|
+
import { CRM_COLLECTIONS, crmPicklistKey, crmPicklistRank, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
|
|
20
20
|
import { mdiPlus } from "@aglyn/shared-data-mdi";
|
|
21
21
|
import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
22
22
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
@@ -38,7 +38,8 @@ import { Button, Chip, Stack, Typography } from "@mui/material";
|
|
|
38
38
|
import { getGridSingleSelectOperators } from "@mui/x-data-grid";
|
|
39
39
|
import { useRouter } from "next/navigation";
|
|
40
40
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
41
|
-
import { COMPANY_LIST_DECLARATION, COMPANY_LIST_FILTER_FIELDS, COMPANY_PICKLIST_FILTERS, COMPANY_PREFIX_SEARCH } from "../constants/company-filters.js";
|
|
41
|
+
import { COMPANY_LIST_DECLARATION, COMPANY_LIST_FILTER_FIELDS, COMPANY_LIST_SORTS, COMPANY_PICKLIST_FILTERS, COMPANY_PREFIX_SEARCH } from "../constants/company-filters.js";
|
|
42
|
+
import { crmViewQuerySort, useCrmColumnSort } from "../hooks/use-crm-column-sort.js";
|
|
42
43
|
import { useCompanyPicklists } from "../hooks/use-company-picklists.js";
|
|
43
44
|
import { formatMoney } from "../model/deal-board-model.js";
|
|
44
45
|
import { useContactFieldDefinitions } from "../hooks/use-contact-field-definitions.js";
|
|
@@ -156,6 +157,10 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
156
157
|
const viewFilters = views.state.filters;
|
|
157
158
|
const [searchWords, setSearchWords] = useState([]);
|
|
158
159
|
const foldsScope = useCrmFoldsScope(orgId, visibleTo);
|
|
160
|
+
// The header order the view asks (AGL-3680), read before the query it orders.
|
|
161
|
+
const companySort = useMemo(()=>crmViewQuerySort(COMPANY_LIST_SORTS, views.state.sort, COMPANY_LIST_SORTS[0]), [
|
|
162
|
+
views.state.sort
|
|
163
|
+
]);
|
|
159
164
|
const { rows: companies, status, hasMore, page, setPage, pageSize, setPageSize, plan } = useCrmListQuery({
|
|
160
165
|
scope,
|
|
161
166
|
collection: CRM_COLLECTIONS.companies,
|
|
@@ -164,7 +169,8 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
164
169
|
declaration: COMPANY_LIST_DECLARATION,
|
|
165
170
|
clauses: viewFilters,
|
|
166
171
|
search: searchWords,
|
|
167
|
-
prefixSearch: COMPANY_PREFIX_SEARCH
|
|
172
|
+
prefixSearch: COMPANY_PREFIX_SEARCH,
|
|
173
|
+
sort: companySort.sort
|
|
168
174
|
});
|
|
169
175
|
const nowMs = useMemo(()=>Date.now(), []);
|
|
170
176
|
const [createOpen, setCreateOpen] = useState(false);
|
|
@@ -495,6 +501,32 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
495
501
|
* stored `equals` back as the single-select `is` the panel offers — so a
|
|
496
502
|
* view opened from its address reads as filtered, not as a mystery.
|
|
497
503
|
*/ const grid = useCrmViewGrid(views, filterColumns, COMPANY_HIDDEN_COLUMNS);
|
|
504
|
+
/*
|
|
505
|
+
* EVERY HEADER SORTS (AGL-3680). The grid used to sort the view's order
|
|
506
|
+
* over the one page on screen while the footer paged the query, which read
|
|
507
|
+
* as the whole list's order. Company, Updated and Next activity sort the
|
|
508
|
+
* QUERY now (`COMPANY_LIST_SORTS`); the rest sort the page and say so —
|
|
509
|
+
* Type, Industry and Rating in the order the org keeps their values.
|
|
510
|
+
*/ const pageSorts = useMemo(()=>Object.fromEntries(COMPANY_PICKLIST_FILTERS.map((entry)=>[
|
|
511
|
+
entry.column,
|
|
512
|
+
(row)=>{
|
|
513
|
+
const label = row[entry.column];
|
|
514
|
+
const list = picklists.lists[entry.picklistId];
|
|
515
|
+
return list && label ? crmPicklistRank(list, label) : null;
|
|
516
|
+
}
|
|
517
|
+
])), [
|
|
518
|
+
picklists.lists
|
|
519
|
+
]);
|
|
520
|
+
const columnSort = useCrmColumnSort({
|
|
521
|
+
views,
|
|
522
|
+
sorts: COMPANY_LIST_SORTS,
|
|
523
|
+
defaultSort: COMPANY_LIST_SORTS[0],
|
|
524
|
+
asked: companySort,
|
|
525
|
+
orderBy: plan.orderBy,
|
|
526
|
+
rows: companies,
|
|
527
|
+
columns: grid.columns,
|
|
528
|
+
pageSorts
|
|
529
|
+
});
|
|
498
530
|
/** Whether anything narrows the list, so an empty one is "no match", not "none yet". */ const narrowed = viewFilters.length > 0 || searchWords.some((word)=>word.trim());
|
|
499
531
|
const newCompanyButton = /*#__PURE__*/ _jsx(Button, {
|
|
500
532
|
size: "small",
|
|
@@ -563,7 +595,10 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
563
595
|
}),
|
|
564
596
|
/*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
565
597
|
refused: refused,
|
|
566
|
-
notices:
|
|
598
|
+
notices: [
|
|
599
|
+
...plan.notices,
|
|
600
|
+
...columnSort.notices
|
|
601
|
+
]
|
|
567
602
|
}),
|
|
568
603
|
/*#__PURE__*/ _jsx(CompaniesBulkBar, {
|
|
569
604
|
hostId: hostId,
|
|
@@ -579,7 +614,8 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
579
614
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
580
615
|
columns: grid.columns,
|
|
581
616
|
slots: CRM_LIST_SLOTS,
|
|
582
|
-
rows:
|
|
617
|
+
rows: columnSort.rows,
|
|
618
|
+
columnSort: columnSort,
|
|
583
619
|
selectable: {
|
|
584
620
|
selected: selectedIds,
|
|
585
621
|
onChange: setSelectedIds
|
|
@@ -598,11 +634,10 @@ const COMPANY_GRID_FILTER_HEADERS = _extends({
|
|
|
598
634
|
filterModel: gridFilter.filterModel,
|
|
599
635
|
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
600
636
|
quickFilter: true,
|
|
601
|
-
// Columns
|
|
637
|
+
// Columns are the view's, controlled (AGL-2617); so is the
|
|
638
|
+
// header order, which `columnSort` keeps in it (AGL-3680).
|
|
602
639
|
columnVisibilityModel: grid.columnVisibilityModel,
|
|
603
640
|
onColumnVisibilityModelChange: grid.onColumnVisibilityModelChange,
|
|
604
|
-
sortModel: grid.sortModel,
|
|
605
|
-
onSortModelChange: grid.onSortModelChange,
|
|
606
641
|
// Paged by the footer below, so the grid must not also slice.
|
|
607
642
|
hideFooter: true
|
|
608
643
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-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 AglynOrgBilling,\n CRM_COLLECTIONS,\n type CrmCompany,\n crmPicklistKey,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterColumn } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n COMPANY_LIST_DECLARATION,\n COMPANY_LIST_FILTER_FIELDS,\n COMPANY_PICKLIST_FILTERS,\n COMPANY_PREFIX_SEARCH,\n} from '../constants/company-filters'\nimport { useCompanyPicklists } from '../hooks/use-company-picklists'\nimport { formatMoney } from '../model/deal-board-model'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { CRM_COMPANIES_RESOURCE } from '../transfer/fields'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { customFieldColumns } from './contact-custom-columns'\n\nexport interface CompaniesSectionProps {\n /** The site the list is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n /** The CRM hub URL, which every company route hangs beneath. */\n basePath: string\n}\n\ntype CompanyRow = Partial<CrmCompany> & { $id: string; updatedAt?: any }\n\n/** What the companies grid's panel offers — every field the query asks. */\nconst COMPANY_GRID_FILTER_FIELDS = COMPANY_LIST_FILTER_FIELDS\nconst COMPANY_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Company',\n ownerUid: 'Owner',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n ...Object.fromEntries(COMPANY_PICKLIST_FILTERS.map((entry) => [entry.column, entry.header])),\n}\n\n/** The grid's select columns — the owner and each picklist — as the filter hook names them. */\nconst COMPANY_SELECT_FIELDS = ['ownerUid', ...COMPANY_PICKLIST_FILTERS.map((entry) => entry.column)]\n\n/**\n * Salesforce's Account columns (AGL-3514), optional: a reader turns one on\n * from the column menu, and a view that shows it stores it by name.\n */\nconst COMPANY_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = {\n type: false,\n industry: false,\n rating: false,\n accountSource: false,\n numberOfEmployees: false,\n annualRevenueCents: false,\n}\n\n/** A text cell that reads as a dash when empty. */\nfunction textCell(value: unknown) {\n const text = value === null || value === undefined ? '' : String(value)\n return text ? (\n <Typography variant=\"body2\" noWrap>\n {text}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n}\n\n/**\n * `/crm/companies` — the organizations behind the people (AGL-2597).\n *\n * A section of its own rather than a column on the contact list, because a\n * company is known by several contacts and carries records of its own: a\n * domain the email suggestion keys on, an address, an owner, and the deals\n * and tasks filed against it. The row opens `/crm/companies/{id}`, where all\n * of that lives; this list is the cheaper surface and reads nothing a row\n * does not show.\n *\n * ## What the listener carries\n *\n * Paged and ordered by the server — `updatedAt desc`, so a company somebody\n * just touched is on page one — and SCOPED by the same `visibleTo\n * array-contains-any` predicate the contact list runs. That predicate is what\n * the rules evaluate: a filtered query is provable per document, and an\n * unfiltered one is refused rather than quietly returning the whole org's\n * accounts to a site that may see one client's.\n *\n * ## What a filter may be\n *\n * Every clause and the search word are on the one query (AGL-3321): the\n * search reads the name's and the domain's word prefixes, the name is also\n * an exact match and a prefix, the owner a choice from the roster, and \"No\n * next activity\" a `null` — each reaching the whole collection, which is\n * the property a filter must have: a company on page four is found, not\n * reported missing. `COMPANY_LIST_DECLARATION` states what the query asks.\n *\n * ## Creating is a drawer\n *\n * \"New company\" opens the same eight-field drawer the company's page edits\n * with, and the new record's page opens when it is saved. A form above a\n * list has nowhere to grow, and this one has an address in it.\n *\n * ## Selection, the bar and the file (AGL-2621)\n *\n * The rows are selectable, and a selection raises `CompaniesBulkBar` over\n * the table. Import and Export open the console's import wizard and\n * export dialog on `crm.companies` (AGL-3527): the export over the\n * selection, the list's own query, or every company.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const { scope, orgId, visibleTo } = useCrmScope({ hostId, org })\n /*\n * The team, read once for this surface: the Owner column names a person\n * from a uid, and the create drawer picks from the same roster. One fetch\n * serves both, which is why the drawer takes it as a prop.\n */\n const members = useOrgMemberOptions(orgId)\n // The org's company fields, for the optional columns below (AGL-2661).\n const companyFields = useContactFieldDefinitions(orgId, 'company')\n // The lists behind Type, Industry and Rating, for their filters (AGL-3514).\n const picklists = useCompanyPicklists(orgId)\n\n /*\n * The clauses are the saved VIEW'S (AGL-2617), beside its columns and\n * sort; the grid's panel and the chips edit them, and the query answers\n * every one and the search word together (AGL-3321).\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n const viewFilters = views.state.filters\n const [searchWords, setSearchWords] = useState<string[]>([])\n const foldsScope = useCrmFoldsScope(orgId, visibleTo)\n const {\n rows: companies,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useCrmListQuery<CompanyRow>({\n scope,\n collection: CRM_COLLECTIONS.companies,\n visibleTo,\n foldsScope,\n declaration: COMPANY_LIST_DECLARATION,\n clauses: viewFilters,\n search: searchWords,\n prefixSearch: COMPANY_PREFIX_SEARCH,\n })\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A filter, a search or a page is a different set of rows.\n useEffect(() => setSelectedIds([]), [plan, page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3527).\n const exportFilter = useMemo(\n () => (plan.served.length || plan.searched ? { label: 'what the list shows', plan } : null),\n [plan],\n )\n const openCompany = useCallback(\n (id: string) => router.push(routes.company(id)),\n [router, routes],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Company',\n flex: 1.6,\n minWidth: 240,\n ...listFilterColumn(COMPANY_LIST_FILTER_FIELDS, 'name'),\n valueGetter: (_value, row: CompanyRow) => String(row.name ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Stack\n sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}\n >\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.name}\n </Typography>\n {row.industry ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ lineHeight: 1.25 }}\n noWrap\n >\n {row.industry}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'domain',\n headerName: 'Domain',\n flex: 1,\n minWidth: 160,\n // Stored normalized, so it could be filtered exactly — but a domain\n // lookup is what the company suggestion on a contact does, and a\n // filter here would need an index of its own for one more way to\n // find a row the name filter already finds.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.domain ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.domain ? (\n <Typography variant=\"body2\">{row.domain}</Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'contactsCount',\n headerName: 'Contacts',\n width: 110,\n align: 'right',\n headerAlign: 'right',\n /*\n * The denormalized count every link moves (AGL-2613) — a stored\n * number, so a page of companies costs no read per row. Absent on a\n * company nobody has linked since the counter existed, which reads\n * as zero; the company's own page takes the live aggregate.\n */\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => Number(row.contactsCount ?? 0),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.contactsCount ? 'text.primary' : 'text.secondary'}\n >\n {Number(row.contactsCount ?? 0).toLocaleString()}\n </Typography>\n ),\n },\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 1,\n minWidth: 140,\n // Written by the drawer and the bulk bar (AGL-2621); read here, not\n // filtered — a tag filter would need an `array-contains` index of\n // its own beside the scope predicate.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => (row.tags ?? []).join(', '),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.tags?.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', overflow: 'hidden' }}>\n {row.tags.slice(0, 3).map((tag) => (\n <Chip key={tag} size=\"small\" variant=\"outlined\" label={tag} />\n ))}\n {row.tags.length > 3 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`+${row.tags.length - 3}`}\n </Typography>\n ) : null}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 160,\n sortable: false,\n /*\n * A CHOICE from the roster, not a text box for a uid. The grid's\n * single-select `is` operator is what the panel offers; the handler\n * below maps it onto the declared field's `equals`, which the\n * translator turns into the equality the index serves.\n */\n type: 'singleSelect',\n valueOptions: members.options.map((option) => ({\n value: option.uid,\n label: crmMemberPickerLabel(option),\n })),\n filterable: members.options.length > 0,\n filterOperators: getGridSingleSelectOperators().filter(\n (operator) => operator.value === 'is',\n ),\n valueGetter: (_value, row: CompanyRow) => String(row.ownerUid ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.ownerUid ? 'text.primary' : 'text.secondary'}\n >\n {row.ownerUid\n ? members.ready\n ? members.labelFor(row.ownerUid)\n : '…'\n : '—'}\n </Typography>\n ),\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n row.updatedAt?.seconds ? new Date(row.updatedAt.seconds * 1000) : null,\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {row.updatedAt?.seconds\n ? new Date(row.updatedAt.seconds * 1000).toLocaleDateString()\n : '—'}\n </Typography>\n ),\n },\n // When the earliest open task against the company is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Account fields (AGL-3514), each optional. Type, Industry\n // and Rating filter by the key stored beside the label; the rest read.\n ...COMPANY_PICKLIST_FILTERS.map(\n (entry): GridColDef => ({\n field: entry.column,\n headerName: entry.header,\n flex: 0.8,\n minWidth: 130,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n crmPicklistKey(row[entry.column as 'type' | 'industry' | 'rating']) ?? '',\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(row[entry.column as 'type' | 'industry' | 'rating']),\n }),\n ),\n {\n field: 'accountSource',\n headerName: 'Account source',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.accountSource ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => textCell(row.accountSource),\n },\n {\n field: 'numberOfEmployees',\n headerName: 'Employees',\n width: 120,\n align: 'right',\n headerAlign: 'right',\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n typeof row.numberOfEmployees === 'number' ? row.numberOfEmployees : null,\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(\n typeof row.numberOfEmployees === 'number' ? row.numberOfEmployees.toLocaleString() : '',\n ),\n },\n {\n field: 'annualRevenueCents',\n headerName: 'Annual revenue',\n width: 150,\n align: 'right',\n headerAlign: 'right',\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n typeof row.annualRevenueCents === 'number' ? row.annualRevenueCents : null,\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(\n typeof row.annualRevenueCents === 'number'\n ? formatMoney(row.annualRevenueCents, row.currency)\n : '',\n ),\n },\n // The org's company fields as optional columns (AGL-2661), read off\n // the row's own `custom` map the way the contacts list reads its own.\n ...customFieldColumns(companyFields.active),\n ],\n [members, companyFields.active, nowMs],\n )\n\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313); the query answers them all (AGL-3321).\n */\n const ownerOptions = useMemo(\n () =>\n members.options.map((option) => ({\n value: option.uid,\n label: crmMemberPickerLabel(option),\n })),\n [members.options],\n )\n /*\n * Each picklist's choices (AGL-3514): every value the org keeps, valued\n * by the key the query compares and captioned by the label, inactive\n * ones marked. A stored clause naming a key no longer listed stays a\n * choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo(() => {\n const options: Record<string, Array<{ value: string; label: string }>> = {\n ownerUid: ownerOptions,\n }\n for (const entry of COMPANY_PICKLIST_FILTERS) {\n const list = picklists.lists[entry.picklistId]\n const known = (list?.values ?? []).flatMap((value) => {\n const key = crmPicklistKey(value.label)\n return key ? [{ value: key, label: value.active ? value.label : `${value.label} (inactive)` }] : []\n })\n const stale = viewFilters\n .filter((clause) => clause.field === entry.column)\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n options[entry.column] = [...known, ...stale]\n }\n return options\n }, [ownerOptions, picklists.lists, viewFilters])\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, COMPANY_GRID_FILTER_FIELDS, filterOptions, COMPANY_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: COMPANY_SELECT_FIELDS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: COMPANY_GRID_FILTER_FIELDS,\n headers: COMPANY_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [plan.refused, filterOptions],\n )\n /*\n * The grid's models are the view's (AGL-2617). The filter model shows the\n * view's clause in the panel as a typed one would appear — the owner's\n * stored `equals` back as the single-select `is` the panel offers — so a\n * view opened from its address reads as filtered, not as a mystery.\n */\n const grid = useCrmViewGrid(views, filterColumns, COMPANY_HIDDEN_COLUMNS)\n\n /** Whether anything narrows the list, so an empty one is \"no match\", not \"none yet\". */\n const narrowed = viewFilters.length > 0 || searchWords.some((word) => word.trim())\n\n const newCompanyButton = (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n onClick={() => setCreateOpen(true)}\n >\n {'New company'}\n </Button>\n )\n\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('companies', { anchor: '#the-companies-list' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_COMPANIES_RESOURCE} noun=\"companies\" hostId={hostId} mappingZone=\"companies\" />\n <CrmExportButton resource={CRM_COMPANIES_RESOURCE} hostId={hostId} filter={exportFilter} />\n {newCompanyButton}\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organizations your contacts belong to. Open one to see its ' +\n 'people, its deals and its open tasks, or to link a contact ' +\n 'to it.'}\n </Typography>\n {/* The saved view, and the clauses narrowing it as chips (AGL-3313). */}\n <CrmListToolbar label=\"Company filters\">\n <CrmViewsControl controller={views} allLabel=\"All companies\" />\n <ListFilterChips\n fields={COMPANY_GRID_FILTER_FIELDS}\n headers={COMPANY_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={plan.notices} />\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rowHeight={TABLE_ROW_HEIGHT}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n rows={companies}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n noRowsLabel={narrowed ? 'No companies match these filters' : 'No companies yet'}\n noRowsDescription={\n narrowed\n ? undefined\n : 'A company groups the contacts who work at one business, with its domain, owner and address. Create the first one, or link a contact to a company from their page.'\n }\n noRowsAction={narrowed ? undefined : newCompanyButton}\n onOpen={(id) => openCompany(String(id))}\n // An empty table while the read is in flight reads as \"you have\n // none\" rather than \"these are on their way\".\n loading={!scope || status === 'loading'}\n /*\n * The grid must NOT also filter. The query answers it, so a client\n * pass could only drop rows the query already matched (AGL-3321).\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 // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={companies.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {/*\n Mounted only while it is open, so the list pays for none of the\n drawer's state — and a fresh mount is what seeds an empty form.\n */}\n {createOpen ? (\n <CompanyEditDrawer\n open\n onClose={() => setCreateOpen(false)}\n hostId={hostId}\n org={org}\n members={members}\n onSaved={openCompany}\n />\n ) : null}\n </CardDisplay>\n )\n}\nCompaniesSection.displayName = 'CompaniesSection'\n\nexport default CompaniesSection\n"],"names":["CRM_COLLECTIONS","crmPicklistKey","pluginDocsHelp","crmMemberPickerLabel","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","listFilterColumn","ListQueryNotices","listQueryRefusals","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","useCrmFoldsScope","useCrmListQuery","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","CrmListActions","CrmListToolbar","TABLE_ROW_HEIGHT","Button","Chip","Stack","Typography","getGridSingleSelectOperators","useRouter","useCallback","useEffect","useMemo","useState","COMPANY_LIST_DECLARATION","COMPANY_LIST_FILTER_FIELDS","COMPANY_PICKLIST_FILTERS","COMPANY_PREFIX_SEARCH","useCompanyPicklists","formatMoney","useContactFieldDefinitions","useCrmScope","useOrgMemberOptions","CRM_COMPANIES_RESOURCE","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CrmExportButton","CrmImportButton","customFieldColumns","COMPANY_GRID_FILTER_FIELDS","COMPANY_GRID_FILTER_HEADERS","name","ownerUid","column","Object","fromEntries","map","entry","header","COMPANY_SELECT_FIELDS","COMPANY_HIDDEN_COLUMNS","type","industry","rating","accountSource","numberOfEmployees","annualRevenueCents","textCell","value","text","undefined","String","variant","noWrap","color","CompaniesSection","props","hostId","org","basePath","routes","router","scope","orgId","visibleTo","members","companyFields","picklists","views","section","viewFilters","state","filters","searchWords","setSearchWords","foldsScope","rows","companies","status","hasMore","page","setPage","pageSize","setPageSize","plan","collection","declaration","clauses","search","prefixSearch","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","exportFilter","served","length","searched","label","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","renderCell","sx","justifyContent","height","lineHeight","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","direction","spacing","alignItems","overflow","slice","tag","size","valueOptions","options","option","uid","filterOperators","filter","operator","ready","labelFor","updatedAt","seconds","toLocaleDateString","currency","active","ownerOptions","filterOptions","list","lists","picklistId","known","values","flatMap","key","stale","clause","split","trim","some","filterColumns","gridFilter","onChange","setFilters","selectFields","words","refused","fields","headers","grid","narrowed","word","newCompanyButton","disabled","startIcon","path","onClick","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","resource","noun","mappingZone","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","noRowsAction","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-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 AglynOrgBilling,\n CRM_COLLECTIONS,\n type CrmCompany,\n crmPicklistKey,\n crmPicklistRank,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterColumn } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport {\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n CRM_NEXT_ACTIVITY_FILTER_HEADER,\n nextActivityColumn,\n} from './crm-next-activity-column'\nimport { useCrmFoldsScope, useCrmListQuery } from '../hooks/use-crm-list-query'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport CrmViewsControl from './crm-views-control'\nimport { CrmListActions, CrmListToolbar } from './crm-list-toolbar'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n COMPANY_LIST_DECLARATION,\n COMPANY_LIST_FILTER_FIELDS,\n COMPANY_LIST_SORTS,\n COMPANY_PICKLIST_FILTERS,\n COMPANY_PREFIX_SEARCH,\n} from '../constants/company-filters'\nimport { crmViewQuerySort, useCrmColumnSort } from '../hooks/use-crm-column-sort'\nimport { useCompanyPicklists } from '../hooks/use-company-picklists'\nimport { formatMoney } from '../model/deal-board-model'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { CRM_COMPANIES_RESOURCE } from '../transfer/fields'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CrmExportButton, CrmImportButton } from './crm-transfer-buttons'\nimport { customFieldColumns } from './contact-custom-columns'\n\nexport interface CompaniesSectionProps {\n /** The site the list is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n /** The CRM hub URL, which every company route hangs beneath. */\n basePath: string\n}\n\ntype CompanyRow = Partial<CrmCompany> & { $id: string; updatedAt?: any }\n\n/** What the companies grid's panel offers — every field the query asks. */\nconst COMPANY_GRID_FILTER_FIELDS = COMPANY_LIST_FILTER_FIELDS\nconst COMPANY_GRID_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Company',\n ownerUid: 'Owner',\n [CRM_NEXT_ACTIVITY_FILTER_FIELD.column]: CRM_NEXT_ACTIVITY_FILTER_HEADER,\n ...Object.fromEntries(COMPANY_PICKLIST_FILTERS.map((entry) => [entry.column, entry.header])),\n}\n\n/** The grid's select columns — the owner and each picklist — as the filter hook names them. */\nconst COMPANY_SELECT_FIELDS = ['ownerUid', ...COMPANY_PICKLIST_FILTERS.map((entry) => entry.column)]\n\n/**\n * Salesforce's Account columns (AGL-3514), optional: a reader turns one on\n * from the column menu, and a view that shows it stores it by name.\n */\nconst COMPANY_HIDDEN_COLUMNS: Readonly<Record<string, boolean>> = {\n type: false,\n industry: false,\n rating: false,\n accountSource: false,\n numberOfEmployees: false,\n annualRevenueCents: false,\n}\n\n/** A text cell that reads as a dash when empty. */\nfunction textCell(value: unknown) {\n const text = value === null || value === undefined ? '' : String(value)\n return text ? (\n <Typography variant=\"body2\" noWrap>\n {text}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n )\n}\n\n/**\n * `/crm/companies` — the organizations behind the people (AGL-2597).\n *\n * A section of its own rather than a column on the contact list, because a\n * company is known by several contacts and carries records of its own: a\n * domain the email suggestion keys on, an address, an owner, and the deals\n * and tasks filed against it. The row opens `/crm/companies/{id}`, where all\n * of that lives; this list is the cheaper surface and reads nothing a row\n * does not show.\n *\n * ## What the listener carries\n *\n * Paged and ordered by the server — `updatedAt desc`, so a company somebody\n * just touched is on page one — and SCOPED by the same `visibleTo\n * array-contains-any` predicate the contact list runs. That predicate is what\n * the rules evaluate: a filtered query is provable per document, and an\n * unfiltered one is refused rather than quietly returning the whole org's\n * accounts to a site that may see one client's.\n *\n * ## What a filter may be\n *\n * Every clause and the search word are on the one query (AGL-3321): the\n * search reads the name's and the domain's word prefixes, the name is also\n * an exact match and a prefix, the owner a choice from the roster, and \"No\n * next activity\" a `null` — each reaching the whole collection, which is\n * the property a filter must have: a company on page four is found, not\n * reported missing. `COMPANY_LIST_DECLARATION` states what the query asks.\n *\n * ## Creating is a drawer\n *\n * \"New company\" opens the same eight-field drawer the company's page edits\n * with, and the new record's page opens when it is saved. A form above a\n * list has nowhere to grow, and this one has an address in it.\n *\n * ## Selection, the bar and the file (AGL-2621)\n *\n * The rows are selectable, and a selection raises `CompaniesBulkBar` over\n * the table. Import and Export open the console's import wizard and\n * export dialog on `crm.companies` (AGL-3527): the export over the\n * selection, the list's own query, or every company.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const { scope, orgId, visibleTo } = useCrmScope({ hostId, org })\n /*\n * The team, read once for this surface: the Owner column names a person\n * from a uid, and the create drawer picks from the same roster. One fetch\n * serves both, which is why the drawer takes it as a prop.\n */\n const members = useOrgMemberOptions(orgId)\n // The org's company fields, for the optional columns below (AGL-2661).\n const companyFields = useContactFieldDefinitions(orgId, 'company')\n // The lists behind Type, Industry and Rating, for their filters (AGL-3514).\n const picklists = useCompanyPicklists(orgId)\n\n /*\n * The clauses are the saved VIEW'S (AGL-2617), beside its columns and\n * sort; the grid's panel and the chips edit them, and the query answers\n * every one and the search word together (AGL-3321).\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n const viewFilters = views.state.filters\n const [searchWords, setSearchWords] = useState<string[]>([])\n const foldsScope = useCrmFoldsScope(orgId, visibleTo)\n // The header order the view asks (AGL-3680), read before the query it orders.\n const companySort = useMemo(\n () => crmViewQuerySort(COMPANY_LIST_SORTS, views.state.sort, COMPANY_LIST_SORTS[0]),\n [views.state.sort],\n )\n const {\n rows: companies,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useCrmListQuery<CompanyRow>({\n scope,\n collection: CRM_COLLECTIONS.companies,\n visibleTo,\n foldsScope,\n declaration: COMPANY_LIST_DECLARATION,\n clauses: viewFilters,\n search: searchWords,\n prefixSearch: COMPANY_PREFIX_SEARCH,\n sort: companySort.sort,\n })\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n // A filter, a search or a page is a different set of rows.\n useEffect(() => setSelectedIds([]), [plan, page])\n // The list's filter, when one narrows it, for the export to read the same\n // records the list does (AGL-3527).\n const exportFilter = useMemo(\n () => (plan.served.length || plan.searched ? { label: 'what the list shows', plan } : null),\n [plan],\n )\n const openCompany = useCallback(\n (id: string) => router.push(routes.company(id)),\n [router, routes],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Company',\n flex: 1.6,\n minWidth: 240,\n ...listFilterColumn(COMPANY_LIST_FILTER_FIELDS, 'name'),\n valueGetter: (_value, row: CompanyRow) => String(row.name ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Stack\n sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}\n >\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.name}\n </Typography>\n {row.industry ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ lineHeight: 1.25 }}\n noWrap\n >\n {row.industry}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'domain',\n headerName: 'Domain',\n flex: 1,\n minWidth: 160,\n // Stored normalized, so it could be filtered exactly — but a domain\n // lookup is what the company suggestion on a contact does, and a\n // filter here would need an index of its own for one more way to\n // find a row the name filter already finds.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.domain ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.domain ? (\n <Typography variant=\"body2\">{row.domain}</Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'contactsCount',\n headerName: 'Contacts',\n width: 110,\n align: 'right',\n headerAlign: 'right',\n /*\n * The denormalized count every link moves (AGL-2613) — a stored\n * number, so a page of companies costs no read per row. Absent on a\n * company nobody has linked since the counter existed, which reads\n * as zero; the company's own page takes the live aggregate.\n */\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => Number(row.contactsCount ?? 0),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.contactsCount ? 'text.primary' : 'text.secondary'}\n >\n {Number(row.contactsCount ?? 0).toLocaleString()}\n </Typography>\n ),\n },\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 1,\n minWidth: 140,\n // Written by the drawer and the bulk bar (AGL-2621); read here, not\n // filtered — a tag filter would need an `array-contains` index of\n // its own beside the scope predicate.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => (row.tags ?? []).join(', '),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.tags?.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', overflow: 'hidden' }}>\n {row.tags.slice(0, 3).map((tag) => (\n <Chip key={tag} size=\"small\" variant=\"outlined\" label={tag} />\n ))}\n {row.tags.length > 3 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`+${row.tags.length - 3}`}\n </Typography>\n ) : null}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 160,\n sortable: false,\n /*\n * A CHOICE from the roster, not a text box for a uid. The grid's\n * single-select `is` operator is what the panel offers; the handler\n * below maps it onto the declared field's `equals`, which the\n * translator turns into the equality the index serves.\n */\n type: 'singleSelect',\n valueOptions: members.options.map((option) => ({\n value: option.uid,\n label: crmMemberPickerLabel(option),\n })),\n filterable: members.options.length > 0,\n filterOperators: getGridSingleSelectOperators().filter(\n (operator) => operator.value === 'is',\n ),\n valueGetter: (_value, row: CompanyRow) => String(row.ownerUid ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.ownerUid ? 'text.primary' : 'text.secondary'}\n >\n {row.ownerUid\n ? members.ready\n ? members.labelFor(row.ownerUid)\n : '…'\n : '—'}\n </Typography>\n ),\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n row.updatedAt?.seconds ? new Date(row.updatedAt.seconds * 1000) : null,\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {row.updatedAt?.seconds\n ? new Date(row.updatedAt.seconds * 1000).toLocaleDateString()\n : '—'}\n </Typography>\n ),\n },\n // When the earliest open task against the company is due (AGL-2661).\n nextActivityColumn(nowMs),\n // Salesforce's Account fields (AGL-3514), each optional. Type, Industry\n // and Rating filter by the key stored beside the label; the rest read.\n ...COMPANY_PICKLIST_FILTERS.map(\n (entry): GridColDef => ({\n field: entry.column,\n headerName: entry.header,\n flex: 0.8,\n minWidth: 130,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n crmPicklistKey(row[entry.column as 'type' | 'industry' | 'rating']) ?? '',\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(row[entry.column as 'type' | 'industry' | 'rating']),\n }),\n ),\n {\n field: 'accountSource',\n headerName: 'Account source',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.accountSource ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => textCell(row.accountSource),\n },\n {\n field: 'numberOfEmployees',\n headerName: 'Employees',\n width: 120,\n align: 'right',\n headerAlign: 'right',\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n typeof row.numberOfEmployees === 'number' ? row.numberOfEmployees : null,\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(\n typeof row.numberOfEmployees === 'number' ? row.numberOfEmployees.toLocaleString() : '',\n ),\n },\n {\n field: 'annualRevenueCents',\n headerName: 'Annual revenue',\n width: 150,\n align: 'right',\n headerAlign: 'right',\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n typeof row.annualRevenueCents === 'number' ? row.annualRevenueCents : null,\n renderCell: ({ row }: { row: CompanyRow }) =>\n textCell(\n typeof row.annualRevenueCents === 'number'\n ? formatMoney(row.annualRevenueCents, row.currency)\n : '',\n ),\n },\n // The org's company fields as optional columns (AGL-2661), read off\n // the row's own `custom` map the way the contacts list reads its own.\n ...customFieldColumns(companyFields.active),\n ],\n [members, companyFields.active, nowMs],\n )\n\n /*\n * The grid's own Filters panel and quick search edit the view's clauses\n * (AGL-3313); the query answers them all (AGL-3321).\n */\n const ownerOptions = useMemo(\n () =>\n members.options.map((option) => ({\n value: option.uid,\n label: crmMemberPickerLabel(option),\n })),\n [members.options],\n )\n /*\n * Each picklist's choices (AGL-3514): every value the org keeps, valued\n * by the key the query compares and captioned by the label, inactive\n * ones marked. A stored clause naming a key no longer listed stays a\n * choice, so the panel can show it and clear it.\n */\n const filterOptions = useMemo(() => {\n const options: Record<string, Array<{ value: string; label: string }>> = {\n ownerUid: ownerOptions,\n }\n for (const entry of COMPANY_PICKLIST_FILTERS) {\n const list = picklists.lists[entry.picklistId]\n const known = (list?.values ?? []).flatMap((value) => {\n const key = crmPicklistKey(value.label)\n return key ? [{ value: key, label: value.active ? value.label : `${value.label} (inactive)` }] : []\n })\n const stale = viewFilters\n .filter((clause) => clause.field === entry.column)\n .flatMap((clause) => clause.value.split(','))\n .map((value) => value.trim())\n .filter((value) => value && !known.some((option) => option.value === value))\n .map((value) => ({ value, label: value }))\n options[entry.column] = [...known, ...stale]\n }\n return options\n }, [ownerOptions, picklists.lists, viewFilters])\n const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, COMPANY_GRID_FILTER_FIELDS, filterOptions, COMPANY_GRID_FILTER_HEADERS),\n [columns, filterOptions],\n )\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: COMPANY_SELECT_FIELDS,\n search: { words: searchWords, onChange: setSearchWords },\n })\n // What the query could not hold, named by the clause the reader set.\n const refused = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: COMPANY_GRID_FILTER_FIELDS,\n headers: COMPANY_GRID_FILTER_HEADERS,\n options: filterOptions,\n }),\n [plan.refused, filterOptions],\n )\n /*\n * The grid's models are the view's (AGL-2617). The filter model shows the\n * view's clause in the panel as a typed one would appear — the owner's\n * stored `equals` back as the single-select `is` the panel offers — so a\n * view opened from its address reads as filtered, not as a mystery.\n */\n const grid = useCrmViewGrid(views, filterColumns, COMPANY_HIDDEN_COLUMNS)\n /*\n * EVERY HEADER SORTS (AGL-3680). The grid used to sort the view's order\n * over the one page on screen while the footer paged the query, which read\n * as the whole list's order. Company, Updated and Next activity sort the\n * QUERY now (`COMPANY_LIST_SORTS`); the rest sort the page and say so —\n * Type, Industry and Rating in the order the org keeps their values.\n */\n const pageSorts = useMemo(\n () =>\n Object.fromEntries(\n COMPANY_PICKLIST_FILTERS.map((entry) => [\n entry.column,\n (row: CompanyRow) => {\n const label = row[entry.column as 'type' | 'industry' | 'rating']\n const list = picklists.lists[entry.picklistId]\n return list && label ? crmPicklistRank(list, label) : null\n },\n ]),\n ),\n [picklists.lists],\n )\n const columnSort = useCrmColumnSort<CompanyRow>({\n views,\n sorts: COMPANY_LIST_SORTS,\n defaultSort: COMPANY_LIST_SORTS[0],\n asked: companySort,\n orderBy: plan.orderBy,\n rows: companies,\n columns: grid.columns,\n pageSorts,\n })\n\n /** Whether anything narrows the list, so an empty one is \"no match\", not \"none yet\". */\n const narrowed = viewFilters.length > 0 || searchWords.some((word) => word.trim())\n\n const newCompanyButton = (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n onClick={() => setCreateOpen(true)}\n >\n {'New company'}\n </Button>\n )\n\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('companies', { anchor: '#the-companies-list' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n // The record actions, top right and never clipped (AGL-3311).\n action: (\n <CrmListActions>\n <CrmImportButton resource={CRM_COMPANIES_RESOURCE} noun=\"companies\" hostId={hostId} mappingZone=\"companies\" />\n <CrmExportButton resource={CRM_COMPANIES_RESOURCE} hostId={hostId} filter={exportFilter} />\n {newCompanyButton}\n </CrmListActions>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The organizations your contacts belong to. Open one to see its ' +\n 'people, its deals and its open tasks, or to link a contact ' +\n 'to it.'}\n </Typography>\n {/* The saved view, and the clauses narrowing it as chips (AGL-3313). */}\n <CrmListToolbar label=\"Company filters\">\n <CrmViewsControl controller={views} allLabel=\"All companies\" />\n <ListFilterChips\n fields={COMPANY_GRID_FILTER_FIELDS}\n headers={COMPANY_GRID_FILTER_HEADERS}\n clauses={viewFilters}\n onChange={views.setFilters}\n options={filterOptions}\n marksServed={false}\n />\n </CrmListToolbar>\n <ListQueryNotices refused={refused} notices={[...plan.notices, ...columnSort.notices]} />\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rowHeight={TABLE_ROW_HEIGHT}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n rows={columnSort.rows}\n columnSort={columnSort}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n noRowsLabel={narrowed ? 'No companies match these filters' : 'No companies yet'}\n noRowsDescription={\n narrowed\n ? undefined\n : 'A company groups the contacts who work at one business, with its domain, owner and address. Create the first one, or link a contact to a company from their page.'\n }\n noRowsAction={narrowed ? undefined : newCompanyButton}\n onOpen={(id) => openCompany(String(id))}\n // An empty table while the read is in flight reads as \"you have\n // none\" rather than \"these are on their way\".\n loading={!scope || status === 'loading'}\n /*\n * The grid must NOT also filter. The query answers it, so a client\n * pass could only drop rows the query already matched (AGL-3321).\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Columns are the view's, controlled (AGL-2617); so is the\n // header order, which `columnSort` keeps in it (AGL-3680).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={companies.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {/*\n Mounted only while it is open, so the list pays for none of the\n drawer's state — and a fresh mount is what seeds an empty form.\n */}\n {createOpen ? (\n <CompanyEditDrawer\n open\n onClose={() => setCreateOpen(false)}\n hostId={hostId}\n org={org}\n members={members}\n onSaved={openCompany}\n />\n ) : null}\n </CardDisplay>\n )\n}\nCompaniesSection.displayName = 'CompaniesSection'\n\nexport default CompaniesSection\n"],"names":["CRM_COLLECTIONS","crmPicklistKey","crmPicklistRank","pluginDocsHelp","crmMemberPickerLabel","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","listFilterColumn","ListQueryNotices","listQueryRefusals","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","useCrmFoldsScope","useCrmListQuery","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","CrmListActions","CrmListToolbar","TABLE_ROW_HEIGHT","Button","Chip","Stack","Typography","getGridSingleSelectOperators","useRouter","useCallback","useEffect","useMemo","useState","COMPANY_LIST_DECLARATION","COMPANY_LIST_FILTER_FIELDS","COMPANY_LIST_SORTS","COMPANY_PICKLIST_FILTERS","COMPANY_PREFIX_SEARCH","crmViewQuerySort","useCrmColumnSort","useCompanyPicklists","formatMoney","useContactFieldDefinitions","useCrmScope","useOrgMemberOptions","CRM_COMPANIES_RESOURCE","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CrmExportButton","CrmImportButton","customFieldColumns","COMPANY_GRID_FILTER_FIELDS","COMPANY_GRID_FILTER_HEADERS","name","ownerUid","column","Object","fromEntries","map","entry","header","COMPANY_SELECT_FIELDS","COMPANY_HIDDEN_COLUMNS","type","industry","rating","accountSource","numberOfEmployees","annualRevenueCents","textCell","value","text","undefined","String","variant","noWrap","color","CompaniesSection","props","hostId","org","basePath","routes","router","scope","orgId","visibleTo","members","companyFields","picklists","views","section","viewFilters","state","filters","searchWords","setSearchWords","foldsScope","companySort","sort","rows","companies","status","hasMore","page","setPage","pageSize","setPageSize","plan","collection","declaration","clauses","search","prefixSearch","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","exportFilter","served","length","searched","label","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","renderCell","sx","justifyContent","height","lineHeight","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","direction","spacing","alignItems","overflow","slice","tag","size","valueOptions","options","option","uid","filterOperators","filter","operator","ready","labelFor","updatedAt","seconds","toLocaleDateString","currency","active","ownerOptions","filterOptions","list","lists","picklistId","known","values","flatMap","key","stale","clause","split","trim","some","filterColumns","gridFilter","onChange","setFilters","selectFields","words","refused","fields","headers","grid","pageSorts","columnSort","sorts","defaultSort","asked","orderBy","narrowed","word","newCompanyButton","disabled","startIcon","path","onClick","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","resource","noun","mappingZone","controller","allLabel","marksServed","notices","selected","onSelectedChange","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","noRowsAction","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
|
|
@@ -38,13 +38,15 @@ import { companyDraftFields, companyDraftFrom, EMPTY_COMPANY_DRAFT } from "../mo
|
|
|
38
38
|
import { crmCustomDraftDocument, crmCustomDraftMissingRequired, crmCustomDraftValue, crmCustomDraftWrites } from "../model/crm-custom-draft.js";
|
|
39
39
|
import { CrmCustomFieldControl } from "./crm-custom-field-control.js";
|
|
40
40
|
import { CrmSitePicker } from "./crm-site-picker.js";
|
|
41
|
-
import { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
|
|
41
|
+
import { CRM_CLIENT_SEARCH_FIELDS, CRM_CLIENT_SORT_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
|
|
42
42
|
/**
|
|
43
43
|
* The list fields a client edit of a company rewrites: the search tokens,
|
|
44
44
|
* and the key beside each picklist field the Companies list filters by
|
|
45
45
|
* (AGL-3514).
|
|
46
46
|
*/ const COMPANY_CLIENT_LIST_FIELDS = [
|
|
47
47
|
...CRM_CLIENT_SEARCH_FIELDS,
|
|
48
|
+
// Any header sort key the edit's inputs move (AGL-3680).
|
|
49
|
+
...CRM_CLIENT_SORT_FIELDS,
|
|
48
50
|
...CRM_COMPANY_PICKLIST_FIELDS.flatMap((entry)=>'keyField' in entry ? [
|
|
49
51
|
entry.keyField
|
|
50
52
|
] : [])
|