@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3278.1.a4d26475e4

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 (212) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/domain_mapping/index.js +31 -0
  3. package/dist/generated/entities/domain_mapping/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +16 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/salesUi.js +18 -17
  9. package/dist/helpers/integration/salesUi.js.map +2 -2
  10. package/dist/modules/customer_accounts/acl.js +2 -1
  11. package/dist/modules/customer_accounts/acl.js.map +2 -2
  12. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
  13. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
  14. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
  15. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
  16. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
  17. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
  18. package/dist/modules/customer_accounts/api/domain-check.js +70 -0
  19. package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
  20. package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
  21. package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
  22. package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
  23. package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
  24. package/dist/modules/customer_accounts/api/login.js +14 -3
  25. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  26. package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
  27. package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
  28. package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
  29. package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
  30. package/dist/modules/customer_accounts/api/signup.js +32 -5
  31. package/dist/modules/customer_accounts/api/signup.js.map +2 -2
  32. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
  33. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
  34. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
  35. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
  36. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
  37. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
  38. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
  39. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
  40. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
  41. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
  42. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
  43. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
  44. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
  45. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
  46. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
  47. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
  48. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
  49. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
  50. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
  51. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
  52. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
  53. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
  54. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
  55. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
  56. package/dist/modules/customer_accounts/data/enrichers.js +78 -12
  57. package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
  58. package/dist/modules/customer_accounts/data/entities.js +85 -2
  59. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  60. package/dist/modules/customer_accounts/data/guards.js +130 -0
  61. package/dist/modules/customer_accounts/data/guards.js.map +7 -0
  62. package/dist/modules/customer_accounts/data/validators.js +19 -0
  63. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  64. package/dist/modules/customer_accounts/di.js +23 -1
  65. package/dist/modules/customer_accounts/di.js.map +2 -2
  66. package/dist/modules/customer_accounts/events.js +9 -2
  67. package/dist/modules/customer_accounts/events.js.map +2 -2
  68. package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
  69. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
  70. package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
  71. package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
  72. package/dist/modules/customer_accounts/lib/hostname.js +55 -0
  73. package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
  74. package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
  75. package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
  76. package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
  77. package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
  78. package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
  79. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
  80. package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
  81. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
  82. package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
  83. package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
  84. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
  85. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
  86. package/dist/modules/customer_accounts/notifications.client.js +76 -0
  87. package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +76 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
  91. package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
  92. package/dist/modules/customer_accounts/setup.js +48 -1
  93. package/dist/modules/customer_accounts/setup.js.map +2 -2
  94. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
  95. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
  96. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
  97. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
  98. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
  99. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
  100. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
  101. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
  102. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
  103. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
  104. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
  105. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
  106. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
  107. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
  108. package/dist/modules/customers/api/companies/route.js +4 -9
  109. package/dist/modules/customers/api/companies/route.js.map +2 -2
  110. package/dist/modules/customers/api/deals/route.js +4 -9
  111. package/dist/modules/customers/api/deals/route.js.map +2 -2
  112. package/dist/modules/customers/api/people/route.js +4 -9
  113. package/dist/modules/customers/api/people/route.js.map +2 -2
  114. package/dist/modules/customers/api/utils.js +0 -12
  115. package/dist/modules/customers/api/utils.js.map +2 -2
  116. package/dist/modules/customers/backend/customers/companies/page.js +361 -284
  117. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  118. package/dist/modules/customers/backend/customers/deals/page.js +378 -473
  119. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  120. package/dist/modules/customers/backend/customers/people/page.js +368 -286
  121. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  122. package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
  123. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  124. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
  125. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  126. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
  127. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
  128. package/dist/modules/customers/widgets/injection-table.js +6 -0
  129. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  130. package/dist/modules/perspectives/services/perspectiveService.js +16 -2
  131. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  132. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +1 -0
  133. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  134. package/dist/modules/query_index/lib/engine.js +31 -8
  135. package/dist/modules/query_index/lib/engine.js.map +2 -2
  136. package/generated/entities/domain_mapping/index.ts +14 -0
  137. package/generated/entities.ids.generated.ts +2 -1
  138. package/generated/entity-fields-registry.ts +16 -0
  139. package/package.json +5 -5
  140. package/src/helpers/integration/salesUi.ts +27 -23
  141. package/src/modules/customer_accounts/acl.ts +1 -0
  142. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
  143. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
  144. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
  145. package/src/modules/customer_accounts/api/domain-check.ts +83 -0
  146. package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
  147. package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
  148. package/src/modules/customer_accounts/api/login.ts +14 -3
  149. package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
  150. package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
  151. package/src/modules/customer_accounts/api/signup.ts +36 -5
  152. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
  153. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
  154. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
  155. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
  156. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
  157. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
  158. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
  159. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
  160. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
  161. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
  162. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
  163. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
  164. package/src/modules/customer_accounts/data/enrichers.ts +95 -12
  165. package/src/modules/customer_accounts/data/entities.ts +80 -1
  166. package/src/modules/customer_accounts/data/guards.ts +155 -0
  167. package/src/modules/customer_accounts/data/validators.ts +26 -0
  168. package/src/modules/customer_accounts/di.ts +35 -1
  169. package/src/modules/customer_accounts/events.ts +8 -1
  170. package/src/modules/customer_accounts/i18n/de.json +78 -0
  171. package/src/modules/customer_accounts/i18n/en.json +78 -0
  172. package/src/modules/customer_accounts/i18n/es.json +78 -0
  173. package/src/modules/customer_accounts/i18n/pl.json +78 -0
  174. package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
  175. package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
  176. package/src/modules/customer_accounts/lib/hostname.ts +76 -0
  177. package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
  178. package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
  179. package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
  180. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
  181. package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
  182. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
  183. package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
  184. package/src/modules/customer_accounts/notifications.client.ts +76 -0
  185. package/src/modules/customer_accounts/notifications.ts +76 -0
  186. package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
  187. package/src/modules/customer_accounts/setup.ts +81 -1
  188. package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
  189. package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
  190. package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
  191. package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
  192. package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
  193. package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
  194. package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
  195. package/src/modules/customers/api/companies/route.ts +4 -9
  196. package/src/modules/customers/api/deals/route.ts +4 -9
  197. package/src/modules/customers/api/people/route.ts +4 -9
  198. package/src/modules/customers/api/utils.ts +0 -14
  199. package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
  200. package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
  201. package/src/modules/customers/backend/customers/people/page.tsx +331 -240
  202. package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
  203. package/src/modules/customers/i18n/de.json +31 -0
  204. package/src/modules/customers/i18n/en.json +31 -0
  205. package/src/modules/customers/i18n/es.json +31 -0
  206. package/src/modules/customers/i18n/pl.json +31 -0
  207. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
  208. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
  209. package/src/modules/customers/widgets/injection-table.ts +6 -0
  210. package/src/modules/perspectives/services/perspectiveService.ts +30 -2
  211. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -0
  212. package/src/modules/query_index/lib/engine.ts +46 -8
