@aglyn/plugins-crm 1.0.0-beta.199 → 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
@@ -13,39 +13,69 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ /*
17
- * Companies (`orgs/{orgId}/companies`, read scoped to a host).
16
+ */ import { CRM_NEXT_ACTIVITY_FILTER_FIELD } from "../components/crm-next-activity-column.js";
17
+ import { CRM_LIST_SEARCH } from "../model/crm-list-query.js";
18
+ /*
19
+ * Companies (`orgs/{orgId}/companies`, read scoped to a host) — every clause
20
+ * and the search word on the list's one query, newest change first
21
+ * (AGL-3321).
18
22
  *
19
- * Every predicate here is appended to the listener's `visibleTo
20
- * array-contains-any`, which is the constraint that decides what a company
21
- * filter may be. Firestore carries ONE array clause per query, so the
22
- * word-prefix `contains` search the contact list declares through
23
- * `nameTokens` cannot run beside the scope predicate — a query with both is
24
- * refused outright, not served slowly. The name is therefore offered as a
25
- * PREFIX range and an exact match over `nameLower`, which the
26
- * `(visibleTo CONTAINS, nameLower ASC)` index serves with the scope in
27
- * place. No `tokensPath`, deliberately.
23
+ * The search box reads `searchTokens`, the word prefixes of the name and
24
+ * the domain every company writer stamps (`crmCompanyListFields`). Under a
25
+ * site the scope clause (`visibleTo array-contains-any`) is the query's one
26
+ * array clause, and the search folds into it (`scopedSearchTokens`) for a
27
+ * reader who may drop it.
28
28
  *
29
- * `ownerUid` is declared so the translator can answer it; the column itself
30
- * renders a person's name from the roster and offers a single-select
31
- * operator in the panel, which the section maps onto this field's `equals`.
32
- * Served by `(visibleTo CONTAINS, ownerUid ASC)`.
29
+ * The name is also an exact match and a PREFIX range over `nameLower` — the
30
+ * list's one range, which then orders the list by name. The owner is a
31
+ * choice from the roster, and "No next activity" asks `nextTaskAtMs ==
32
+ * null`. Each equality rides one `(field, updatedAt DESC)` composite, and
33
+ * one `(field, nameLower ASC)` beside it for the prefix.
33
34
  */ export const COMPANY_LIST_FILTER_FIELDS = [
34
35
  {
35
36
  column: 'name',
36
37
  kind: 'text',
37
38
  path: 'name',
38
39
  lowerPath: 'nameLower',
39
- presence: 'always'
40
+ presence: 'always',
41
+ operators: [
42
+ 'equals',
43
+ 'startsWith'
44
+ ]
40
45
  },
41
46
  {
42
47
  column: 'ownerUid',
43
48
  kind: 'exact',
44
49
  path: 'ownerUid',
45
50
  operators: [
46
- 'equals'
51
+ 'equals',
52
+ 'isAnyOf'
47
53
  ]
54
+ },
55
+ CRM_NEXT_ACTIVITY_FILTER_FIELD
56
+ ];
57
+ /**
58
+ * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the
59
+ * start of the name, beside the scope clause — the prefix range the
60
+ * `(field, nameLower)` composites already serve.
61
+ */ export const COMPANY_PREFIX_SEARCH = {
62
+ field: COMPANY_LIST_FILTER_FIELDS[0],
63
+ notice: 'Search matches the start of a company’s name for access limited to specific sites.'
64
+ };
65
+ /** The list's orders: newest change first, and by name under a prefix. */ export const COMPANY_LIST_SORTS = [
66
+ {
67
+ path: 'updatedAt',
68
+ direction: 'desc'
69
+ },
70
+ {
71
+ path: 'nameLower',
72
+ direction: 'asc'
48
73
  }
49
74
  ];
75
+ export const COMPANY_LIST_DECLARATION = {
76
+ fields: COMPANY_LIST_FILTER_FIELDS,
77
+ sorts: COMPANY_LIST_SORTS,
78
+ search: CRM_LIST_SEARCH
79
+ };
50
80
 
