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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/companies-section.d.ts +6 -6
  3. package/src/lib/components/companies-section.js +54 -84
  4. package/src/lib/components/companies-section.js.map +1 -1
  5. package/src/lib/components/company-edit-drawer.js +17 -4
  6. package/src/lib/components/company-edit-drawer.js.map +1 -1
  7. package/src/lib/components/company-picker.js +6 -2
  8. package/src/lib/components/company-picker.js.map +1 -1
  9. package/src/lib/components/contact-detail-page.js +14 -2
  10. package/src/lib/components/contact-detail-page.js.map +1 -1
  11. package/src/lib/components/contact-list-columns.d.ts +3 -5
  12. package/src/lib/components/contact-list-columns.js +6 -8
  13. package/src/lib/components/contact-list-columns.js.map +1 -1
  14. package/src/lib/components/contacts-bulk-bar.js +10 -1
  15. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  16. package/src/lib/components/contacts-section.js +115 -163
  17. package/src/lib/components/contacts-section.js.map +1 -1
  18. package/src/lib/components/crm-next-activity-column.d.ts +4 -2
  19. package/src/lib/components/crm-next-activity-column.js +6 -3
  20. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  21. package/src/lib/components/deal-edit-drawer.js +8 -4
  22. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  23. package/src/lib/components/deals-section.d.ts +2 -2
  24. package/src/lib/components/deals-section.js +59 -84
  25. package/src/lib/components/deals-section.js.map +1 -1
  26. package/src/lib/components/lead-campaigns-card.js +11 -3
  27. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  28. package/src/lib/components/lead-properties-card.js +9 -1
  29. package/src/lib/components/lead-properties-card.js.map +1 -1
  30. package/src/lib/components/leads-bulk-bar.js +16 -3
  31. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  32. package/src/lib/components/leads-section.d.ts +2 -1
  33. package/src/lib/components/leads-section.js +110 -148
  34. package/src/lib/components/leads-section.js.map +1 -1
  35. package/src/lib/components/tasks-section.js +128 -121
  36. package/src/lib/components/tasks-section.js.map +1 -1
  37. package/src/lib/constants/company-filters.d.ts +13 -0
  38. package/src/lib/constants/company-filters.js +47 -17
  39. package/src/lib/constants/company-filters.js.map +1 -1
  40. package/src/lib/constants/contact-filters.d.ts +61 -10
  41. package/src/lib/constants/contact-filters.js +373 -141
  42. package/src/lib/constants/contact-filters.js.map +1 -1
  43. package/src/lib/constants/deal-filters.d.ts +44 -0
  44. package/src/lib/constants/deal-filters.js +78 -0
  45. package/src/lib/constants/deal-filters.js.map +1 -0
  46. package/src/lib/hooks/use-crm-list-query.d.ts +68 -0
  47. package/src/lib/hooks/use-crm-list-query.js +182 -0
  48. package/src/lib/hooks/use-crm-list-query.js.map +1 -0
  49. package/src/lib/hooks/use-crm-tasks.d.ts +23 -6
  50. package/src/lib/hooks/use-crm-tasks.js +44 -42
  51. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  52. package/src/lib/hooks/use-deals.d.ts +2 -34
  53. package/src/lib/hooks/use-deals.js +3 -62
  54. package/src/lib/hooks/use-deals.js.map +1 -1
  55. package/src/lib/model/crm-list-query.d.ts +91 -0
  56. package/src/lib/model/crm-list-query.js +182 -0
  57. package/src/lib/model/crm-list-query.js.map +1 -0
  58. package/src/lib/model/lead-filters.d.ts +49 -43
  59. package/src/lib/model/lead-filters.js +194 -88
  60. package/src/lib/model/lead-filters.js.map +1 -1
  61. package/src/lib/model/list-fields-api.d.ts +26 -0
  62. package/src/lib/model/list-fields-api.js +47 -0
  63. package/src/lib/model/list-fields-api.js.map +1 -0
  64. package/src/lib/model/list-fields.d.ts +47 -0
  65. package/src/lib/model/list-fields.js +39 -0
  66. package/src/lib/model/list-fields.js.map +1 -0
  67. package/src/lib/model/task-views.d.ts +15 -10
  68. package/src/lib/model/task-views.js +68 -9
  69. package/src/lib/model/task-views.js.map +1 -1
  70. package/src/lib/server/companies-import.js +3 -3
  71. package/src/lib/server/companies-import.js.map +1 -1
  72. package/src/lib/server/company-delete.js +3 -1
  73. package/src/lib/server/company-delete.js.map +1 -1
  74. package/src/lib/server/consent-group-participant.js +4 -0
  75. package/src/lib/server/consent-group-participant.js.map +1 -1
  76. package/src/lib/server/contact-update.js +14 -8
  77. package/src/lib/server/contact-update.js.map +1 -1
  78. package/src/lib/server/contacts-import.js +4 -2
  79. package/src/lib/server/contacts-import.js.map +1 -1
  80. package/src/lib/server/deals-import.js +4 -2
  81. package/src/lib/server/deals-import.js.map +1 -1
  82. package/src/lib/server/erase-person.js +2 -1
  83. package/src/lib/server/erase-person.js.map +1 -1
  84. package/src/lib/server/lead-create.js +4 -1
  85. package/src/lib/server/lead-create.js.map +1 -1
  86. package/src/lib/server/lead-source-values.js +9 -5
  87. package/src/lib/server/lead-source-values.js.map +1 -1
  88. package/src/lib/server/leads-import.js +4 -1
  89. package/src/lib/server/leads-import.js.map +1 -1
  90. package/src/lib/server/list-fields-routes.d.ts +29 -0
  91. package/src/lib/server/list-fields-routes.js +85 -0
  92. package/src/lib/server/list-fields-routes.js.map +1 -0
  93. package/src/lib/server/record-email-state.js +5 -2
  94. package/src/lib/server/record-email-state.js.map +1 -1
  95. package/src/lib/server/record-timeline.js +3 -2
  96. package/src/lib/server/record-timeline.js.map +1 -1
  97. package/src/lib/server/task-routes.js +6 -3
  98. package/src/lib/server/task-routes.js.map +1 -1
  99. package/src/lib/server/tasks-import.js +5 -2
  100. package/src/lib/server/tasks-import.js.map +1 -1
  101. package/src/lib/server.js +10 -1
  102. package/src/lib/server.js.map +1 -1
  103. package/src/lib/hooks/use-org-leads.d.ts +0 -50
  104. package/src/lib/hooks/use-org-leads.js +0 -104
  105. package/src/lib/hooks/use-org-leads.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-crm",