@@ -2,12 +2,12 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
- import { useRouter } from "next/navigation";
5
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
6
6
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
7
  import { DataTable, withDataTableNamespaces } from "@open-mercato/ui/backend/DataTable";
8
8
  import { Button } from "@open-mercato/ui/primitives/button";
9
9
  import { RowActions } from "@open-mercato/ui/backend/RowActions";
10
- import { apiCall, apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
10
+ import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
11
11
  import { buildCrudExportUrl } from "@open-mercato/ui/backend/utils/crud";
12
12
  import { groupBulkDeleteFailures, runBulkDelete } from "@open-mercato/ui/backend/utils/bulkDelete";
13
13
  import { coalesceLastOperations } from "@open-mercato/ui/backend/operations/store";
@@ -17,7 +17,9 @@ import { E } from "../../../../../generated/entities.ids.generated.js";
17
17
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
18
18
  import { useT } from "@open-mercato/shared/lib/i18n/context";
19
19
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
20
- import { serializeAdvancedFilter } from "@open-mercato/shared/lib/query/advanced-filter";
20
+ import { createEmptyTree, makeRuleTree } from "@open-mercato/shared/lib/query/advanced-filter-tree";
21
+ import { deserializeAdvancedFilter, deserializeTree, flatToTree, mapDictionaryColorToTone, serializeTree } from "@open-mercato/shared/lib/query/advanced-filter";
22
+ import { useCurrentUserId } from "@open-mercato/ui/backend/utils/useCurrentUserId";
21
23
  import {
22
24
  DictionaryValue,
23
25
  createEmptyCustomerDictionaryMaps,
@@ -32,9 +34,50 @@ import {
32
34
  normalizeCustomFieldFilterOptions,
33
35
  supportsCustomFieldColumn
34
36
  } from "@open-mercato/ui/backend/utils/customFieldColumns";
37
+ import { useAutoDiscoveredFields } from "@open-mercato/ui/backend/utils/useAutoDiscoveredFields";
38
+ import { useAdvancedFilterTree } from "@open-mercato/ui/backend/hooks/useAdvancedFilter";
39
+ import { AdvancedFilterPanel } from "@open-mercato/ui/backend/filters/AdvancedFilterPanel";
40
+ import { ActiveFilterChips } from "@open-mercato/ui/backend/filters/ActiveFilterChips";
35
41
  import { useQueryClient } from "@tanstack/react-query";
36
42
  import { ensureCustomerDictionary } from "../../../components/detail/hooks/useCustomerDictionary.js";
43
+ import {
44
+ ensureCurrentUserFilterOption,
45
+ fetchAssignableStaffMembers,
46
+ mapAssignableStaffToFilterOptions
47
+ } from "../../../components/detail/assignableStaff.js";
37
48
  import { CollectionPreviewCell, normalizeCollectionLabels } from "../../../components/list/CollectionPreviewCell.js";
49
+ function makePeoplePresets() {
50
+ return [
51
+ {
52
+ id: "recently-active",
53
+ labelKey: "customers.people.presets.recentlyActive",
54
+ iconName: "clock",
55
+ build: ({ now }) => {
56
+ const cutoff = new Date(now.getTime() - 7 * 24 * 3600 * 1e3).toISOString().slice(0, 10);
57
+ return makeRuleTree({ field: "next_interaction_at", operator: "is_after", value: cutoff });
58
+ }
59
+ },
60
+ {
61
+ id: "my-contacts",
62
+ labelKey: "customers.people.presets.myContacts",
63
+ requiresUser: true,
64
+ build: ({ userId }) => makeRuleTree({ field: "owner_user_id", operator: "is", value: userId })
65
+ },
66
+ {
67
+ id: "hot-leads",
68
+ labelKey: "customers.people.presets.hotLeads",
69
+ build: () => makeRuleTree({ field: "lifecycle_stage", operator: "is", value: "lead" })
70
+ },
71
+ {
72
+ id: "stale-30",
73
+ labelKey: "customers.people.presets.stale30",
74
+ build: ({ now }) => {
75
+ const cutoff = new Date(now.getTime() - 30 * 24 * 3600 * 1e3).toISOString().slice(0, 10);
76
+ return makeRuleTree({ field: "next_interaction_at", operator: "is_before", value: cutoff });
77
+ }
78
+ }
79
+ ];
80
+ }
38
81
  const NO_MATCH_TAG_SENTINEL = "__no_match__";
39
82
  function formatDate(value, fallback) {
40
83
  if (!value) return fallback;
@@ -109,13 +152,37 @@ function CustomersPeoplePage() {
109
152
  const [total, setTotal] = React.useState(0);
110
153
  const [totalPages, setTotalPages] = React.useState(1);
111
154
  const [search, setSearch] = React.useState("");
112
- const [filterValues, setFilterValues] = React.useState({});
113
- const [advancedFilterState, setAdvancedFilterState] = React.useState({ logic: "and", conditions: [] });
155
+ const pathname = usePathname();
156
+ const searchParams = useSearchParams();
157
+ const initialFilterTree = React.useMemo(() => {
158
+ if (!searchParams) return createEmptyTree();
159
+ const record = {};
160
+ searchParams.forEach((value, key) => {
161
+ if (key.startsWith("filter[")) record[key] = value;
162
+ });
163
+ const v2 = deserializeTree(record);
164
+ if (v2) return v2;
165
+ const flat = deserializeAdvancedFilter(record);
166
+ if (flat) return flatToTree(flat);
167
+ return createEmptyTree();
168
+ }, []);
169
+ const [panelFields, setPanelFields] = React.useState([]);
170
+ const [filtersOpen, setFiltersOpen] = React.useState(false);
171
+ const filtersTriggerRef = React.useRef(null);
172
+ const filterPanel = useAdvancedFilterTree({
173
+ initial: initialFilterTree,
174
+ fields: panelFields,
175
+ onApply: () => setPage(1)
176
+ });
177
+ const advancedFilterState = filterPanel.appliedTree;
178
+ const handleAdvancedFilterClear = React.useCallback(() => {
179
+ filterPanel.clear();
180
+ setPage(1);
181
+ }, [filterPanel]);
114
182
  const [isLoading, setIsLoading] = React.useState(true);
115
183
  const [reloadToken, setReloadToken] = React.useState(0);
116
184
  const [cacheStatus, setCacheStatus] = React.useState(null);
117
185
  const [dictionaryMaps, setDictionaryMaps] = React.useState(createEmptyCustomerDictionaryMaps());
118
- const [tagIdToLabel, setTagIdToLabel] = React.useState({});
119
186
  const scopeVersion = useOrganizationScopeVersion();
120
187
  const queryClient = useQueryClient();
121
188
  const t = useT();
@@ -146,65 +213,19 @@ function CustomersPeoplePage() {
146
213
  return [];
147
214
  }
148
215
  }, [queryClient, scopeVersion]);
149
- const loadDictionaryOptions = React.useCallback(async (kind) => {
150
- const entries = await fetchDictionaryEntries(kind);
151
- return entries.map((entry) => ({ value: entry.value, label: entry.label }));
152
- }, [fetchDictionaryEntries]);
153
216
  const dictionaryOptions = React.useMemo(() => {
154
- const toOptions = (map) => Object.values(map ?? {}).map((entry) => ({ value: entry.value, label: entry.label })).sort((a, b) => a.label.localeCompare(b.label, void 0, { sensitivity: "base" }));
217
+ const toOptions = (map) => Object.values(map ?? {}).map((entry) => {
218
+ const tone = mapDictionaryColorToTone(entry.color);
219
+ const option = { value: entry.value, label: entry.label };
220
+ if (tone) option.tone = tone;
221
+ return option;
222
+ }).sort((a, b) => a.label.localeCompare(b.label, void 0, { sensitivity: "base" }));
155
223
  return {
156
224
  statuses: toOptions(dictionaryMaps.statuses),
157
225
  sources: toOptions(dictionaryMaps.sources),
158
226
  lifecycleStages: toOptions(dictionaryMaps["lifecycle-stages"])
159
227
  };
160
228
  }, [dictionaryMaps]);
161
- const loadTagOptions = React.useCallback(async (query) => {
162
- try {
163
- const params = new URLSearchParams({ pageSize: "100" });
164
- const trimmedQuery = typeof query === "string" ? query.trim() : "";
165
- if (trimmedQuery) params.set("search", trimmedQuery);
166
- const payload = await readApiResultOrThrow(
167
- `/api/customers/tags?${params.toString()}`,
168
- void 0,
169
- { errorMessage: t("customers.people.detail.tags.loadError", "Failed to load tags.") }
170
- );
171
- const items = Array.isArray(payload?.items) ? payload.items : [];
172
- const options = [];
173
- for (const item of items) {
174
- if (!item || typeof item !== "object") continue;
175
- const raw = item;
176
- const rawId = typeof raw.id === "string" ? raw.id : typeof raw.tagId === "string" ? raw.tagId : null;
177
- if (!rawId) continue;
178
- const label = typeof raw.label === "string" && raw.label.trim().length ? raw.label.trim() : typeof raw.slug === "string" && raw.slug.trim().length ? raw.slug.trim() : rawId;
179
- options.push({ value: rawId, label });
180
- }
181
- if (options.length) {
182
- setTagIdToLabel((prev) => {
183
- let changed = false;
184
- const next = { ...prev };
185
- for (const option of options) {
186
- if (next[option.value] !== option.label) {
187
- next[option.value] = option.label;
188
- changed = true;
189
- }
190
- }
191
- return changed ? next : prev;
192
- });
193
- }
194
- return options;
195
- } catch (err) {
196
- console.error("customers.people.list.loadTagOptions", err);
197
- return [];
198
- }
199
- }, [setTagIdToLabel, t]);
200
- const tagLabelToId = React.useMemo(() => {
201
- const map = {};
202
- for (const [id, label] of Object.entries(tagIdToLabel)) {
203
- if (!label) continue;
204
- map[label] = id;
205
- }
206
- return map;
207
- }, [tagIdToLabel]);
208
229
  React.useEffect(() => {
209
230
  let cancelled = false;
210
231
  async function loadAll() {
@@ -226,62 +247,33 @@ function CustomersPeoplePage() {
226
247
  [E.customers.customer_entity, E.customers.customer_person_profile],
227
248
  { keyExtras: [scopeVersion, reloadToken] }
228
249
  );
229
- const filters = React.useMemo(() => [
230
- {
231
- id: "status",
232
- label: t("customers.people.list.filters.status"),
233
- type: "select",
234
- options: dictionaryOptions.statuses,
235
- loadOptions: () => loadDictionaryOptions("statuses")
236
- },
237
- {
238
- id: "source",
239
- label: t("customers.people.list.filters.source"),
240
- type: "select",
241
- options: dictionaryOptions.sources,
242
- loadOptions: () => loadDictionaryOptions("sources")
243
- },
244
- {
245
- id: "lifecycleStage",
246
- label: t("customers.people.list.filters.lifecycleStage"),
247
- type: "select",
248
- options: dictionaryOptions.lifecycleStages,
249
- loadOptions: () => loadDictionaryOptions("lifecycle-stages")
250
- },
251
- {
252
- id: "tagIds",
253
- label: t("customers.people.list.filters.tags"),
254
- type: "tags",
255
- loadOptions: loadTagOptions,
256
- formatValue: (value) => tagIdToLabel[value] ?? value
257
- },
258
- {
259
- id: "createdAt",
260
- label: t("customers.people.list.filters.createdAt"),
261
- type: "dateRange"
262
- },
263
- {
264
- id: "emailContains",
265
- label: t("customers.people.list.filters.emailContains"),
266
- type: "text",
267
- placeholder: t("customers.people.list.filters.emailContainsPlaceholder")
268
- },
269
- {
270
- id: "hasEmail",
271
- label: t("customers.people.list.filters.hasEmail"),
272
- type: "checkbox"
273
- },
274
- {
275
- id: "hasPhone",
276
- label: t("customers.people.list.filters.hasPhone"),
277
- type: "checkbox"
278
- },
279
- {
280
- id: "hasNextInteraction",
281
- label: t("customers.people.list.filters.hasNextInteraction"),
282
- type: "checkbox"
283
- }
284
- ], [dictionaryOptions.lifecycleStages, dictionaryOptions.sources, dictionaryOptions.statuses, loadDictionaryOptions, loadTagOptions, tagIdToLabel, t]);
250
+ const currentUserId = useCurrentUserId();
251
+ const [ownerFilterOptions, setOwnerFilterOptions] = React.useState([]);
252
+ React.useEffect(() => {
253
+ const controller = new AbortController();
254
+ let cancelled = false;
255
+ void fetchAssignableStaffMembers("", { pageSize: 100, signal: controller.signal }).then((items) => {
256
+ if (!cancelled) setOwnerFilterOptions(mapAssignableStaffToFilterOptions(items));
257
+ }).catch(() => {
258
+ if (!cancelled) setOwnerFilterOptions([]);
259
+ });
260
+ return () => {
261
+ cancelled = true;
262
+ controller.abort();
263
+ };
264
+ }, [scopeVersion]);
265
+ const resolvedOwnerFilterOptions = React.useMemo(
266
+ () => ensureCurrentUserFilterOption(
267
+ ownerFilterOptions,
268
+ currentUserId,
269
+ t("customers.filters.currentUser", "Current user")
270
+ ),
271
+ [currentUserId, ownerFilterOptions, t]
272
+ );
273
+ const loadOwnerFilterOptions = React.useCallback(async (query) => {
274
+ const items = await fetchAssignableStaffMembers(query ?? "", { pageSize: 100 });
275
+ return mapAssignableStaffToFilterOptions(items);
276
+ }, []);
285
277
  const queryParams = React.useMemo(() => {
286
278
  const params = new URLSearchParams();
287
279
  params.set("page", String(page));
@@ -291,63 +283,32 @@ function CustomersPeoplePage() {
291
283
  params.set("order", sorting[0].desc ? "desc" : "asc");
292
284
  }
293
285
  if (search.trim()) params.set("search", search.trim());
294
- const status = filterValues.status;
295
- if (typeof status === "string" && status.trim()) params.set("status", status);
296
- const source = filterValues.source;
297
- if (typeof source === "string" && source.trim()) params.set("source", source);
298
- const lifecycleStage = filterValues.lifecycleStage;
299
- if (typeof lifecycleStage === "string" && lifecycleStage.trim()) params.set("lifecycleStage", lifecycleStage);
300
- const createdAt = filterValues.createdAt;
301
- if (createdAt && typeof createdAt === "object") {
302
- if (createdAt.from) params.set("createdFrom", createdAt.from);
303
- if (createdAt.to) params.set("createdTo", createdAt.to);
304
- }
305
- const emailContains = filterValues.emailContains;
306
- if (typeof emailContains === "string" && emailContains.trim()) {
307
- params.set("emailContains", emailContains.trim());
308
- }
309
- const booleanFilters = [
310
- ["hasEmail", "hasEmail"],
311
- ["hasPhone", "hasPhone"],
312
- ["hasNextInteraction", "hasNextInteraction"]
313
- ];
314
- for (const [key, queryKey] of booleanFilters) {
315
- const value = filterValues[key];
316
- if (value === true) params.set(queryKey, "true");
317
- if (value === false) params.set(queryKey, "false");
318
- }
319
- const tagValues = Array.isArray(filterValues.tagIds) ? filterValues.tagIds.map((value) => typeof value === "string" ? value.trim() : String(value || "").trim()).filter((value) => value.length > 0) : [];
320
- if (tagValues.length > 0) {
321
- const normalizedTagIds = tagValues.map((value) => typeof tagIdToLabel[value] === "string" ? value : tagLabelToId[value]).filter((id) => typeof id === "string" && id.length > 0);
322
- if (normalizedTagIds.length === tagValues.length && normalizedTagIds.length > 0) {
323
- params.set("tagIds", normalizedTagIds.join(","));
324
- } else {
325
- params.set("tagIdsEmpty", "true");
326
- }
327
- }
328
- Object.entries(filterValues).forEach(([key, value]) => {
329
- if (!key.startsWith("cf_") || value == null) return;
330
- if (Array.isArray(value)) {
331
- const normalized = value.map((item) => {
332
- if (item == null) return "";
333
- if (typeof item === "string") return item.trim();
334
- return String(item).trim();
335
- }).filter((item) => item.length > 0);
336
- if (normalized.length) params.set(key, normalized.join(","));
337
- } else if (typeof value === "object") {
338
- return;
339
- } else if (value !== "") {
340
- const stringValue = typeof value === "string" ? value.trim() : String(value);
341
- if (stringValue) params.set(key, stringValue);
342
- }
343
- });
344
- const advancedParams = serializeAdvancedFilter(advancedFilterState);
286
+ const advancedParams = serializeTree(advancedFilterState);
345
287
  for (const [key, val] of Object.entries(advancedParams)) {
346
288
  params.set(key, val);
347
289
  }
348
290
  return params.toString();
349
- }, [advancedFilterState, filterValues, page, pageSize, search, sorting, tagIdToLabel, tagLabelToId]);
291
+ }, [advancedFilterState, page, pageSize, search, sorting]);
350
292
  const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams]);
293
+ const queryRef = React.useRef(searchParams?.toString() ?? "");
294
+ React.useEffect(() => {
295
+ if (!pathname) return;
296
+ const params = new URLSearchParams();
297
+ if (search.trim().length) params.set("search", search.trim());
298
+ if (page > 1) params.set("page", String(page));
299
+ if (sorting.length > 0) {
300
+ params.set("sort", sorting[0].id);
301
+ params.set("order", sorting[0].desc ? "desc" : "asc");
302
+ }
303
+ const advancedParams = serializeTree(advancedFilterState);
304
+ for (const [key, val] of Object.entries(advancedParams)) {
305
+ params.set(key, val);
306
+ }
307
+ const next = params.toString();
308
+ if (queryRef.current === next) return;
309
+ queryRef.current = next;
310
+ router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false });
311
+ }, [pathname, router, page, search, sorting, advancedFilterState]);
351
312
  const exportConfig = React.useMemo(() => ({
352
313
  view: {
353
314
  getUrl: (format) => buildCrudExportUrl("customers/people", { ...currentParams, exportScope: "view" }, format)
@@ -497,28 +458,6 @@ function CustomersPeoplePage() {
497
458
  }
498
459
  return succeeded.length > 0;
499
460
  }, [bulkMutationContextId, confirm, retryBulkMutation, runBulkMutation, t]);
500
- const handleFiltersApply = React.useCallback((values) => {
501
- const next = {};
502
- Object.entries(values).forEach(([key, value]) => {
503
- if (value !== void 0) {
504
- next[key] = value;
505
- }
506
- });
507
- const rawTags = Array.isArray(values.tagIds) ? values.tagIds : [];
508
- const sanitizedTags = rawTags.map((tag) => {
509
- const normalized = typeof tag === "string" ? tag.trim() : "";
510
- if (!normalized) return "";
511
- return tagIdToLabel[normalized] ?? normalized;
512
- }).filter((tag) => tag.length > 0);
513
- if (sanitizedTags.length) next.tagIds = sanitizedTags;
514
- else delete next.tagIds;
515
- setFilterValues(next);
516
- setPage(1);
517
- }, [setFilterValues, setPage, tagIdToLabel]);
518
- const handleFiltersClear = React.useCallback(() => {
519
- setFilterValues({});
520
- setPage(1);
521
- }, [setFilterValues, setPage]);
522
461
  const columns = React.useMemo(() => {
523
462
  const noValue = /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: t("customers.people.list.noValue") });
524
463
  const renderDictionaryCell = (kind, rawValue) => /* @__PURE__ */ jsx(
@@ -558,19 +497,36 @@ function CustomersPeoplePage() {
558
497
  {
559
498
  accessorKey: "name",
560
499
  header: t("customers.people.list.columns.name"),
561
- meta: { alwaysVisible: true, columnChooserGroup: "Basic Info", filterKey: "display_name", maxWidth: "240px" },
500
+ meta: {
501
+ alwaysVisible: true,
502
+ columnChooserGroup: "Basic Info",
503
+ filterKey: "display_name",
504
+ filterGroup: "CRM",
505
+ maxWidth: "240px"
506
+ },
562
507
  cell: ({ row }) => /* @__PURE__ */ jsx(Link, { href: `/backend/customers/people-v2/${row.original.id}`, className: "font-medium hover:underline", children: row.original.name })
563
508
  },
564
509
  {
565
510
  accessorKey: "email",
566
511
  header: t("customers.people.list.columns.email"),
567
- meta: { columnChooserGroup: "Contact", filterKey: "primary_email", maxWidth: "220px" },
512
+ meta: {
513
+ columnChooserGroup: "Contact",
514
+ filterKey: "primary_email",
515
+ filterGroup: "Contact",
516
+ filterIconName: "mail",
517
+ maxWidth: "220px"
518
+ },
568
519
  cell: ({ row }) => row.original.email || /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: t("customers.people.list.noValue") })
569
520
  },
570
521
  {
571
522
  accessorKey: "status",
572
523
  header: t("customers.people.list.columns.status"),
573
- meta: { filterType: "select", filterOptions: dictionaryOptions.statuses, columnChooserGroup: "Basic Info" },
524
+ meta: {
525
+ filterType: "select",
526
+ filterOptions: dictionaryOptions.statuses,
527
+ columnChooserGroup: "Basic Info",
528
+ filterGroup: "CRM"
529
+ },
574
530
  cell: ({ row }) => renderDictionaryCell("statuses", row.original.status)
575
531
  },
576
532
  {
@@ -580,7 +536,8 @@ function CustomersPeoplePage() {
580
536
  filterType: "select",
581
537
  filterOptions: dictionaryOptions.lifecycleStages,
582
538
  columnChooserGroup: "Basic Info",
583
- filterKey: "lifecycle_stage"
539
+ filterKey: "lifecycle_stage",
540
+ filterGroup: "CRM"
584
541
  },
585
542
  cell: ({ row }) => renderDictionaryCell("lifecycle-stages", row.original.lifecycleStage)
586
543
  },
@@ -590,6 +547,8 @@ function CustomersPeoplePage() {
590
547
  meta: {
591
548
  columnChooserGroup: "Dates",
592
549
  filterKey: "next_interaction_at",
550
+ filterGroup: "Activity",
551
+ filterIconName: "calendar",
593
552
  tooltipContent: (row) => {
594
553
  if (!row.nextInteractionAt) return void 0;
595
554
  const date = formatDate(row.nextInteractionAt, "");
@@ -609,67 +568,137 @@ function CustomersPeoplePage() {
609
568
  {
610
569
  accessorKey: "source",
611
570
  header: t("customers.people.list.columns.source"),
612
- meta: { filterType: "select", filterOptions: dictionaryOptions.sources, columnChooserGroup: "Basic Info" },
571
+ meta: {
572
+ filterType: "select",
573
+ filterOptions: dictionaryOptions.sources,
574
+ columnChooserGroup: "Basic Info",
575
+ filterGroup: "CRM"
576
+ },
613
577
  cell: ({ row }) => renderDictionaryCell("sources", row.original.source)
614
578
  },
579
+ {
580
+ accessorKey: "ownerUserId",
581
+ header: t("customers.people.list.columns.owner", "Owner"),
582
+ meta: {
583
+ columnChooserGroup: "CRM",
584
+ filterType: "select",
585
+ filterOptions: resolvedOwnerFilterOptions,
586
+ filterLoadOptions: loadOwnerFilterOptions,
587
+ filterGroup: "CRM",
588
+ filterIconName: "user-round",
589
+ filterKey: "owner_user_id",
590
+ hidden: true
591
+ },
592
+ cell: ({ row }) => row.original.ownerUserId ?? null
593
+ },
615
594
  {
616
595
  accessorKey: "firstName",
617
596
  header: t("customers.people.form.firstName", "First name"),
618
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.first_name" },
597
+ meta: {
598
+ columnChooserGroup: "Profile",
599
+ hidden: true,
600
+ filterKey: "person_profile.first_name",
601
+ filterGroup: "Profile"
602
+ },
619
603
  cell: ({ row }) => row.original.firstName || noValue
620
604
  },
621
605
  {
622
606
  accessorKey: "lastName",
623
607
  header: t("customers.people.form.lastName", "Last name"),
624
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.last_name" },
608
+ meta: {
609
+ columnChooserGroup: "Profile",
610
+ hidden: true,
611
+ filterKey: "person_profile.last_name",
612
+ filterGroup: "Profile"
613
+ },
625
614
  cell: ({ row }) => row.original.lastName || noValue
626
615
  },
627
616
  {
628
617
  accessorKey: "preferredName",
629
618
  header: t("customers.people.form.preferredName", "Preferred name"),
630
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.preferred_name" },
619
+ meta: {
620
+ columnChooserGroup: "Profile",
621
+ hidden: true,
622
+ filterKey: "person_profile.preferred_name",
623
+ filterGroup: "Profile"
624
+ },
631
625
  cell: ({ row }) => row.original.preferredName || noValue
632
626
  },
633
627
  {
634
628
  accessorKey: "jobTitle",
635
629
  header: t("customers.people.form.jobTitle", "Job title"),
636
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.job_title" },
630
+ meta: {
631
+ columnChooserGroup: "Profile",
632
+ hidden: true,
633
+ filterKey: "person_profile.job_title",
634
+ filterGroup: "Profile"
635
+ },
637
636
  cell: ({ row }) => row.original.jobTitle || noValue
638
637
  },
639
638
  {
640
639
  accessorKey: "department",
641
640
  header: t("customers.people.detail.fields.department", "Department"),
642
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.department" },
641
+ meta: {
642
+ columnChooserGroup: "Profile",
643
+ hidden: true,
644
+ filterKey: "person_profile.department",
645
+ filterGroup: "Profile"
646
+ },
643
647
  cell: ({ row }) => row.original.department || noValue
644
648
  },
645
649
  {
646
650
  accessorKey: "seniority",
647
651
  header: t("customers.people.detail.fields.seniority", "Seniority"),
648
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.seniority" },
652
+ meta: {
653
+ columnChooserGroup: "Profile",
654
+ hidden: true,
655
+ filterKey: "person_profile.seniority",
656
+ filterGroup: "Profile"
657
+ },
649
658
  cell: ({ row }) => row.original.seniority || noValue
650
659
  },
651
660
  {
652
661
  accessorKey: "timezone",
653
662
  header: t("customers.people.detail.fields.timezone", "Timezone"),
654
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "person_profile.timezone" },
663
+ meta: {
664
+ columnChooserGroup: "Profile",
665
+ hidden: true,
666
+ filterKey: "person_profile.timezone",
667
+ filterGroup: "Profile"
668
+ },
655
669
  cell: ({ row }) => row.original.timezone || noValue
656
670
  },
657
671
  {
658
672
  accessorKey: "linkedInUrl",
659
673
  header: t("customers.people.detail.fields.linkedIn", "LinkedIn"),
660
- meta: { columnChooserGroup: "Socials", hidden: true, filterKey: "person_profile.linked_in_url" },
674
+ meta: {
675
+ columnChooserGroup: "Socials",
676
+ hidden: true,
677
+ filterKey: "person_profile.linked_in_url",
678
+ filterGroup: "Socials"
679
+ },
661
680
  cell: ({ row }) => row.original.linkedInUrl || noValue
662
681
  },
663
682
  {
664
683
  accessorKey: "twitterUrl",
665
684
  header: t("customers.people.detail.fields.twitter", "Twitter"),
666
- meta: { columnChooserGroup: "Socials", hidden: true, filterKey: "person_profile.twitter_url" },
685
+ meta: {
686
+ columnChooserGroup: "Socials",
687
+ hidden: true,
688
+ filterKey: "person_profile.twitter_url",
689
+ filterGroup: "Socials"
690
+ },
667
691
  cell: ({ row }) => row.original.twitterUrl || noValue
668
692
  },
669
693
  {
670
694
  accessorKey: "description",
671
695
  header: t("customers.people.form.description", "Description"),
672
- meta: { columnChooserGroup: "Notes", hidden: true, filterKey: "description" },
696
+ meta: {
697
+ columnChooserGroup: "Notes",
698
+ hidden: true,
699
+ filterKey: "description",
700
+ filterGroup: "Notes"
701
+ },
673
702
  cell: ({ row }) => row.original.description || noValue
674
703
  }
675
704
  ];
@@ -687,93 +716,146 @@ function CustomersPeoplePage() {
687
716
  cell: ({ getValue }) => renderCustomFieldCell(getValue())
688
717
  }));
689
718
  return [...baseColumns, ...customColumns];
690
- }, [customFieldDefs, dictionaryMaps, dictionaryOptions, t]);
719
+ }, [customFieldDefs, dictionaryMaps, dictionaryOptions, loadOwnerFilterOptions, resolvedOwnerFilterOptions, t]);
720
+ const { advancedFilterFields } = useAutoDiscoveredFields({ columns, customFieldDefs });
721
+ React.useEffect(() => {
722
+ setPanelFields((prev) => {
723
+ if (prev === advancedFilterFields) return prev;
724
+ if (prev.length === advancedFilterFields.length) {
725
+ let same = true;
726
+ for (let i = 0; i < prev.length; i++) {
727
+ if (prev[i].key !== advancedFilterFields[i].key) {
728
+ same = false;
729
+ break;
730
+ }
731
+ }
732
+ if (same) return prev;
733
+ }
734
+ return advancedFilterFields;
735
+ });
736
+ }, [advancedFilterFields]);
737
+ const peoplePresets = React.useMemo(() => makePeoplePresets(), []);
691
738
  return /* @__PURE__ */ jsxs(Page, { children: [
692
- /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
693
- DataTable,
694
- {
695
- stickyFirstColumn: true,
696
- stickyActionsColumn: true,
697
- title: t("customers.people.list.title"),
698
- refreshButton: {
699
- label: t("customers.people.list.actions.refresh"),
700
- onRefresh: () => {
701
- setSearch("");
739
+ /* @__PURE__ */ jsxs(PageBody, { children: [
740
+ /* @__PURE__ */ jsx(
741
+ DataTable,
742
+ {
743
+ stickyFirstColumn: true,
744
+ stickyActionsColumn: true,
745
+ title: t("customers.people.list.title"),
746
+ refreshButton: {
747
+ label: t("customers.people.list.actions.refresh"),
748
+ onRefresh: () => {
749
+ setSearch("");
750
+ setPage(1);
751
+ handleRefresh();
752
+ }
753
+ },
754
+ actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/people/create", children: t("customers.people.list.actions.new") }) }),
755
+ columns,
756
+ columnChooser: { auto: true },
757
+ data: rows,
758
+ exporter: exportConfig,
759
+ searchValue: search,
760
+ onSearchChange: (value) => {
761
+ setSearch(value);
702
762
  setPage(1);
703
- handleRefresh();
704
- }
705
- },
706
- actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/people/create", children: t("customers.people.list.actions.new") }) }),
707
- columns,
708
- columnChooser: { auto: true },
709
- data: rows,
710
- exporter: exportConfig,
711
- searchValue: search,
712
- onSearchChange: (value) => {
713
- setSearch(value);
714
- setPage(1);
715
- },
716
- searchPlaceholder: t("customers.people.list.searchPlaceholder"),
717
- filters,
718
- filterValues,
719
- onFiltersApply: handleFiltersApply,
720
- onFiltersClear: handleFiltersClear,
721
- entityIds: [E.customers.customer_entity, E.customers.customer_person_profile],
722
- perspective: { tableId: "customers.people.list" },
723
- onRowClick: (row) => router.push(`/backend/customers/people-v2/${row.id}`),
724
- sortable: true,
725
- sorting,
726
- onSortingChange: setSorting,
727
- bulkActions: [
728
- {
729
- id: "delete",
730
- label: t("customers.people.list.bulkDelete.action", "Delete selected"),
731
- destructive: true,
732
- onExecute: handleBulkDelete
733
- }
734
- ],
735
- rowActions: (row) => /* @__PURE__ */ jsx(
736
- RowActions,
737
- {
738
- items: [
739
- {
740
- id: "view",
741
- label: t("customers.people.list.actions.view"),
742
- onSelect: () => {
743
- router.push(`/backend/customers/people-v2/${row.id}`);
763
+ },
764
+ searchPlaceholder: t("customers.people.list.searchPlaceholder"),
765
+ entityIds: [E.customers.customer_entity, E.customers.customer_person_profile],
766
+ perspective: { tableId: "customers.people.list" },
767
+ onRowClick: (row) => router.push(`/backend/customers/people-v2/${row.id}`),
768
+ sortable: true,
769
+ sorting,
770
+ onSortingChange: setSorting,
771
+ bulkActions: [
772
+ {
773
+ id: "delete",
774
+ label: t("customers.people.list.bulkDelete.action", "Delete selected"),
775
+ destructive: true,
776
+ onExecute: handleBulkDelete
777
+ }
778
+ ],
779
+ rowActions: (row) => /* @__PURE__ */ jsx(
780
+ RowActions,
781
+ {
782
+ items: [
783
+ {
784
+ id: "view",
785
+ label: t("customers.people.list.actions.view"),
786
+ onSelect: () => {
787
+ router.push(`/backend/customers/people-v2/${row.id}`);
788
+ }
789
+ },
790
+ {
791
+ id: "open-new-tab",
792
+ label: t("customers.people.list.actions.openInNewTab"),
793
+ onSelect: () => window.open(`/backend/customers/people-v2/${row.id}`, "_blank", "noopener")
794
+ },
795
+ {
796
+ id: "delete",
797
+ label: t("customers.people.list.actions.delete"),
798
+ destructive: true,
799
+ onSelect: () => handleDelete(row)
744
800
  }
745
- },
746
- {
747
- id: "open-new-tab",
748
- label: t("customers.people.list.actions.openInNewTab"),
749
- onSelect: () => window.open(`/backend/customers/people-v2/${row.id}`, "_blank", "noopener")
750
- },
751
- {
752
- id: "delete",
753
- label: t("customers.people.list.actions.delete"),
754
- destructive: true,
755
- onSelect: () => handleDelete(row)
756
- }
757
- ]
758
- }
759
- ),
760
- advancedFilter: {
761
- auto: true,
762
- value: advancedFilterState,
763
- onChange: setAdvancedFilterState,
764
- onApply: () => {
765
- setPage(1);
801
+ ]
802
+ }
803
+ ),
804
+ advancedFilter: {
805
+ auto: true,
806
+ value: filterPanel.tree,
807
+ onChange: filterPanel.setTree,
808
+ onApply: () => filterPanel.flush(),
809
+ onClear: handleAdvancedFilterClear,
810
+ triggerRef: filtersTriggerRef,
811
+ externalPopover: true,
812
+ onTriggerClick: () => setFiltersOpen((prev) => !prev),
813
+ onApplyTree: (tree) => {
814
+ filterPanel.replaceTree(tree);
815
+ setPage(1);
816
+ }
766
817
  },
767
- onClear: () => {
768
- setAdvancedFilterState({ logic: "and", conditions: [] });
769
- setPage(1);
770
- }
771
- },
772
- virtualized: true,
773
- pagination: { page, pageSize, total, totalPages, onPageChange: setPage, cacheStatus, pageSizeOptions: [10, 25, 50, 100], onPageSizeChange: handlePageSizeChange },
774
- isLoading
775
- }
776
- ) }),
818
+ activeFilterChips: /* @__PURE__ */ jsx(
819
+ ActiveFilterChips,
820
+ {
821
+ tree: filterPanel.tree,
822
+ fields: advancedFilterFields,
823
+ popoverOpen: filtersOpen,
824
+ onRemoveNode: (id) => filterPanel.dispatch({ type: "removeNode", nodeId: id }),
825
+ onOpen: () => setFiltersOpen(true)
826
+ }
827
+ ),
828
+ filterAwareEmptyState: {
829
+ active: advancedFilterState.root.children.length > 0,
830
+ entityNamePlural: t("customers.people.entityPlural", "people"),
831
+ canRemoveLast: filterPanel.tree.root.children.length > 0,
832
+ onClearAll: handleAdvancedFilterClear,
833
+ onRemoveLast: () => filterPanel.dispatch({ type: "removeLast" })
834
+ },
835
+ virtualized: true,
836
+ pagination: { page, pageSize, total, totalPages, onPageChange: setPage, cacheStatus, pageSizeOptions: [10, 25, 50, 100], onPageSizeChange: handlePageSizeChange },
837
+ isLoading
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx(
841
+ AdvancedFilterPanel,
842
+ {
843
+ fields: advancedFilterFields,
844
+ value: filterPanel.tree,
845
+ onChange: filterPanel.setTree,
846
+ onApply: filterPanel.flush,
847
+ onClear: handleAdvancedFilterClear,
848
+ onFlush: filterPanel.flush,
849
+ pendingErrors: filterPanel.pendingErrors,
850
+ userId: currentUserId,
851
+ presets: peoplePresets,
852
+ open: filtersOpen,
853
+ onOpenChange: setFiltersOpen,
854
+ triggerRef: filtersTriggerRef,
855
+ savedFilterStorageKey: "customers.people.list"
856
+ }
857
+ )
858
+ ] }),
777
859
  ConfirmDialogElement
778
860
  ] });
779
861
  }