51
81
  //# sourceMappingURL=company-filters.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/company-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\n\n/*\n * Companies (`orgs/{orgId}/companies`, read scoped to a host).\n *\n * Every predicate here is appended to the listener's `visibleTo\n * array-contains-any`, which is the constraint that decides what a company\n * filter may be. Firestore carries ONE array clause per query, so the\n * word-prefix `contains` search the contact list declares through\n * `nameTokens` cannot run beside the scope predicate — a query with both is\n * refused outright, not served slowly. The name is therefore offered as a\n * PREFIX range and an exact match over `nameLower`, which the\n * `(visibleTo CONTAINS, nameLower ASC)` index serves with the scope in\n * place. No `tokensPath`, deliberately.\n *\n * `ownerUid` is declared so the translator can answer it; the column itself\n * renders a person's name from the roster and offers a single-select\n * operator in the panel, which the section maps onto this field's `equals`.\n * Served by `(visibleTo CONTAINS, ownerUid ASC)`.\n */\nexport const COMPANY_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n presence: 'always',\n },\n {\n column: 'ownerUid',\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals'],\n },\n]\n"],"names":["COMPANY_LIST_FILTER_FIELDS","column","kind","path","lowerPath","presence","operators"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,MAAMA,6BAAyD;IACpE;QACEC,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNC,WAAW;QACXC,UAAU;IACZ;IACA;QACEJ,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNG,WAAW;YAAC;SAAS;IACvB;CACD,CAAA"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/company-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\n\nimport { CRM_NEXT_ACTIVITY_FILTER_FIELD } from '../components/crm-next-activity-column'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { CRM_LIST_SEARCH } from '../model/crm-list-query'\n\n/*\n * Companies (`orgs/{orgId}/companies`, read scoped to a host) — every clause\n * and the search word on the list's one query, newest change first\n * (AGL-3321).\n *\n * The search box reads `searchTokens`, the word prefixes of the name and\n * the domain every company writer stamps (`crmCompanyListFields`). Under a\n * site the scope clause (`visibleTo array-contains-any`) is the query's one\n * array clause, and the search folds into it (`scopedSearchTokens`) for a\n * reader who may drop it.\n *\n * The name is also an exact match and a PREFIX range over `nameLower` — the\n * list's one range, which then orders the list by name. The owner is a\n * choice from the roster, and \"No next activity\" asks `nextTaskAtMs ==\n * null`. Each equality rides one `(field, updatedAt DESC)` composite, and\n * one `(field, nameLower ASC)` beside it for the prefix.\n */\nexport const COMPANY_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n },\n {\n column: 'ownerUid',\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf'],\n },\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n]\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the name, beside the scope clause — the prefix range the\n * `(field, nameLower)` composites already serve.\n */\nexport const COMPANY_PREFIX_SEARCH = {\n field: COMPANY_LIST_FILTER_FIELDS[0],\n notice: 'Search matches the start of a company’s name for access limited to specific sites.',\n}\n\n/** The list's orders: newest change first, and by name under a prefix. */\nexport const COMPANY_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n { path: 'nameLower', direction: 'asc' },\n]\n\nexport const COMPANY_LIST_DECLARATION: ListQueryDeclaration = {\n fields: COMPANY_LIST_FILTER_FIELDS,\n sorts: COMPANY_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n"],"names":["CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_LIST_SEARCH","COMPANY_LIST_FILTER_FIELDS","column","kind","path","lowerPath","presence","operators","COMPANY_PREFIX_SEARCH","field","notice","COMPANY_LIST_SORTS","direction","COMPANY_LIST_DECLARATION","fields","sorts","search"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID,SAASA,8BAA8B,QAAQ,4CAAwC;AAEvF,SAASC,eAAe,QAAQ,6BAAyB;AAEzD;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,6BAAyD;IACpE;QACEC,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNC,WAAW;QACXC,UAAU;QACVC,WAAW;YAAC;YAAU;SAAa;IACrC;IACA;QACEL,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNG,WAAW;YAAC;YAAU;SAAU;IAClC;IACAR;CACD,CAAA;AAED;;;;CAIC,GACD,OAAO,MAAMS,wBAAwB;IACnCC,OAAOR,0BAA0B,CAAC,EAAE;IACpCS,QAAQ;AACV,EAAC;AAED,wEAAwE,GACxE,OAAO,MAAMC,qBAA+C;IAC1D;QAAEP,MAAM;QAAaQ,WAAW;IAAO;IACvC;QAAER,MAAM;QAAaQ,WAAW;IAAM;CACvC,CAAA;AAED,OAAO,MAAMC,2BAAiD;IAC5DC,QAAQb;IACRc,OAAOJ;IACPK,QAAQhB;AACV,EAAC"}
