@open-mercato/core 0.6.1-develop.3260.1.dc719cac84 → 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,45 @@ 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 makeCompaniesPresets() {
50
+ return [
51
+ {
52
+ id: "my-accounts",
53
+ labelKey: "customers.companies.presets.myAccounts",
54
+ requiresUser: true,
55
+ build: ({ userId }) => makeRuleTree({ field: "owner_user_id", operator: "is", value: userId })
56
+ },
57
+ {
58
+ id: "recently-created",
59
+ labelKey: "customers.companies.presets.recentlyCreated",
60
+ iconName: "clock",
61
+ build: ({ now }) => {
62
+ const cutoff = new Date(now.getTime() - 7 * 24 * 3600 * 1e3).toISOString().slice(0, 10);
63
+ return makeRuleTree({ field: "created_at", operator: "is_after", value: cutoff });
64
+ }
65
+ },
66
+ {
67
+ id: "inactive-60",
68
+ labelKey: "customers.companies.presets.inactive60",
69
+ build: ({ now }) => {
70
+ const cutoff = new Date(now.getTime() - 60 * 24 * 3600 * 1e3).toISOString().slice(0, 10);
71
+ return makeRuleTree({ field: "next_interaction_at", operator: "is_before", value: cutoff });
72
+ }
73
+ }
74
+ ];
75
+ }
38
76
  function formatDate(value, fallback) {
39
77
  if (!value) return fallback;
40
78
  const date = new Date(value);
@@ -63,6 +101,7 @@ function mapApiItem(item) {
63
101
  const nextInteractionColor = typeof item.next_interaction_color === "string" ? item.next_interaction_color : null;
64
102
  const organizationId = typeof item.organization_id === "string" ? item.organization_id : null;
65
103
  const source = typeof item.source === "string" ? item.source : null;
104
+ const ownerUserId = typeof item.owner_user_id === "string" ? item.owner_user_id : null;
66
105
  const customFields = {};
67
106
  for (const [key, value] of Object.entries(item)) {
68
107
  if (key.startsWith("cf_")) {
@@ -90,6 +129,7 @@ function mapApiItem(item) {
90
129
  nextInteractionColor,
91
130
  organizationId,
92
131
  source,
132
+ ownerUserId,
93
133
  ...customFields
94
134
  }, item);
95
135
  }
@@ -102,13 +142,37 @@ function CustomersCompaniesPage() {
102
142
  const [total, setTotal] = React.useState(0);
103
143
  const [totalPages, setTotalPages] = React.useState(1);
104
144
  const [search, setSearch] = React.useState("");
105
- const [filterValues, setFilterValues] = React.useState({});
106
- const [advancedFilterState, setAdvancedFilterState] = React.useState({ logic: "and", conditions: [] });
145
+ const pathname = usePathname();
146
+ const searchParams = useSearchParams();
147
+ const initialFilterTree = React.useMemo(() => {
148
+ if (!searchParams) return createEmptyTree();
149
+ const record = {};
150
+ searchParams.forEach((value, key) => {
151
+ if (key.startsWith("filter[")) record[key] = value;
152
+ });
153
+ const v2 = deserializeTree(record);
154
+ if (v2) return v2;
155
+ const flat = deserializeAdvancedFilter(record);
156
+ if (flat) return flatToTree(flat);
157
+ return createEmptyTree();
158
+ }, []);
159
+ const [panelFields, setPanelFields] = React.useState([]);
160
+ const [filtersOpen, setFiltersOpen] = React.useState(false);
161
+ const filtersTriggerRef = React.useRef(null);
162
+ const filterPanel = useAdvancedFilterTree({
163
+ initial: initialFilterTree,
164
+ fields: panelFields,
165
+ onApply: () => setPage(1)
166
+ });
167
+ const advancedFilterState = filterPanel.appliedTree;
168
+ const handleAdvancedFilterClear = React.useCallback(() => {
169
+ filterPanel.clear();
170
+ setPage(1);
171
+ }, [filterPanel]);
107
172
  const [isLoading, setIsLoading] = React.useState(true);
108
173
  const [reloadToken, setReloadToken] = React.useState(0);
109
174
  const [cacheStatus, setCacheStatus] = React.useState(null);
110
175
  const [dictionaryMaps, setDictionaryMaps] = React.useState(createEmptyCustomerDictionaryMaps());
111
- const [tagIdToLabel, setTagIdToLabel] = React.useState({});
112
176
  const scopeVersion = useOrganizationScopeVersion();
113
177
  const queryClient = useQueryClient();
114
178
  const t = useT();
@@ -139,65 +203,19 @@ function CustomersCompaniesPage() {
139
203
  return [];
140
204
  }
141
205
  }, [queryClient, scopeVersion]);
142
- const loadDictionaryOptions = React.useCallback(async (kind) => {
143
- const entries = await fetchDictionaryEntries(kind);
144
- return entries.map((entry) => ({ value: entry.value, label: entry.label }));
145
- }, [fetchDictionaryEntries]);
146
206
  const dictionaryOptions = React.useMemo(() => {
147
- 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" }));
207
+ const toOptions = (map) => Object.values(map ?? {}).map((entry) => {
208
+ const tone = mapDictionaryColorToTone(entry.color);
209
+ const option = { value: entry.value, label: entry.label };
210
+ if (tone) option.tone = tone;
211
+ return option;
212
+ }).sort((a, b) => a.label.localeCompare(b.label, void 0, { sensitivity: "base" }));
148
213
  return {
149
214
  statuses: toOptions(dictionaryMaps.statuses),
150
215
  sources: toOptions(dictionaryMaps.sources),
151
216
  lifecycleStages: toOptions(dictionaryMaps["lifecycle-stages"])
152
217
  };
153
218
  }, [dictionaryMaps]);
154
- const loadTagOptions = React.useCallback(async (query) => {
155
- try {
156
- const params = new URLSearchParams({ pageSize: "100" });
157
- const trimmedQuery = typeof query === "string" ? query.trim() : "";
158
- if (trimmedQuery) params.set("search", trimmedQuery);
159
- const payload = await readApiResultOrThrow(
160
- `/api/customers/tags?${params.toString()}`,
161
- void 0,
162
- { errorMessage: t("customers.companies.list.tags.loadError", "Failed to load tags.") }
163
- );
164
- const items = Array.isArray(payload?.items) ? payload.items : [];
165
- const options = [];
166
- for (const item of items) {
167
- if (!item || typeof item !== "object") continue;
168
- const raw = item;
169
- const rawId = typeof raw.id === "string" ? raw.id : typeof raw.tagId === "string" ? raw.tagId : null;
170
- if (!rawId) continue;
171
- 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;
172
- options.push({ value: rawId, label });
173
- }
174
- if (options.length) {
175
- setTagIdToLabel((prev) => {
176
- let changed = false;
177
- const next = { ...prev };
178
- for (const option of options) {
179
- if (next[option.value] !== option.label) {
180
- next[option.value] = option.label;
181
- changed = true;
182
- }
183
- }
184
- return changed ? next : prev;
185
- });
186
- }
187
- return options;
188
- } catch (err) {
189
- console.error("customers.companies.list.loadTagOptions", err);
190
- return [];
191
- }
192
- }, [setTagIdToLabel, t]);
193
- const tagLabelToId = React.useMemo(() => {
194
- const map = {};
195
- for (const [id, label] of Object.entries(tagIdToLabel)) {
196
- if (!label) continue;
197
- map[label] = id;
198
- }
199
- return map;
200
- }, [tagIdToLabel]);
201
219
  React.useEffect(() => {
202
220
  let cancelled = false;
203
221
  async function loadAll() {
@@ -219,62 +237,33 @@ function CustomersCompaniesPage() {
219
237
  [E.customers.customer_entity, E.customers.customer_company_profile],
220
238
  { keyExtras: [scopeVersion, reloadToken] }
221
239
  );
222
- const filters = React.useMemo(() => [
223
- {
224
- id: "status",
225
- label: t("customers.companies.list.filters.status"),
226
- type: "select",
227
- options: dictionaryOptions.statuses,
228
- loadOptions: () => loadDictionaryOptions("statuses")
229
- },
230
- {
231
- id: "source",
232
- label: t("customers.companies.list.filters.source"),
233
- type: "select",
234
- options: dictionaryOptions.sources,
235
- loadOptions: () => loadDictionaryOptions("sources")
236
- },
237
- {
238
- id: "lifecycleStage",
239
- label: t("customers.companies.list.filters.lifecycleStage"),
240
- type: "select",
241
- options: dictionaryOptions.lifecycleStages,
242
- loadOptions: () => loadDictionaryOptions("lifecycle-stages")
243
- },
244
- {
245
- id: "tagIds",
246
- label: t("customers.companies.list.filters.tags"),
247
- type: "tags",
248
- loadOptions: loadTagOptions,
249
- formatValue: (value) => tagIdToLabel[value] ?? value
250
- },
251
- {
252
- id: "createdAt",
253
- label: t("customers.companies.list.filters.createdAt"),
254
- type: "dateRange"
255
- },
256
- {
257
- id: "emailContains",
258
- label: t("customers.companies.list.filters.emailContains"),
259
- type: "text",
260
- placeholder: t("customers.companies.list.filters.emailContainsPlaceholder")
261
- },
262
- {
263
- id: "hasEmail",
264
- label: t("customers.companies.list.filters.hasEmail"),
265
- type: "checkbox"
266
- },
267
- {
268
- id: "hasPhone",
269
- label: t("customers.companies.list.filters.hasPhone"),
270
- type: "checkbox"
271
- },
272
- {
273
- id: "hasNextInteraction",
274
- label: t("customers.companies.list.filters.hasNextInteraction"),
275
- type: "checkbox"
276
- }
277
- ], [dictionaryOptions.lifecycleStages, dictionaryOptions.sources, dictionaryOptions.statuses, loadDictionaryOptions, loadTagOptions, tagIdToLabel, t]);
240
+ const currentUserId = useCurrentUserId();
241
+ const [ownerFilterOptions, setOwnerFilterOptions] = React.useState([]);
242
+ React.useEffect(() => {
243
+ const controller = new AbortController();
244
+ let cancelled = false;
245
+ void fetchAssignableStaffMembers("", { pageSize: 100, signal: controller.signal }).then((items) => {
246
+ if (!cancelled) setOwnerFilterOptions(mapAssignableStaffToFilterOptions(items));
247
+ }).catch(() => {
248
+ if (!cancelled) setOwnerFilterOptions([]);
249
+ });
250
+ return () => {
251
+ cancelled = true;
252
+ controller.abort();
253
+ };
254
+ }, [scopeVersion]);
255
+ const resolvedOwnerFilterOptions = React.useMemo(
256
+ () => ensureCurrentUserFilterOption(
257
+ ownerFilterOptions,
258
+ currentUserId,
259
+ t("customers.filters.currentUser", "Current user")
260
+ ),
261
+ [currentUserId, ownerFilterOptions, t]
262
+ );
263
+ const loadOwnerFilterOptions = React.useCallback(async (query) => {
264
+ const items = await fetchAssignableStaffMembers(query ?? "", { pageSize: 100 });
265
+ return mapAssignableStaffToFilterOptions(items);
266
+ }, []);
278
267
  const queryParams = React.useMemo(() => {
279
268
  const params = new URLSearchParams();
280
269
  params.set("page", String(page));
@@ -284,63 +273,32 @@ function CustomersCompaniesPage() {
284
273
  params.set("order", sorting[0].desc ? "desc" : "asc");
285
274
  }
286
275
  if (search.trim()) params.set("search", search.trim());
287
- const status = filterValues.status;
288
- if (typeof status === "string" && status.trim()) params.set("status", status);
289
- const source = filterValues.source;
290
- if (typeof source === "string" && source.trim()) params.set("source", source);
291
- const lifecycleStage = filterValues.lifecycleStage;
292
- if (typeof lifecycleStage === "string" && lifecycleStage.trim()) params.set("lifecycleStage", lifecycleStage);
293
- const createdAt = filterValues.createdAt;
294
- if (createdAt && typeof createdAt === "object") {
295
- if (createdAt.from) params.set("createdFrom", createdAt.from);
296
- if (createdAt.to) params.set("createdTo", createdAt.to);
297
- }
298
- const emailContains = filterValues.emailContains;
299
- if (typeof emailContains === "string" && emailContains.trim()) {
300
- params.set("emailContains", emailContains.trim());
301
- }
302
- const tagValues = Array.isArray(filterValues.tagIds) ? filterValues.tagIds.map((value) => typeof value === "string" ? value.trim() : String(value || "").trim()).filter((value) => value.length > 0) : [];
303
- if (tagValues.length > 0) {
304
- const normalizedTagIds = tagValues.map((value) => typeof tagIdToLabel[value] === "string" ? value : tagLabelToId[value]).filter((id) => typeof id === "string" && id.length > 0);
305
- if (normalizedTagIds.length === tagValues.length && normalizedTagIds.length > 0) {
306
- params.set("tagIds", normalizedTagIds.join(","));
307
- } else {
308
- params.set("tagIdsEmpty", "true");
309
- }
310
- }
311
- const booleanFilters = [
312
- ["hasEmail", "hasEmail"],
313
- ["hasPhone", "hasPhone"],
314
- ["hasNextInteraction", "hasNextInteraction"]
315
- ];
316
- for (const [key, queryKey] of booleanFilters) {
317
- const value = filterValues[key];
318
- if (value === true) params.set(queryKey, "true");
319
- if (value === false) params.set(queryKey, "false");
320
- }
321
- Object.entries(filterValues).forEach(([key, value]) => {
322
- if (!key.startsWith("cf_") || value == null) return;
323
- if (Array.isArray(value)) {
324
- const normalized = value.map((item) => {
325
- if (item == null) return "";
326
- if (typeof item === "string") return item.trim();
327
- return String(item).trim();
328
- }).filter((item) => item.length > 0);
329
- if (normalized.length) params.set(key, normalized.join(","));
330
- } else if (typeof value === "object") {
331
- return;
332
- } else if (value !== "") {
333
- const stringValue = typeof value === "string" ? value.trim() : String(value);
334
- if (stringValue) params.set(key, stringValue);
335
- }
336
- });
337
- const advancedParams = serializeAdvancedFilter(advancedFilterState);
276
+ const advancedParams = serializeTree(advancedFilterState);
338
277
  for (const [key, val] of Object.entries(advancedParams)) {
339
278
  params.set(key, val);
340
279
  }
341
280
  return params.toString();
342
- }, [advancedFilterState, filterValues, page, pageSize, search, sorting, tagIdToLabel, tagLabelToId]);
281
+ }, [advancedFilterState, page, pageSize, search, sorting]);
343
282
  const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams]);