3
- "version": "1.0.0-beta.198",
3
+ "version": "1.0.0-beta.200",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.198",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.198",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.198",
31
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.198",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.198",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.198",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.198",
35
- "@aglyn/shared-util-email": "1.0.0-beta.198",
36
- "@aglyn/shared-util-http": "1.0.0-beta.198",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.198",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.198",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.198",
28
+ "@aglyn/aglyn": "1.0.0-beta.200",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.200",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.200",
31
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.200",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.200",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.200",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.200",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.200",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.200",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.200",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.200",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.200",
40
40
  "@dnd-kit/core": "^6.3.1",
41
41
  "@dnd-kit/utilities": "^3.2.2",
42
42
  "@swc/helpers": "0.5.23",
@@ -27,12 +27,12 @@ export interface CompaniesSectionProps {
27
27
  *
28
28
  * ## What a filter may be
29
29
  *
30
- * The scope predicate is an array clause, and Firestore carries one per
31
- * query, so the word-prefix search the contact list offers cannot run here.
32
- * The name is a prefix range and an exact match instead, and the owner is a
33
- * choice from the roster — each reaching the whole collection, which is the
34
- * property a search must have: a company on page four is found, not reported
35
- * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.
30
+ * Every clause and the search word are on the one query (AGL-3321): the
31
+ * search reads the name's and the domain's word prefixes, the name is also
32
+ * an exact match and a prefix, the owner a choice from the roster, and "No
33
+ * next activity" a `null` — each reaching the whole collection, which is
34
+ * the property a filter must have: a company on page four is found, not
35
+ * reported missing. `COMPANY_LIST_DECLARATION` states what the query asks.
36
36
  *
37
37
  * ## Creating is a drawer
38
38
  *
@@ -16,31 +16,31 @@
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, filterByNextActivity, isNoNextActivityClause, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
19
+ import { CRM_COLLECTIONS, 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";
23
23
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
24
- import { listFilterColumn } from "@aglyn/shared-ui-jsx/const/list-filter";
24
+ import { listFilterColumn } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
25
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
25
26
  import { useCrmSavedView } from "../hooks/use-crm-saved-view.js";
26
27
  import { useCrmViewGrid } from "../hooks/use-crm-view-grid.js";
27
28
  import { CRM_LIST_SLOTS, CrmColumnOrderProvider } from "./crm-column-menu.js";
28
29
  import { CRM_NEXT_ACTIVITY_FILTER_FIELD, CRM_NEXT_ACTIVITY_FILTER_HEADER, nextActivityColumn } from "./crm-next-activity-column.js";
30
+ import { useCrmFoldsScope, useCrmListQuery } from "../hooks/use-crm-list-query.js";
29
31
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
30
32
  import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
31
33
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
32
34
  import CrmViewsControl from "./crm-views-control.js";
33
35
  import { CrmListActions, CrmListToolbar } from "./crm-list-toolbar.js";
34
36
  import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
35
- import { listFilterConstraints, usePagedCollection, useFirestore } from "@aglyn/tenant-feature-instance";
36
37
  import { Button, Chip, Stack, Typography } from "@mui/material";
37
38
  import { getGridSingleSelectOperators } from "@mui/x-data-grid";
38
- import { collection, limit, orderBy, query } from "firebase/firestore";
39
39
  import { useRouter } from "next/navigation";
40
- import { useCallback, useMemo, useState } from "react";
41
- import { COMPANY_LIST_FILTER_FIELDS } from "../constants/company-filters.js";
40
+ import { useCallback, useEffect, useMemo, useState } from "react";
41
+ import { COMPANY_LIST_DECLARATION, COMPANY_LIST_FILTER_FIELDS, COMPANY_PREFIX_SEARCH } from "../constants/company-filters.js";
42
42
  import { useContactFieldDefinitions } from "../hooks/use-contact-field-definitions.js";
43
- import { crmVisibleToClause, useCrmScope } from "../hooks/use-crm-scope.js";
43
+ import { useCrmScope } from "../hooks/use-crm-scope.js";
44
44
  import { useOrgMemberOptions } from "../hooks/use-org-member-options.js";
45
45
  import { companiesCsv } from "../model/companies-csv.js";
46
46
  import { downloadTextFile } from "../model/contacts-csv.js";
@@ -49,10 +49,7 @@ import CompaniesBulkBar from "./companies-bulk-bar.js";
49
49
  import CompanyEditDrawer from "./company-edit-drawer.js";
50
50
  import { CompanyImportButton } from "./company-import-drawer.js";
51
51
  import { customFieldColumns } from "./contact-custom-columns.js";
52
- /** What the companies grid's panel offers: the served fields, and "No next activity". */ const COMPANY_GRID_FILTER_FIELDS = [
53
- ...COMPANY_LIST_FILTER_FIELDS,
54
- CRM_NEXT_ACTIVITY_FILTER_FIELD
55
- ];
52
+ /** What the companies grid's panel offers — every field the query asks. */ const COMPANY_GRID_FILTER_FIELDS = COMPANY_LIST_FILTER_FIELDS;
56
53
  const COMPANY_GRID_FILTER_HEADERS = {
57
54
  name: 'Company',
58
55
  ownerUid: 'Owner',
@@ -79,12 +76,12 @@ const COMPANY_GRID_FILTER_HEADERS = {
79
76
  *
80
77
  * ## What a filter may be
81
78
  *
82
- * The scope predicate is an array clause, and Firestore carries one per
83
- * query, so the word-prefix search the contact list offers cannot run here.
84
- * The name is a prefix range and an exact match instead, and the owner is a
85
- * choice from the roster — each reaching the whole collection, which is the
86
- * property a search must have: a company on page four is found, not reported
87
- * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.
79
+ * Every clause and the search word are on the one query (AGL-3321): the
80
+ * search reads the name's and the domain's word prefixes, the name is also
81
+ * an exact match and a prefix, the owner a choice from the roster, and "No
82
+ * next activity" a `null` — each reaching the whole collection, which is
83
+ * the property a filter must have: a company on page four is found, not
84
+ * reported missing. `COMPANY_LIST_DECLARATION` states what the query asks.
88
85
  *
89
86
  * ## Creating is a drawer
90
87
  *
@@ -99,11 +96,9 @@ const COMPANY_GRID_FILTER_HEADERS = {
99
96
  * the same file the bar writes over the selection, headed as the import
100
97
  * reads it — and Import CSV opens the companies drawer beside it.
101
98
  */ export function CompaniesSection(props) {
102
- var _viewFilters_find, _ref;
103
99
  const { hostId, org, basePath } = props;
104
100
  const routes = crmRoutes(basePath);
105
101
  const router = useRouter();
106
- const firestore = useFirestore();
107
102
  const { scope, orgId, visibleTo } = useCrmScope({
108
103
  hostId,
109
104
  org
@@ -116,54 +111,36 @@ const COMPANY_GRID_FILTER_HEADERS = {
116
111
  // The org's company fields, for the optional columns below (AGL-2661).
117
112
  const companyFields = useContactFieldDefinitions(orgId, 'company');
118
113
  /*
119
- * The column filter is the saved VIEW'S first clause (AGL-2617): this
120
- * grid's panel holds one, and a view of companies holds that one beside
121
- * the columns and the sort. Declared BEFORE the listener that reads it,
122
- * as the filter always was — the query is rebuilt from it.
114
+ * The clauses are the saved VIEW'S (AGL-2617), beside its columns and
115
+ * sort; the grid's panel and the chips edit them, and the query answers
116
+ * every one and the search word together (AGL-3321).
123
117
  */ const views = useCrmSavedView({
124
118
  section: 'companies',
125
119
  hostId,
126
120
  org,
127
121
  basePath
128
122
  });
129
- /*
130
- * ...beside the "No next activity" clause (AGL-2661), which is not a
131
- * query the listener can run — absence has no index — and narrows the
132
- * loaded page below instead. The grid's one clause is the first that is
133
- * not it, and setting the grid's clause keeps it.
134
- */ const viewFilters = views.state.filters;
135
- const filter = (_viewFilters_find = viewFilters.find((clause)=>!isNoNextActivityClause(clause))) != null ? _viewFilters_find : null;
136
- const setFilter = useCallback((request)=>views.setFilters([
137
- ...request ? [
138
- request
139
- ] : [],
140
- ...viewFilters.filter(isNoNextActivityClause)
141
- ]), [
142
- views.setFilters,
143
- viewFilters
144
- ]);
145
- const { rows: loaded, status, hasMore, page, setPage, pageSize, setPageSize } = usePagedCollection((pageLimit)=>{
146
- if (!scope) return null;
147
- const constraints = listFilterConstraints(COMPANY_LIST_FILTER_FIELDS, filter);
148
- return query(collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies), // Absent at the organization level, where the tokens are `null` (AGL-2630).
149
- ...crmVisibleToClause(visibleTo), ...constraints != null ? constraints : [
150
- orderBy('updatedAt', 'desc')
151
- ], limit(pageLimit));
152
- }, [
153
- firestore,
123
+ const viewFilters = views.state.filters;
124
+ const [searchWords, setSearchWords] = useState([]);
125
+ const foldsScope = useCrmFoldsScope(orgId, visibleTo);
126
+ const { rows: companies, status, hasMore, page, setPage, pageSize, setPageSize, plan } = useCrmListQuery({
154
127
  scope,
128
+ collection: CRM_COLLECTIONS.companies,
155
129
  visibleTo,
156
- filter
157
- ], {
158
- idField: '$id'
130
+ foldsScope,
131
+ declaration: COMPANY_LIST_DECLARATION,
132
+ clauses: viewFilters,
133
+ search: searchWords,
134
+ prefixSearch: COMPANY_PREFIX_SEARCH
159
135
  });
160
- const companies = useMemo(()=>filterByNextActivity(loaded, viewFilters), [
161
- loaded,
162
- viewFilters
163
- ]);
164
136
  const nowMs = useMemo(()=>Date.now(), []);
165
137
  const [createOpen, setCreateOpen] = useState(false);
166
138
  const [selectedIds, setSelectedIds] = useState([]);
139
+ // A filter, a search or a page is a different set of rows.
140
+ useEffect(()=>setSelectedIds([]), [
141
+ plan,
142
+ page
143
+ ]);
167
144
  /*
168
145
  * The owner is written by ADDRESS, because the companies import resolves
169
146
  * an owner by email; the same options reach the bar, so the page's file
@@ -372,11 +349,7 @@ const COMPANY_GRID_FILTER_HEADERS = {
372
349
  ]);
373
350
  /*
374
351
  * The grid's own Filters panel and quick search edit the view's clauses
375
- * (AGL-3313). The query serves ONE clause — a paged list has no window to
376
- * narrow beyond the page — so the panel's clause replaces the last, and
377
- * "No next activity", which narrows the loaded page, stands beside it.
378
- * The search box is the name's prefix: the one text search the indexes
379
- * serve here, so typing in it is the same clause as "Company starts with".
352
+ * (AGL-3313); the query answers them all (AGL-3321).
380
353
  */ const ownerOptions = useMemo(()=>members.options.map((option)=>({
381
354
  value: option.uid,
382
355
  label: crmMemberPickerLabel(option)
@@ -389,42 +362,35 @@ const COMPANY_GRID_FILTER_HEADERS = {
389
362
  columns,
390
363
  ownerOptions
391
364
  ]);
392
- const searchWords = useMemo(()=>(filter == null ? void 0 : filter.field) === 'name' && filter.op === 'startsWith' ? [
393
- filter.value
394
- ] : [], [
395
- filter
396
- ]);
397
- const setSearchWords = useCallback((words)=>{
398
- const term = words.join(' ').trim();
399
- if (term) setFilter({
400
- field: 'name',
401
- op: 'startsWith',
402
- value: term
403
- });
404
- else if ((filter == null ? void 0 : filter.field) === 'name' && filter.op === 'startsWith') setFilter(null);
405
- }, [
406
- filter,
407
- setFilter
408
- ]);
409
365
  const gridFilter = useListGridFilter({
410
366
  clauses: viewFilters,
411
367
  onChange: views.setFilters,
412
368
  selectFields: [
413
369
  'ownerUid'
414
370
  ],
415
- single: true,
416
- keepAlongside: isNoNextActivityClause,
417
371
  search: {
418
372
  words: searchWords,
419
373
  onChange: setSearchWords
420
374
  }
421
375
  });
376
+ // What the query could not hold, named by the clause the reader set.
377
+ const refused = useMemo(()=>listQueryRefusals(plan.refused, {
378
+ fields: COMPANY_GRID_FILTER_FIELDS,
379
+ headers: COMPANY_GRID_FILTER_HEADERS,
380
+ options: {
381
+ ownerUid: ownerOptions
382
+ }
383
+ }), [
384
+ plan.refused,
385
+ ownerOptions
386
+ ]);
422
387
  /*
423
388
  * The grid's models are the view's (AGL-2617). The filter model shows the
424
389
  * view's clause in the panel as a typed one would appear — the owner's
425
390
  * stored `equals` back as the single-select `is` the panel offers — so a
426
391
  * view opened from its address reads as filtered, not as a mystery.
427
392
  */ const grid = useCrmViewGrid(views, filterColumns);
393
+ /** 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());
428
394
  const newCompanyButton = /*#__PURE__*/ _jsx(Button, {
429
395
  size: "small",
430
396
  variant: "contained",
@@ -487,10 +453,14 @@ const COMPANY_GRID_FILTER_HEADERS = {
487
453
  options: {
488
454
  ownerUid: ownerOptions
489
455
  },
490
- servedField: (_ref = filter == null ? void 0 : filter.field) != null ? _ref : null
456
+ marksServed: false
491
457
  })
492
458
  ]
493
459
  }),
460
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
461
+ refused: refused,
462
+ notices: plan.notices
463
+ }),
494
464
  /*#__PURE__*/ _jsx(CompaniesBulkBar, {
495
465
  hostId: hostId,
496
466
  scope: scope,
@@ -511,16 +481,16 @@ const COMPANY_GRID_FILTER_HEADERS = {
511
481
  selected: selectedIds,
512
482
  onChange: setSelectedIds
513
483
  },
514
- noRowsLabel: "No companies yet",
515
- noRowsDescription: "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.",
516
- noRowsAction: newCompanyButton,
484
+ noRowsLabel: narrowed ? 'No companies match these filters' : 'No companies yet',
485
+ noRowsDescription: narrowed ? undefined : '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.',
486
+ noRowsAction: narrowed ? undefined : newCompanyButton,
517
487
  onOpen: (id)=>openCompany(String(id)),
518
488
  // An empty table while the read is in flight reads as "you have
519
489
  // none" rather than "these are on their way".
520
490
  loading: !scope || status === 'loading',
521
491
  /*
522
492
  * The grid must NOT also filter. The query answers it, so a client
523
- * pass could only drop rows the query already matched.
493
+ * pass could only drop rows the query already matched (AGL-3321).
524
494
  */ filterMode: "server",
525
495
  filterModel: gridFilter.filterModel,
526
496
  onFilterModelChange: gridFilter.onFilterModelChange,
@@ -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 filterByNextActivity,\n isNoNextActivityClause,\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 {\n listFilterColumn,\n type ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\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 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 {\n listFilterConstraints,\n usePagedCollection,\n useFirestore,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { COMPANY_LIST_FILTER_FIELDS } from '../constants/company-filters'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { crmVisibleToClause, useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CompanyCsvOptions, companiesCsv } from '../model/companies-csv'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CompanyImportButton } from './company-import-drawer'\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: the served fields, and \"No next activity\". */\nconst COMPANY_GRID_FILTER_FIELDS = [\n ...COMPANY_LIST_FILTER_FIELDS,\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n]\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}\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 * The scope predicate is an array clause, and Firestore carries one per\n * query, so the word-prefix search the contact list offers cannot run here.\n * The name is a prefix range and an exact match instead, and the owner is a\n * choice from the roster — each reaching the whole collection, which is the\n * property a search must have: a company on page four is found, not reported\n * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.\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. Export CSV writes the loaded page through `companiesCsv()` —\n * the same file the bar writes over the selection, headed as the import\n * reads it — and Import CSV opens the companies drawer beside it.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\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\n /*\n * The column filter is the saved VIEW'S first clause (AGL-2617): this\n * grid's panel holds one, and a view of companies holds that one beside\n * the columns and the sort. Declared BEFORE the listener that reads it,\n * as the filter always was — the query is rebuilt from it.\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n /*\n * ...beside the \"No next activity\" clause (AGL-2661), which is not a\n * query the listener can run — absence has no index — and narrows the\n * loaded page below instead. The grid's one clause is the first that is\n * not it, and setting the grid's clause keeps it.\n */\n const viewFilters = views.state.filters\n const filter: ListFilterRequest | null =\n viewFilters.find((clause) => !isNoNextActivityClause(clause)) ?? null\n const setFilter = useCallback(\n (request: ListFilterRequest | null) =>\n views.setFilters([\n ...(request ? [request] : []),\n ...viewFilters.filter(isNoNextActivityClause),\n ]),\n [views.setFilters, viewFilters],\n )\n\n const {\n rows: loaded,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<CompanyRow>(\n (pageLimit) => {\n if (!scope) return null\n const constraints = listFilterConstraints(\n COMPANY_LIST_FILTER_FIELDS,\n filter,\n )\n return query(\n collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies),\n // Absent at the organization level, where the tokens are `null` (AGL-2630).\n ...crmVisibleToClause(visibleTo),\n ...(constraints ?? [orderBy('updatedAt', 'desc')]),\n limit(pageLimit),\n )\n },\n [firestore, scope, visibleTo, filter],\n { idField: '$id' },\n )\n const companies = useMemo(\n () => filterByNextActivity(loaded, viewFilters),\n [loaded, viewFilters],\n )\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n /*\n * The owner is written by ADDRESS, because the companies import resolves\n * an owner by email; the same options reach the bar, so the page's file\n * and the selection's are one format.\n */\n const csvOptions: CompanyCsvOptions = useMemo(\n () => ({ ownerEmail: members.emailFor }),\n [members.emailFor],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('companies.csv', 'text/csv', companiesCsv(companies, csvOptions))\n }, [companies, csvOptions])\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 // 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 serves ONE clause — a paged list has no window to\n * narrow beyond the page — so the panel's clause replaces the last, and\n * \"No next activity\", which narrows the loaded page, stands beside it.\n * The search box is the name's prefix: the one text search the indexes\n * serve here, so typing in it is the same clause as \"Company starts with\".\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 const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, COMPANY_GRID_FILTER_FIELDS, { ownerUid: ownerOptions }, COMPANY_GRID_FILTER_HEADERS),\n [columns, ownerOptions],\n )\n const searchWords = useMemo(\n () => (filter?.field === 'name' && filter.op === 'startsWith' ? [filter.value] : []),\n [filter],\n )\n const setSearchWords = useCallback(\n (words: string[]) => {\n const term = words.join(' ').trim()\n if (term) setFilter({ field: 'name', op: 'startsWith', value: term })\n else if (filter?.field === 'name' && filter.op === 'startsWith') setFilter(null)\n },\n [filter, setFilter],\n )\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['ownerUid'],\n single: true,\n keepAlongside: isNoNextActivityClause,\n search: { words: searchWords, onChange: setSearchWords },\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)\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 <CompanyImportButton hostId={hostId} org={org} />\n <Button size=\"small\" onClick={handleExport} disabled={!companies.length}>\n {'Export CSV'}\n </Button>\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={{ ownerUid: ownerOptions }}\n servedField={filter?.field ?? null}\n />\n </CrmListToolbar>\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n csv={csvOptions}\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=\"No companies yet\"\n noRowsDescription=\"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 noRowsAction={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.\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","filterByNextActivity","isNoNextActivityClause","pluginDocsHelp","crmMemberPickerLabel","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","listFilterColumn","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_NEXT_ACTIVITY_FILTER_HEADER","nextActivityColumn","ListFilterChips","listFilterGridColumns","useListGridFilter","CrmViewsControl","CrmListActions","CrmListToolbar","TABLE_ROW_HEIGHT","listFilterConstraints","usePagedCollection","useFirestore","Button","Chip","Stack","Typography","getGridSingleSelectOperators","collection","limit","orderBy","query","useRouter","useCallback","useMemo","useState","COMPANY_LIST_FILTER_FIELDS","useContactFieldDefinitions","crmVisibleToClause","useCrmScope","useOrgMemberOptions","companiesCsv","downloadTextFile","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CompanyImportButton","customFieldColumns","COMPANY_GRID_FILTER_FIELDS","COMPANY_GRID_FILTER_HEADERS","name","ownerUid","column","CompaniesSection","props","viewFilters","hostId","org","basePath","routes","router","firestore","scope","orgId","visibleTo","members","companyFields","views","section","state","filters","filter","find","clause","setFilter","request","setFilters","rows","loaded","status","hasMore","page","setPage","pageSize","setPageSize","pageLimit","constraints","companies","idField","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","handleExport","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","String","renderCell","sx","justifyContent","height","lineHeight","variant","industry","color","noWrap","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","length","direction","spacing","alignItems","overflow","slice","map","tag","size","label","type","valueOptions","options","option","value","uid","filterOperators","operator","ready","labelFor","updatedAt","seconds","toLocaleDateString","active","ownerOptions","filterColumns","searchWords","op","setSearchWords","words","term","trim","gridFilter","clauses","onChange","selectFields","single","keepAlongside","search","grid","newCompanyButton","disabled","startIcon","path","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","controller","allLabel","fields","headers","servedField","selected","onSelectedChange","csv","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":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAEEA,eAAe,EAEfC,oBAAoB,EACpBC,sBAAsB,EACtBC,cAAc,EACdC,oBAAoB,QACf,eAAc;AACrB,SAASC,OAAO,QAAQ,yBAAwB;AAChD,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,SAAS,QAAQ,uDAAsD;AAChF,SACEC,gBAAgB,QAEX,yCAAwC;AAC/C,SAASC,eAAe,QAAQ,iCAA6B;AAC7D,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,cAAc,EAAEC,sBAAsB,QAAQ,uBAAmB;AAC1E,SACEC,8BAA8B,EAC9BC,+BAA+B,EAC/BC,kBAAkB,QACb,gCAA4B;AACnC,OAAOC,qBAAqB,8DAA6D;AACzF,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,iBAAiB,QAAQ,kDAAiD;AACnF,OAAOC,qBAAqB,yBAAqB;AACjD,SAASC,cAAc,EAAEC,cAAc,QAAQ,wBAAoB;AACnE,SAASC,gBAAgB,QAAQ,8CAA6C;AAC9E,SACEC,qBAAqB,EACrBC,kBAAkB,EAClBC,YAAY,QACP,iCAAgC;AACvC,SAASC,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAC/D,SACEC,4BAA4B,QAEvB,mBAAkB;AACzB,SAASC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,QAAe,qBAAoB;AAC7E,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACtD,SAASC,0BAA0B,QAAQ,kCAA8B;AACzE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,kBAAkB,EAAEC,WAAW,QAAQ,4BAAwB;AACxE,SAASC,mBAAmB,QAAQ,qCAAiC;AACrE,SAAiCC,YAAY,QAAQ,4BAAwB;AAC7E,SAASC,gBAAgB,QAAQ,2BAAuB;AACxD,SAASC,SAAS,QAAQ,yBAAqB;AAC/C,OAAOC,sBAAsB,0BAAsB;AACnD,OAAOC,uBAAuB,2BAAuB;AACrD,SAASC,mBAAmB,QAAQ,6BAAyB;AAC7D,SAASC,kBAAkB,QAAQ,8BAA0B;AAY7D,uFAAuF,GACvF,MAAMC,6BAA6B;OAC9BZ;IACH1B;CACD;AACD,MAAMuC,8BAAgE;IACpEC,MAAM;IACNC,UAAU;IACV,CAACzC,+BAA+B0C,MAAM,CAAC,EAAEzC;AAC3C;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCC,GACD,OAAO,SAAS0C,iBAAiBC,KAA4B;QA8BzDC;IA7BF,MAAM,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGJ;IAClC,MAAMK,SAAShB,UAAUe;IACzB,MAAME,SAAS5B;IACf,MAAM6B,YAAYvC;IAClB,MAAM,EAAEwC,KAAK,EAAEC,KAAK,EAAEC,SAAS,EAAE,GAAGzB,YAAY;QAAEiB;QAAQC;IAAI;IAC9D;;;;GAIC,GACD,MAAMQ,UAAUzB,oBAAoBuB;IACpC,uEAAuE;IACvE,MAAMG,gBAAgB7B,2BAA2B0B,OAAO;IAExD;;;;;GAKC,GACD,MAAMI,QAAQ7D,gBAAgB;QAAE8D,SAAS;QAAaZ;QAAQC;QAAKC;IAAS;IAC5E;;;;;GAKC,GACD,MAAMH,cAAcY,MAAME,KAAK,CAACC,OAAO;IACvC,MAAMC,UACJhB,oBAAAA,YAAYiB,IAAI,CAAC,CAACC,SAAW,CAAC5E,uBAAuB4E,oBAArDlB,oBAAiE;IACnE,MAAMmB,YAAYzC,YAChB,CAAC0C,UACCR,MAAMS,UAAU,CAAC;eACXD,UAAU;gBAACA;aAAQ,GAAG,EAAE;eACzBpB,YAAYgB,MAAM,CAAC1E;SACvB,GACH;QAACsE,MAAMS,UAAU;QAAErB;KAAY;IAGjC,MAAM,EACJsB,MAAMC,MAAM,EACZC,MAAM,EACNC,OAAO,EACPC,IAAI,EACJC,OAAO,EACPC,QAAQ,EACRC,WAAW,EACZ,GAAG/D,mBACF,CAACgE;QACC,IAAI,CAACvB,OAAO,OAAO;QACnB,MAAMwB,cAAclE,sBAClBgB,4BACAmC;QAEF,OAAOxC,MACLH,WAAWiC,WAAWC,KAAK,CAAC,EAAE,EAAEA,KAAK,CAAC,EAAE,EAAEnE,gBAAgB4F,SAAS,GACnE,4EAA4E;WACzEjD,mBAAmB0B,eAClBsB,sBAAAA,cAAe;YAACxD,QAAQ,aAAa;SAAQ,EACjDD,MAAMwD;IAEV,GACA;QAACxB;QAAWC;QAAOE;QAAWO;KAAO,EACrC;QAAEiB,SAAS;IAAM;IAEnB,MAAMD,YAAYrD,QAChB,IAAMtC,qBAAqBkF,QAAQvB,cACnC;QAACuB;QAAQvB;KAAY;IAEvB,MAAMkC,QAAQvD,QAAQ,IAAMwD,KAAKC,GAAG,IAAI,EAAE;IAE1C,MAAM,CAACC,YAAYC,cAAc,GAAG1D,SAAS;IAC7C,MAAM,CAAC2D,aAAaC,eAAe,GAAG5D,SAAmB,EAAE;IAC3D;;;;GAIC,GACD,MAAM6D,aAAgC9D,QACpC,IAAO,CAAA;YAAE+D,YAAYhC,QAAQiC,QAAQ;QAAC,CAAA,GACtC;QAACjC,QAAQiC,QAAQ;KAAC;IAEpB,MAAMC,eAAelE,YAAY;QAC/BS,iBAAiB,iBAAiB,YAAYD,aAAa8C,WAAWS;IACxE,GAAG;QAACT;QAAWS;KAAW;IAC1B,MAAMI,cAAcnE,YAClB,CAACoE,KAAezC,OAAO0C,IAAI,CAAC3C,OAAO4C,OAAO,CAACF,MAC3C;QAACzC;QAAQD;KAAO;IAGlB,MAAM6C,UAAwBtE,QAC5B,IAAM;YACJ;gBACEuE,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;eACPvG,iBAAiB+B,4BAA4B;gBAChDyE,aAAa,CAACC,QAAQC;wBAA2BA;2BAAPC,QAAOD,YAAAA,IAAI7D,IAAI,YAAR6D,YAAY;;gBAC7DE,YAAY,CAAC,EAAEF,GAAG,EAAuB,iBACvC,MAACtF;wBACCyF,IAAI;4BAAEC,gBAAgB;4BAAUC,QAAQ;4BAAQC,YAAY;wBAAK;;0CAEjE,KAAC3F;gCAAW4F,SAAQ;gCAAQJ,IAAI;oCAAEG,YAAY;gCAAK;0CAChDN,IAAI7D,IAAI;;4BAEV6D,IAAIQ,QAAQ,iBACX,KAAC7F;gCACC4F,SAAQ;gCACRE,OAAM;gCACNN,IAAI;oCAAEG,YAAY;gCAAK;gCACvBI,MAAM;0CAELV,IAAIQ,QAAQ;iCAEb;;;;YAIV;gBACEd,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACV,oEAAoE;gBACpE,iEAAiE;gBACjE,iEAAiE;gBACjE,4CAA4C;gBAC5Cc,YAAY;gBACZC,UAAU;gBACVd,aAAa,CAACC,QAAQC;wBAA2BA;2BAAPC,QAAOD,cAAAA,IAAIa,MAAM,YAAVb,cAAc;;gBAC/DE,YAAY,CAAC,EAAEF,GAAG,EAAuB,GACvCA,IAAIa,MAAM,iBACR,KAAClG;wBAAW4F,SAAQ;kCAASP,IAAIa,MAAM;uCAEvC,KAAClG;wBAAW4F,SAAQ;wBAAQE,OAAM;kCAC/B;;YAGT;YACA;gBACEf,OAAO;gBACPC,YAAY;gBACZmB,OAAO;gBACPC,OAAO;gBACPC,aAAa;gBACb;;;;;SAKC,GACDL,YAAY;gBACZC,UAAU;gBACVd,aAAa,CAACC,QAAQC;wBAA2BA;2BAAPiB,QAAOjB,qBAAAA,IAAIkB,aAAa,YAAjBlB,qBAAqB;;gBACtEE,YAAY,CAAC,EAAEF,GAAG,EAAuB;wBAK7BA;yCAJV,KAACrF;wBACC4F,SAAQ;wBACRE,OAAOT,IAAIkB,aAAa,GAAG,iBAAiB;kCAE3CD,QAAOjB,qBAAAA,IAAIkB,aAAa,YAAjBlB,qBAAqB,GAAGmB,cAAc;;;YAGpD;YACA;gBACEzB,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACV,oEAAoE;gBACpE,kEAAkE;gBAClE,sCAAsC;gBACtCc,YAAY;gBACZC,UAAU;gBACVd,aAAa,CAACC,QAAQC;wBAAqBA;2BAAD,EAACA,YAAAA,IAAIoB,IAAI,YAARpB,YAAY,EAAE,EAAEqB,IAAI,CAAC;;gBAChEnB,YAAY,CAAC,EAAEF,GAAG,EAAuB;wBACvCA;2BAAAA,EAAAA,YAAAA,IAAIoB,IAAI,qBAARpB,UAAUsB,MAAM,kBACd,MAAC5G;wBAAM6G,WAAU;wBAAMC,SAAS;wBAAKrB,IAAI;4BAAEsB,YAAY;4BAAUC,UAAU;wBAAS;;4BACjF1B,IAAIoB,IAAI,CAACO,KAAK,CAAC,GAAG,GAAGC,GAAG,CAAC,CAACC,oBACzB,KAACpH;oCAAeqH,MAAK;oCAAQvB,SAAQ;oCAAWwB,OAAOF;mCAA5CA;4BAEZ7B,IAAIoB,IAAI,CAACE,MAAM,GAAG,kBACjB,KAAC3G;gCAAW4F,SAAQ;gCAAUE,OAAM;0CACjC,CAAC,CAAC,EAAET,IAAIoB,IAAI,CAACE,MAAM,GAAG,GAAG;iCAE1B;;uCAGN,KAAC3G;wBAAW4F,SAAQ;wBAAQE,OAAM;kCAC/B;;;YAGT;YACA;gBACEf,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVe,UAAU;gBACV;;;;;SAKC,GACDoB,MAAM;gBACNC,cAAc/E,QAAQgF,OAAO,CAACN,GAAG,CAAC,CAACO,SAAY,CAAA;wBAC7CC,OAAOD,OAAOE,GAAG;wBACjBN,OAAO/I,qBAAqBmJ;oBAC9B,CAAA;gBACAxB,YAAYzD,QAAQgF,OAAO,CAACZ,MAAM,GAAG;gBACrCgB,iBAAiB1H,+BAA+B4C,MAAM,CACpD,CAAC+E,WAAaA,SAASH,KAAK,KAAK;gBAEnCtC,aAAa,CAACC,QAAQC;wBAA2BA;2BAAPC,QAAOD,gBAAAA,IAAI5D,QAAQ,YAAZ4D,gBAAgB;;gBACjEE,YAAY,CAAC,EAAEF,GAAG,EAAuB,iBACvC,KAACrF;wBACC4F,SAAQ;wBACRE,OAAOT,IAAI5D,QAAQ,GAAG,iBAAiB;kCAEtC4D,IAAI5D,QAAQ,GACTc,QAAQsF,KAAK,GACXtF,QAAQuF,QAAQ,CAACzC,IAAI5D,QAAQ,IAC7B,MACF;;YAGV;YACA;gBACEsD,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVc,YAAY;gBACZC,UAAU;gBACVd,aAAa,CAACC,QAAQC;wBACpBA;2BAAAA,EAAAA,iBAAAA,IAAI0C,SAAS,qBAAb1C,eAAe2C,OAAO,IAAG,IAAIhE,KAAKqB,IAAI0C,SAAS,CAACC,OAAO,GAAG,QAAQ;;gBACpEzC,YAAY,CAAC,EAAEF,GAAG,EAAuB;wBAEpCA;yCADH,KAACrF;wBAAW4F,SAAQ;wBAAUE,OAAM;kCACjCT,EAAAA,iBAAAA,IAAI0C,SAAS,qBAAb1C,eAAe2C,OAAO,IACnB,IAAIhE,KAAKqB,IAAI0C,SAAS,CAACC,OAAO,GAAG,MAAMC,kBAAkB,KACzD;;;YAGV;YACA,qEAAqE;YACrE/I,mBAAmB6E;YACnB,oEAAoE;YACpE,sEAAsE;eACnE1C,mBAAmBmB,cAAc0F,MAAM;SAC3C,EACD;QAAC3F;QAASC,cAAc0F,MAAM;QAAEnE;KAAM;IAGxC;;;;;;;GAOC,GACD,MAAMoE,eAAe3H,QACnB,IACE+B,QAAQgF,OAAO,CAACN,GAAG,CAAC,CAACO,SAAY,CAAA;gBAC/BC,OAAOD,OAAOE,GAAG;gBACjBN,OAAO/I,qBAAqBmJ;YAC9B,CAAA,IACF;QAACjF,QAAQgF,OAAO;KAAC;IAEnB,MAAMa,gBAAgB5H,QACpB,IACEpB,sBAAsB0F,SAASxD,4BAA4B;YAAEG,UAAU0G;QAAa,GAAG5G,8BACzF;QAACuD;QAASqD;KAAa;IAEzB,MAAME,cAAc7H,QAClB,IAAOqC,CAAAA,0BAAAA,OAAQkC,KAAK,MAAK,UAAUlC,OAAOyF,EAAE,KAAK,eAAe;YAACzF,OAAO4E,KAAK;SAAC,GAAG,EAAE,EACnF;QAAC5E;KAAO;IAEV,MAAM0F,iBAAiBhI,YACrB,CAACiI;QACC,MAAMC,OAAOD,MAAM9B,IAAI,CAAC,KAAKgC,IAAI;QACjC,IAAID,MAAMzF,UAAU;YAAE+B,OAAO;YAAQuD,IAAI;YAAcb,OAAOgB;QAAK;aAC9D,IAAI5F,CAAAA,0BAAAA,OAAQkC,KAAK,MAAK,UAAUlC,OAAOyF,EAAE,KAAK,cAActF,UAAU;IAC7E,GACA;QAACH;QAAQG;KAAU;IAErB,MAAM2F,aAAatJ,kBAAkB;QACnCuJ,SAAS/G;QACTgH,UAAUpG,MAAMS,UAAU;QAC1B4F,cAAc;YAAC;SAAW;QAC1BC,QAAQ;QACRC,eAAe7K;QACf8K,QAAQ;YAAET,OAAOH;YAAaQ,UAAUN;QAAe;IACzD;IACA;;;;;GAKC,GACD,MAAMW,OAAOrK,eAAe4D,OAAO2F;IAEnC,MAAMe,iCACJ,KAACtJ;QACCsH,MAAK;QACLvB,SAAQ;QACRE,OAAM;QACNsD,UAAU,CAAChH;QACXiH,yBAAW,KAAC7K;YAAQ8K,MAAMhL,QAAQgL,IAAI;YAAEnC,MAAM;;QAC9CoC,SAAS,IAAMpF,cAAc;kBAE5B;;IAIL,qBACE,MAAC5F;QACCiL,QAAQ;QACRC,MAAMrL,eAAe,aAAa;YAAEsL,QAAQ;QAAsB;QAClEC,cAAc;QACdC,cAAc;QACdC,iBAAgB;QAChBC,aAAa;YACX,8DAA8D;YAC9DC,sBACE,MAACxK;;kCACC,KAAC6B;wBAAoBU,QAAQA;wBAAQC,KAAKA;;kCAC1C,KAAClC;wBAAOsH,MAAK;wBAAQoC,SAAS9E;wBAAc2E,UAAU,CAACvF,UAAU8C,MAAM;kCACpE;;oBAEFwC;;;QAGP;;0BAEA,MAACpJ;gBAAM8G,SAAS;;kCACd,KAAC7G;wBAAW4F,SAAQ;wBAAQE,OAAM;kCAC/B,oEACC,gEACA;;kCAGJ,MAACtG;wBAAe4H,OAAM;;0CACpB,KAAC9H;gCAAgB0K,YAAYvH;gCAAOwH,UAAS;;0CAC7C,KAAC9K;gCACC+K,QAAQ5I;gCACR6I,SAAS5I;gCACTqH,SAAS/G;gCACTgH,UAAUpG,MAAMS,UAAU;gCAC1BqE,SAAS;oCAAE9F,UAAU0G;gCAAa;gCAClCiC,WAAW,UAAEvH,0BAAAA,OAAQkC,KAAK,mBAAI;;;;kCAGlC,KAAC7D;wBACCY,QAAQA;wBACRM,OAAOA;wBACPe,MAAMU;wBACNwG,UAAUjG;wBACVkG,kBAAkBjG;wBAClB9B,SAASA;wBACTgI,KAAKjG;;kCAEP,KAACvF;wBAAuB0I,OAAOyB,KAAKsB,WAAW;kCAC7C,cAAA,KAAC9L;4BACC+L,WAAWhL;4BACXqF,SAASoE,KAAKpE,OAAO;4BACrB4F,OAAO5L;4BACPqE,MAAMU;4BACN8G,YAAY;gCAAEN,UAAUjG;gCAAayE,UAAUxE;4BAAe;4BAC9DuG,aAAY;4BACZC,mBAAkB;4BAClBC,cAAc3B;4BACd4B,QAAQ,CAACpG,KAAOD,YAAYY,OAAOX;4BACnC,gEAAgE;4BAChE,8CAA8C;4BAC9CqG,SAAS,CAAC5I,SAASiB,WAAW;4BAC9B;;;aAGC,GACD4H,YAAW;4BACXC,aAAavC,WAAWuC,WAAW;4BACnCC,qBAAqBxC,WAAWwC,mBAAmB;4BACnDC,WAAW;4BACX,0DAA0D;4BAC1DC,uBAAuBnC,KAAKmC,qBAAqB;4BACjDC,+BAA+BpC,KAAKoC,6BAA6B;4BACjEC,WAAWrC,KAAKqC,SAAS;4BACzBC,mBAAmBtC,KAAKsC,iBAAiB;4BACzC,8DAA8D;4BAC9DC,UAAU;;;kCAGd,KAAChN;wBACC8E,MAAMA;wBACNE,UAAUA;wBACViI,UAAU7H,UAAU8C,MAAM;wBAC1BrD,SAASA;wBACTqI,cAAcnI;wBACdoI,kBAAkBlI;;;;YAOrBQ,2BACC,KAAC/C;gBACC0K,IAAI;gBACJC,SAAS,IAAM3H,cAAc;gBAC7BrC,QAAQA;gBACRC,KAAKA;gBACLQ,SAASA;gBACTwJ,SAASrH;iBAET;;;AAGV;AACA/C,iBAAiBqK,WAAW,GAAG;AAE/B,eAAerK,iBAAgB"}
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 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_PREFIX_SEARCH,\n} from '../constants/company-filters'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CompanyCsvOptions, companiesCsv } from '../model/companies-csv'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CompanyImportButton } from './company-import-drawer'\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}\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. Export CSV writes the loaded page through `companiesCsv()` —\n * the same file the bar writes over the selection, headed as the import\n * reads it — and Import CSV opens the companies drawer beside it.\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\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 /*\n * The owner is written by ADDRESS, because the companies import resolves\n * an owner by email; the same options reach the bar, so the page's file\n * and the selection's are one format.\n */\n const csvOptions: CompanyCsvOptions = useMemo(\n () => ({ ownerEmail: members.emailFor }),\n [members.emailFor],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('companies.csv', 'text/csv', companiesCsv(companies, csvOptions))\n }, [companies, csvOptions])\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 // 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 const filterColumns = useMemo(\n () =>\n listFilterGridColumns(columns, COMPANY_GRID_FILTER_FIELDS, { ownerUid: ownerOptions }, COMPANY_GRID_FILTER_HEADERS),\n [columns, ownerOptions],\n )\n const gridFilter = useListGridFilter({\n clauses: viewFilters,\n onChange: views.setFilters,\n selectFields: ['ownerUid'],\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: { ownerUid: ownerOptions },\n }),\n [plan.refused, ownerOptions],\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)\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 <CompanyImportButton hostId={hostId} org={org} />\n <Button size=\"small\" onClick={handleExport} disabled={!companies.length}>\n {'Export CSV'}\n </Button>\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={{ ownerUid: ownerOptions }}\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 csv={csvOptions}\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","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_PREFIX_SEARCH","useContactFieldDefinitions","useCrmScope","useOrgMemberOptions","companiesCsv","downloadTextFile","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CompanyImportButton","customFieldColumns","COMPANY_GRID_FILTER_FIELDS","COMPANY_GRID_FILTER_HEADERS","name","ownerUid","column","CompaniesSection","props","hostId","org","basePath","routes","router","scope","orgId","visibleTo","members","companyFields","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","csvOptions","ownerEmail","emailFor","handleExport","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","String","renderCell","sx","justifyContent","height","lineHeight","variant","industry","color","noWrap","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","length","direction","spacing","alignItems","overflow","slice","map","tag","size","label","type","valueOptions","options","option","value","uid","filterOperators","filter","operator","ready","labelFor","updatedAt","seconds","toLocaleDateString","active","ownerOptions","filterColumns","gridFilter","onChange","setFilters","selectFields","words","refused","fields","headers","grid","narrowed","some","word","trim","newCompanyButton","disabled","startIcon","path","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","controller","allLabel","marksServed","notices","selected","onSelectedChange","csv","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","undefined","noRowsAction","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
@@ -15,8 +15,9 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
18
19
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
- import { createResourceUid, CRM_COLLECTIONS, CRM_RECORDS_BAND_FULL_MESSAGE, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
20
+ import { createResourceUid, CRM_COLLECTIONS, CRM_RECORDS_BAND_FULL_MESSAGE, crmNewRecordListFields, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
20
21
  import { ICON_VARIANT_CLOSE } from "@aglyn/shared-data-enums";
21
22
  import { Container, HelpTip, MdiIcon, SrOnly } from "@aglyn/shared-ui-jsx";
22
23
  import { NavigationDrawerComponent } from "@aglyn/shared-ui-jsx/components/navigation-drawer.component";
@@ -33,6 +34,7 @@ import { companyDraftFields, companyDraftFrom, EMPTY_COMPANY_DRAFT } from "../mo
33
34
  import { crmCustomDraftDocument, crmCustomDraftMissingRequired, crmCustomDraftValue, crmCustomDraftWrites } from "../model/crm-custom-draft.js";
34
35
  import { CrmCustomFieldControl } from "./crm-custom-field-control.js";
35
36
  import { CrmSitePicker } from "./crm-site-picker.js";
37
+ import { CRM_CLIENT_SEARCH_FIELDS, crmClientListFields } from "../model/crm-list-query.js";
36
38
  const ADDRESS_FIELDS = [
37
39
  {
38
40
  key: 'line1',
@@ -163,10 +165,17 @@ const ADDRESS_FIELDS = [
163
165
  unreadable: (_ref = seed == null ? void 0 : seed.unreadable) != null ? _ref : false,
164
166
  fromCache: (_ref1 = seed == null ? void 0 : seed.fromCache) != null ? _ref1 : false
165
167
  }, async ()=>{
166
- await updateDoc(ref, _extends({}, result.set, Object.fromEntries(result.cleared.map((field)=>[
168
+ const cleared = Object.fromEntries(result.cleared.map((field)=>[
167
169
  field,
168
170
  deleteField()
169
- ])), crmCustomDraftWrites(storedCustom, custom), {
171
+ ]));
172
+ // The stored record the list fields are computed from: the row
173
+ // without the id the listener stamped on it.
174
+ const { $id: _rowId } = company, stored = _object_without_properties_loose(company, [
175
+ "$id"
176
+ ]);
177
+ const searchFields = crmClientListFields('companies', stored, _extends({}, result.set, cleared), CRM_CLIENT_SEARCH_FIELDS);
178
+ await updateDoc(ref, _extends({}, result.set, cleared, searchFields, crmCustomDraftWrites(storedCustom, custom), {
170
179
  updatedAt: serverTimestamp()
171
180
  }));
172
181
  });
@@ -185,7 +194,11 @@ const ADDRESS_FIELDS = [
185
194
  if (!createHostId) return;
186
195
  await setDoc(doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies, id), _extends({}, result.set, customDocument ? {
187
196
  custom: customDocument
188
- } : {}, {
197
+ } : {}, crmNewRecordListFields('companies', _extends({}, result.set, {
198
+ visibleTo: [
199
+ ...createTokens
200
+ ]
201
+ })), {
189
202
  /*
190
203
  * The scope every CRM creator stamps: the whole org when the org
191
204
  * shares by default, and otherwise the sites that present as one