@@ -15,18 +15,17 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import { type ContactFieldDefinition } from '@aglyn/aglyn';
18
- import type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter';
18
+ import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
19
+ import type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
20
+ import { type CrmClauseAsked } from '../model/crm-list-query';
21
+ /** What the Filters panel offers on a contact, in the grid's grammar. */
19
22
  export declare const CONTACT_LIST_FILTER_FIELDS: readonly ListFilterField[];
20
23
  /**
21
- * One filter field per active custom contact field (AGL-2617).
22
- *
23
- * A custom value lives under the facet's `custom` map, so every one is
24
- * window-only for the reason the facet fields above are, and its column is
25
- * the one the table shows it under — `crmContactCustomColumn(key)` — so a
26
- * clause and a column agree on the name and a saved view's columns and
27
- * filters name the same thing. The kind follows the definition's type, and
28
- * `nullable` is how a cleared value is stored, which is what makes both
29
- * empty operators honest here.
24
+ * One filter field per active custom contact field (AGL-2617). A custom
25
+ * value lives under the holder's `custom` map, so it is asked through the
26
+ * holder's facet key: equality on a picked, typed or ticked value, and
27
+ * "is not empty". Its column is the one the table shows it under —
28
+ * `crmContactCustomColumn(key)` — so a clause and a column agree on the name.
30
29
  */
31
30
  export declare function contactCustomFilterFields(definitions: readonly Pick<ContactFieldDefinition, 'key' | 'type' | 'retiredAt'>[]): ListFilterField[];
32
31
  /** The custom fields' headers, keyed by the column each filters as. */
@@ -36,3 +35,55 @@ export declare function contactCustomFilterHeaders(definitions: readonly Pick<Co
36
35
  * picker, and as the header of a filter-only hidden column.
37
36
  */
38
37
  export declare const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>>;
38
+ /** The list's one order: newest change first. */
39
+ export declare const CONTACT_LIST_SORTS: readonly ListQuerySort[];
40
+ /** The stored array a facet clause is asked of. */
41
+ export declare const CONTACT_FACET_KEYS_PATH = "facetKeys";
42
+ /** The fields the Contacts query asks — each a stored field, as named. */
43
+ export declare const CONTACT_QUERY_FIELDS: readonly ListFilterField[];
44
+ export declare const CONTACT_LIST_DECLARATION: ListQueryDeclaration;
45
+ /**
46
+ * The ranges that stand ALONE beside the scope clause: Created and a name
47
+ * or address prefix each order the list by their own field, which the
48
+ * `(visibleTo, field)` composites serve and nothing beside them. Updated is
49
+ * not one: the list is ordered by it already, so it composes freely.
50
+ */
51
+ export declare function contactSoloClause(clause: ListFilterRequest): boolean;
52
+ /**
53
+ * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the
54
+ * start of the contact's name, beside the scope clause.
55
+ */
56
+ export declare const CONTACT_PREFIX_SEARCH: {
57
+ field: ListFilterField;
58
+ notice: string;
59
+ };
60
+ /** Who is asking: the viewing holder, and whether their query may drop the scope clause. */
61
+ export interface ContactQueryReader {
62
+ /** The viewing holder's group id under a site; `null` at the organization level. */
63
+ groupId: string | null;
64
+ /**
65
+ * Whether the reader's query may stand a facet or form clause in the
66
+ * scope clause's place (`useCrmFoldsScope`) — always at the organization
67
+ * level, where there is no scope clause.
68
+ */
69
+ foldsScope: boolean;
70
+ }
71
+ /**
72
+ * One grid clause as the clause the Contacts query asks — see the block at
73
+ * the top. A clause a query cannot serve for this reader is refused by
74
+ * name, never matched over the rows a page loaded.
75
+ */
76
+ export declare function contactQueryClause(clause: ListFilterRequest, reader: ContactQueryReader): CrmClauseAsked;
77
+ /**
78
+ * Whether an asked clause stands in for the scope clause: a facet key of
79
+ * the viewing holder, which only a record that holder keeps carries, or a
80
+ * form, which only its site's captures carry.
81
+ */
82
+ export declare function contactClauseImpliesScope(groupId: string | null): (clause: ListFilterRequest) => boolean;
83
+ /**
84
+ * The query shapes the Contacts list can send, as declarations the index
85
+ * pin enumerates (AGL-3321): the list's own, without its solo ranges, whose
86
+ * every equality rides `(field, updatedAt DESC)`; and each solo range beside
87
+ * the scope clause alone.
88
+ */
89
+ export declare function contactIndexShapes(): ListQueryDeclaration[];