283
+ const queryRef = React.useRef(searchParams?.toString() ?? "");
284
+ React.useEffect(() => {
285
+ if (!pathname) return;
286
+ const params = new URLSearchParams();
287
+ if (search.trim().length) params.set("search", search.trim());
288
+ if (page > 1) params.set("page", String(page));
289
+ if (sorting.length > 0) {
290
+ params.set("sort", sorting[0].id);
291
+ params.set("order", sorting[0].desc ? "desc" : "asc");
292
+ }
293
+ const advancedParams = serializeTree(advancedFilterState);
294
+ for (const [key, val] of Object.entries(advancedParams)) {
295
+ params.set(key, val);
296
+ }
297
+ const next = params.toString();
298
+ if (queryRef.current === next) return;
299
+ queryRef.current = next;
300
+ router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false });
301
+ }, [pathname, router, page, search, sorting, advancedFilterState]);
344
302
  const exportConfig = React.useMemo(() => ({
345
303
  view: {
346
304
  getUrl: (format) => buildCrudExportUrl("customers/companies", { ...currentParams, exportScope: "view" }, format)
@@ -489,26 +447,6 @@ function CustomersCompaniesPage() {
489
447
  }
490
448
  return succeeded.length > 0;
491
449
  }, [bulkMutationContextId, confirm, retryBulkMutation, runBulkMutation, t]);
492
- const handleFiltersApply = React.useCallback((values) => {
493
- const next = {};
494
- Object.entries(values).forEach(([key, value]) => {
495
- if (value !== void 0) next[key] = value;
496
- });
497
- const rawTags = Array.isArray(values.tagIds) ? values.tagIds : [];
498
- const sanitizedTags = rawTags.map((tag) => {
499
- const normalized = typeof tag === "string" ? tag.trim() : "";
500
- if (!normalized) return "";
501
- return tagIdToLabel[normalized] ?? normalized;
502
- }).filter((tag) => tag.length > 0);
503
- if (sanitizedTags.length) next.tagIds = sanitizedTags;
504
- else delete next.tagIds;
505
- setFilterValues(next);
506
- setPage(1);
507
- }, [setFilterValues, setPage, tagIdToLabel]);
508
- const handleFiltersClear = React.useCallback(() => {
509
- setFilterValues({});
510
- setPage(1);
511
- }, [setFilterValues, setPage]);
512
450
  const columns = React.useMemo(() => {
513
451
  const noValue = /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: t("customers.companies.list.noValue") });
514
452
  const renderDictionaryCell = (kind, rawValue) => /* @__PURE__ */ jsx(
@@ -548,25 +486,49 @@ function CustomersCompaniesPage() {
548
486
  {
549
487
  accessorKey: "name",
550
488
  header: t("customers.companies.list.columns.name"),
551
- meta: { alwaysVisible: true, columnChooserGroup: "Basic Info", filterKey: "display_name", maxWidth: "260px" },
489
+ meta: {
490
+ alwaysVisible: true,
491
+ columnChooserGroup: "Basic Info",
492
+ filterKey: "display_name",
493
+ filterGroup: "CRM",
494
+ maxWidth: "260px"
495
+ },
552
496
  cell: ({ row }) => /* @__PURE__ */ jsx(Link, { href: `/backend/customers/companies-v2/${row.original.id}`, className: "font-medium hover:underline", children: row.original.name })
553
497
  },
554
498
  {
555
499
  accessorKey: "email",
556
500
  header: t("customers.companies.list.columns.email"),
557
- meta: { columnChooserGroup: "Contact", filterKey: "primary_email", maxWidth: "220px" },
501
+ meta: {
502
+ columnChooserGroup: "Contact",
503
+ filterKey: "primary_email",
504
+ filterGroup: "Contact",
505
+ filterIconName: "mail",
506
+ maxWidth: "220px"
507
+ },
558
508
  cell: ({ row }) => row.original.email || noValue
559
509
  },
560
510
  {
561
511
  accessorKey: "phone",
562
512
  header: t("customers.companies.detail.highlights.primaryPhone", "Primary phone"),
563
- meta: { columnChooserGroup: "Contact", hidden: true, filterKey: "primary_phone", maxWidth: "180px" },
513
+ meta: {
514
+ columnChooserGroup: "Contact",
515
+ hidden: true,
516
+ filterKey: "primary_phone",
517
+ filterGroup: "Contact",
518
+ filterIconName: "phone",
519
+ maxWidth: "180px"
520
+ },
564
521
  cell: ({ row }) => row.original.phone || noValue
565
522
  },
566
523
  {
567
524
  accessorKey: "status",
568
525
  header: t("customers.companies.list.columns.status"),
569
- meta: { filterType: "select", filterOptions: dictionaryOptions.statuses, columnChooserGroup: "Basic Info" },
526
+ meta: {
527
+ filterType: "select",
528
+ filterOptions: dictionaryOptions.statuses,
529
+ columnChooserGroup: "Basic Info",
530
+ filterGroup: "CRM"
531
+ },
570
532
  cell: ({ row }) => renderDictionaryCell("statuses", row.original.status)
571
533
  },
572
534
  {
@@ -576,14 +538,20 @@ function CustomersCompaniesPage() {
576
538
  filterType: "select",
577
539
  filterOptions: dictionaryOptions.lifecycleStages,
578
540
  columnChooserGroup: "Basic Info",
579
- filterKey: "lifecycle_stage"
541
+ filterKey: "lifecycle_stage",
542
+ filterGroup: "CRM"
580
543
  },
581
544
  cell: ({ row }) => renderDictionaryCell("lifecycle-stages", row.original.lifecycleStage)
582
545
  },
583
546
  {
584
547
  accessorKey: "nextInteractionAt",
585
548
  header: t("customers.companies.list.columns.nextInteraction"),
586
- meta: { columnChooserGroup: "Dates", filterKey: "next_interaction_at" },
549
+ meta: {
550
+ columnChooserGroup: "Dates",
551
+ filterKey: "next_interaction_at",
552
+ filterGroup: "Activity",
553
+ filterIconName: "calendar"
554
+ },
587
555
  cell: ({ row }) => row.original.nextInteractionAt ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-sm", children: [
588
556
  row.original.nextInteractionIcon ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card", children: renderDictionaryIcon(row.original.nextInteractionIcon, "h-4 w-4") }) : null,
589
557
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
@@ -596,43 +564,93 @@ function CustomersCompaniesPage() {
596
564
  {
597
565
  accessorKey: "source",
598
566
  header: t("customers.companies.list.columns.source"),
599
- meta: { filterType: "select", filterOptions: dictionaryOptions.sources, columnChooserGroup: "Basic Info" },
567
+ meta: {
568
+ filterType: "select",
569
+ filterOptions: dictionaryOptions.sources,
570
+ columnChooserGroup: "Basic Info",
571
+ filterGroup: "CRM"
572
+ },
600
573
  cell: ({ row }) => renderDictionaryCell("sources", row.original.source)
601
574
  },
575
+ {
576
+ accessorKey: "ownerUserId",
577
+ header: t("customers.companies.list.columns.owner", "Owner"),
578
+ meta: {
579
+ columnChooserGroup: "CRM",
580
+ filterType: "select",
581
+ filterOptions: resolvedOwnerFilterOptions,
582
+ filterLoadOptions: loadOwnerFilterOptions,
583
+ filterGroup: "CRM",
584
+ filterIconName: "user-round",
585
+ filterKey: "owner_user_id",
586
+ hidden: true
587
+ },
588
+ cell: ({ row }) => row.original.ownerUserId ?? null
589
+ },
602
590
  {
603
591
  accessorKey: "legalName",
604
592
  header: t("customers.companies.detail.fields.legalName", "Legal name"),
605
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.legal_name" },
593
+ meta: {
594
+ columnChooserGroup: "Profile",
595
+ hidden: true,
596
+ filterKey: "company_profile.legal_name",
597
+ filterGroup: "Profile"
598
+ },
606
599
  cell: ({ row }) => row.original.legalName || noValue
607
600
  },
608
601
  {
609
602
  accessorKey: "brandName",
610
603
  header: t("customers.companies.detail.fields.brandName", "Brand name"),
611
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.brand_name" },
604
+ meta: {
605
+ columnChooserGroup: "Profile",
606
+ hidden: true,
607
+ filterKey: "company_profile.brand_name",
608
+ filterGroup: "Profile"
609
+ },
612
610
  cell: ({ row }) => row.original.brandName || noValue
613
611
  },
614
612
  {
615
613
  accessorKey: "domain",
616
614
  header: t("customers.companies.detail.fields.domain", "Domain"),
617
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.domain" },
615
+ meta: {
616
+ columnChooserGroup: "Profile",
617
+ hidden: true,
618
+ filterKey: "company_profile.domain",
619
+ filterGroup: "Profile"
620
+ },
618
621
  cell: ({ row }) => row.original.domain || noValue
619
622
  },
620
623
  {
621
624
  accessorKey: "websiteUrl",
622
625
  header: t("customers.companies.detail.fields.website", "Website"),
623
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.website_url" },
626
+ meta: {
627
+ columnChooserGroup: "Profile",
628
+ hidden: true,
629
+ filterKey: "company_profile.website_url",
630
+ filterGroup: "Profile"
631
+ },
624
632
  cell: ({ row }) => row.original.websiteUrl || noValue
625
633
  },
626
634
  {
627
635
  accessorKey: "industry",
628
636
  header: t("customers.companies.detail.fields.industry", "Industry"),
629
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.industry" },
637
+ meta: {
638
+ columnChooserGroup: "Profile",
639
+ hidden: true,
640
+ filterKey: "company_profile.industry",
641
+ filterGroup: "Profile"
642
+ },
630
643
  cell: ({ row }) => row.original.industry || noValue
631
644
  },
632
645
  {
633
646
  accessorKey: "sizeBucket",
634
647
  header: t("customers.companies.detail.fields.sizeBucket", "Company size"),
635
- meta: { columnChooserGroup: "Profile", hidden: true, filterKey: "company_profile.size_bucket" },
648
+ meta: {
649
+ columnChooserGroup: "Profile",
650
+ hidden: true,
651
+ filterKey: "company_profile.size_bucket",
652
+ filterGroup: "Profile"
653
+ },
636
654
  cell: ({ row }) => row.original.sizeBucket || noValue
637
655
  },
638
656
  {
@@ -642,14 +660,20 @@ function CustomersCompaniesPage() {
642
660
  columnChooserGroup: "Profile",
643
661
  hidden: true,
644
662
  filterKey: "company_profile.annual_revenue",
645
- filterType: "number"
663
+ filterType: "number",
664
+ filterGroup: "Profile"
646
665
  },
647
666
  cell: ({ row }) => row.original.annualRevenue || noValue
648
667
  },
649
668
  {
650
669
  accessorKey: "description",
651
670
  header: t("customers.companies.detail.fields.description", "Description"),
652
- meta: { columnChooserGroup: "Notes", hidden: true, filterKey: "description" },
671
+ meta: {
672
+ columnChooserGroup: "Notes",
673
+ hidden: true,
674
+ filterKey: "description",
675
+ filterGroup: "Notes"
676
+ },
653
677
  cell: ({ row }) => row.original.description || noValue
654
678
  }
655
679
  ];
@@ -667,93 +691,146 @@ function CustomersCompaniesPage() {
667
691
  cell: ({ getValue }) => renderCustomFieldCell(getValue())
668
692
  }));
669
693
  return [...baseColumns, ...customColumns];
670
- }, [customFieldDefs, dictionaryMaps, t]);
694
+ }, [customFieldDefs, dictionaryMaps, dictionaryOptions, loadOwnerFilterOptions, resolvedOwnerFilterOptions, t]);
695
+ const { advancedFilterFields } = useAutoDiscoveredFields({ columns, customFieldDefs });
696
+ React.useEffect(() => {
697
+ setPanelFields((prev) => {
698
+ if (prev === advancedFilterFields) return prev;
699
+ if (prev.length === advancedFilterFields.length) {
700
+ let same = true;
701
+ for (let i = 0; i < prev.length; i++) {
702
+ if (prev[i].key !== advancedFilterFields[i].key) {
703
+ same = false;
704
+ break;
705
+ }
706
+ }
707
+ if (same) return prev;
708
+ }
709
+ return advancedFilterFields;
710
+ });
711
+ }, [advancedFilterFields]);
712
+ const companiesPresets = React.useMemo(() => makeCompaniesPresets(), []);
671
713
  return /* @__PURE__ */ jsxs(Page, { children: [
672
- /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
673
- DataTable,
674
- {
675
- stickyFirstColumn: true,
676
- stickyActionsColumn: true,
677
- title: t("customers.companies.list.title"),
678
- refreshButton: {
679
- label: t("customers.companies.list.actions.refresh"),
680
- onRefresh: () => {
681
- setSearch("");
714
+ /* @__PURE__ */ jsxs(PageBody, { children: [
715
+ /* @__PURE__ */ jsx(
716
+ DataTable,
717
+ {
718
+ stickyFirstColumn: true,
719
+ stickyActionsColumn: true,
720
+ title: t("customers.companies.list.title"),
721
+ refreshButton: {
722
+ label: t("customers.companies.list.actions.refresh"),
723
+ onRefresh: () => {
724
+ setSearch("");
725
+ setPage(1);
726
+ handleRefresh();
727
+ }
728
+ },
729
+ actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/companies/create", children: t("customers.companies.list.actions.new") }) }),
730
+ columns,
731
+ columnChooser: { auto: true },
732
+ data: rows,
733
+ exporter: exportConfig,
734
+ searchValue: search,
735
+ onSearchChange: (value) => {
736
+ setSearch(value);
682
737
  setPage(1);
683
- handleRefresh();
684
- }
685
- },
686
- actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/companies/create", children: t("customers.companies.list.actions.new") }) }),
687
- columns,
688
- columnChooser: { auto: true },
689
- data: rows,
690
- exporter: exportConfig,
691
- searchValue: search,
692
- onSearchChange: (value) => {
693
- setSearch(value);
694
- setPage(1);
695
- },
696
- searchPlaceholder: t("customers.companies.list.searchPlaceholder"),
697
- filters,
698
- filterValues,
699
- onFiltersApply: handleFiltersApply,
700
- onFiltersClear: handleFiltersClear,
701
- entityIds: [E.customers.customer_entity, E.customers.customer_company_profile],
702
- onRowClick: (row) => router.push(`/backend/customers/companies-v2/${row.id}`),
703
- perspective: { tableId: "customers.companies.list" },
704
- sortable: true,
705
- sorting,
706
- onSortingChange: setSorting,
707
- bulkActions: [
708
- {
709
- id: "delete",
710
- label: t("customers.companies.list.actions.bulkDelete", "Delete selected"),
711
- destructive: true,
712
- onExecute: handleBulkDelete
713
- }
714
- ],
715
- rowActions: (row) => /* @__PURE__ */ jsx(
716
- RowActions,
717
- {
718
- items: [
719
- {
720
- id: "view",
721
- label: t("customers.companies.list.actions.view"),
722
- onSelect: () => {
723
- router.push(`/backend/customers/companies-v2/${row.id}`);
738
+ },
739
+ searchPlaceholder: t("customers.companies.list.searchPlaceholder"),
740
+ entityIds: [E.customers.customer_entity, E.customers.customer_company_profile],
741
+ onRowClick: (row) => router.push(`/backend/customers/companies-v2/${row.id}`),
742
+ perspective: { tableId: "customers.companies.list" },
743
+ sortable: true,
744
+ sorting,
745
+ onSortingChange: setSorting,
746
+ bulkActions: [
747
+ {
748
+ id: "delete",
749
+ label: t("customers.companies.list.actions.bulkDelete", "Delete selected"),
750
+ destructive: true,
751
+ onExecute: handleBulkDelete
752
+ }
753
+ ],
754
+ rowActions: (row) => /* @__PURE__ */ jsx(
755
+ RowActions,
756
+ {
757
+ items: [
758
+ {
759
+ id: "view",
760
+ label: t("customers.companies.list.actions.view"),
761
+ onSelect: () => {
762
+ router.push(`/backend/customers/companies-v2/${row.id}`);
763
+ }
764
+ },
765
+ {
766
+ id: "open-new-tab",
767
+ label: t("customers.companies.list.actions.openInNewTab"),
768
+ onSelect: () => window.open(`/backend/customers/companies-v2/${row.id}`, "_blank", "noopener")
769
+ },
770
+ {
771
+ id: "delete",
772
+ label: t("customers.companies.list.actions.delete"),
773
+ destructive: true,
774
+ onSelect: () => handleDelete(row)
724
775
  }
725
- },
726
- {
727
- id: "open-new-tab",
728
- label: t("customers.companies.list.actions.openInNewTab"),
729
- onSelect: () => window.open(`/backend/customers/companies-v2/${row.id}`, "_blank", "noopener")
730
- },
731
- {
732
- id: "delete",
733
- label: t("customers.companies.list.actions.delete"),
734
- destructive: true,
735
- onSelect: () => handleDelete(row)
736
- }
737
- ]
738
- }
739
- ),
740
- advancedFilter: {
741
- auto: true,
742
- value: advancedFilterState,
743
- onChange: setAdvancedFilterState,
744
- onApply: () => {
745
- setPage(1);
776
+ ]
777
+ }
778
+ ),
779
+ advancedFilter: {
780
+ auto: true,
781
+ value: filterPanel.tree,
782
+ onChange: filterPanel.setTree,
783
+ onApply: () => filterPanel.flush(),
784
+ onClear: handleAdvancedFilterClear,
785
+ triggerRef: filtersTriggerRef,
786
+ externalPopover: true,
787
+ onTriggerClick: () => setFiltersOpen((prev) => !prev),
788
+ onApplyTree: (tree) => {
789
+ filterPanel.replaceTree(tree);
790
+ setPage(1);
791
+ }
746
792
  },
747
- onClear: () => {
748
- setAdvancedFilterState({ logic: "and", conditions: [] });
749
- setPage(1);
750
- }
751
- },
752
- virtualized: true,
753
- pagination: { page, pageSize, total, totalPages, onPageChange: setPage, pageSizeOptions: [10, 25, 50, 100], onPageSizeChange: handlePageSizeChange, cacheStatus },
754
- isLoading
755
- }
756
- ) }),
793
+ activeFilterChips: /* @__PURE__ */ jsx(
794
+ ActiveFilterChips,
795
+ {
796
+ tree: filterPanel.tree,
797
+ fields: advancedFilterFields,
798
+ popoverOpen: filtersOpen,
799
+ onRemoveNode: (id) => filterPanel.dispatch({ type: "removeNode", nodeId: id }),
800
+ onOpen: () => setFiltersOpen(true)
801
+ }
802
+ ),
803
+ filterAwareEmptyState: {
804
+ active: advancedFilterState.root.children.length > 0,
805
+ entityNamePlural: t("customers.companies.entityPlural", "companies"),
806
+ canRemoveLast: filterPanel.tree.root.children.length > 0,
807
+ onClearAll: handleAdvancedFilterClear,
808
+ onRemoveLast: () => filterPanel.dispatch({ type: "removeLast" })
809
+ },
810
+ virtualized: true,
811
+ pagination: { page, pageSize, total, totalPages, onPageChange: setPage, pageSizeOptions: [10, 25, 50, 100], onPageSizeChange: handlePageSizeChange, cacheStatus },
812
+ isLoading
813
+ }
814
+ ),
815
+ /* @__PURE__ */ jsx(
816
+ AdvancedFilterPanel,
817
+ {
818
+ fields: advancedFilterFields,
819
+ value: filterPanel.tree,
820
+ onChange: filterPanel.setTree,
821
+ onApply: filterPanel.flush,
822
+ onClear: handleAdvancedFilterClear,
823
+ onFlush: filterPanel.flush,
824
+ pendingErrors: filterPanel.pendingErrors,
825
+ userId: currentUserId,
826
+ presets: companiesPresets,
827
+ open: filtersOpen,
828
+ onOpenChange: setFiltersOpen,
829
+ triggerRef: filtersTriggerRef,
830
+ savedFilterStorageKey: "customers.companies.list"
831
+ }
832
+ )
833
+ ] }),
757
834
  ConfirmDialogElement
758
835
  ] });
759
836
  }