@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3287.1.450f4ffb56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/domain_mapping/index.js +31 -0
- package/dist/generated/entities/domain_mapping/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +16 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/auth.js +1 -1
- package/dist/helpers/integration/auth.js.map +2 -2
- package/dist/helpers/integration/authUi.js +1 -1
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +18 -17
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/auth/frontend/login.js +4 -4
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +5 -5
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/frontend/reset.js +2 -2
- package/dist/modules/auth/frontend/reset.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +2 -1
- package/dist/modules/customer_accounts/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-check.js +70 -0
- package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +14 -3
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
- package/dist/modules/customer_accounts/api/signup.js +32 -5
- package/dist/modules/customer_accounts/api/signup.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +4 -3
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +6 -5
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/data/enrichers.js +78 -12
- package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
- package/dist/modules/customer_accounts/data/entities.js +85 -2
- package/dist/modules/customer_accounts/data/entities.js.map +2 -2
- package/dist/modules/customer_accounts/data/guards.js +130 -0
- package/dist/modules/customer_accounts/data/guards.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +19 -0
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/di.js +23 -1
- package/dist/modules/customer_accounts/di.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +9 -2
- package/dist/modules/customer_accounts/events.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
- package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
- package/dist/modules/customer_accounts/lib/hostname.js +55 -0
- package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +76 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +76 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +48 -1
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
- package/dist/modules/customers/api/companies/route.js +4 -9
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +4 -9
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +4 -9
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +0 -12
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +361 -284
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +378 -473
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +368 -286
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +2 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +6 -0
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +2 -3
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +16 -2
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +5 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +3 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +31 -8
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +3 -5
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -4
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +3 -4
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/generated/entities/domain_mapping/index.ts +14 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +16 -0
- package/package.json +5 -5
- package/src/helpers/integration/auth.ts +1 -1
- package/src/helpers/integration/authUi.ts +1 -1
- package/src/helpers/integration/salesUi.ts +27 -23
- package/src/modules/auth/frontend/login.tsx +4 -3
- package/src/modules/auth/frontend/reset/[token]/page.tsx +4 -4
- package/src/modules/auth/frontend/reset.tsx +2 -2
- package/src/modules/customer_accounts/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
- package/src/modules/customer_accounts/api/domain-check.ts +83 -0
- package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
- package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
- package/src/modules/customer_accounts/api/login.ts +14 -3
- package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
- package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
- package/src/modules/customer_accounts/api/signup.ts +36 -5
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +3 -2
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +5 -4
- package/src/modules/customer_accounts/data/enrichers.ts +95 -12
- package/src/modules/customer_accounts/data/entities.ts +80 -1
- package/src/modules/customer_accounts/data/guards.ts +155 -0
- package/src/modules/customer_accounts/data/validators.ts +26 -0
- package/src/modules/customer_accounts/di.ts +35 -1
- package/src/modules/customer_accounts/events.ts +8 -1
- package/src/modules/customer_accounts/i18n/de.json +78 -0
- package/src/modules/customer_accounts/i18n/en.json +78 -0
- package/src/modules/customer_accounts/i18n/es.json +78 -0
- package/src/modules/customer_accounts/i18n/pl.json +78 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
- package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
- package/src/modules/customer_accounts/lib/hostname.ts +76 -0
- package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
- package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
- package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
- package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
- package/src/modules/customer_accounts/notifications.client.ts +76 -0
- package/src/modules/customer_accounts/notifications.ts +76 -0
- package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
- package/src/modules/customer_accounts/setup.ts +81 -1
- package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
- package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
- package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
- package/src/modules/customers/api/companies/route.ts +4 -9
- package/src/modules/customers/api/deals/route.ts +4 -9
- package/src/modules/customers/api/people/route.ts +4 -9
- package/src/modules/customers/api/utils.ts +0 -14
- package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
- package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
- package/src/modules/customers/backend/customers/people/page.tsx +331 -240
- package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
- package/src/modules/customers/components/formConfig.tsx +2 -2
- package/src/modules/customers/i18n/de.json +31 -0
- package/src/modules/customers/i18n/en.json +31 -0
- package/src/modules/customers/i18n/es.json +31 -0
- package/src/modules/customers/i18n/pl.json +31 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
- package/src/modules/customers/widgets/injection-table.ts +6 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +2 -2
- package/src/modules/perspectives/services/perspectiveService.ts +30 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +5 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +3 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -2
- package/src/modules/query_index/lib/engine.ts +46 -8
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +3 -5
- package/src/modules/sales/components/ProviderFieldInput.tsx +2 -2
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -3
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +3 -4
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +2 -2
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
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, type DataTableExportFormat, withDataTableNamespaces } from '@open-mercato/ui/backend/DataTable'
|
|
8
8
|
import type { ColumnDef } from '@tanstack/react-table'
|
|
9
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
10
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
11
|
-
import { apiCall, apiCallOrThrow
|
|
11
|
+
import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
12
12
|
import { buildCrudExportUrl } from '@open-mercato/ui/backend/utils/crud'
|
|
13
13
|
import { groupBulkDeleteFailures, runBulkDelete } from '@open-mercato/ui/backend/utils/bulkDelete'
|
|
14
14
|
import { coalesceLastOperations } from '@open-mercato/ui/backend/operations/store'
|
|
@@ -18,10 +18,12 @@ import { E } from '#generated/entities.ids.generated'
|
|
|
18
18
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
19
19
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
20
20
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
21
|
-
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
22
21
|
import type { FilterOption } from '@open-mercato/ui/backend/FilterOverlay'
|
|
23
|
-
import type {
|
|
24
|
-
import {
|
|
22
|
+
import type { FilterFieldDef, FilterOption as AdvancedFilterOption } from '@open-mercato/shared/lib/query/advanced-filter'
|
|
23
|
+
import type { AdvancedFilterTree } from '@open-mercato/shared/lib/query/advanced-filter-tree'
|
|
24
|
+
import { createEmptyTree, makeRuleTree } from '@open-mercato/shared/lib/query/advanced-filter-tree'
|
|
25
|
+
import { deserializeAdvancedFilter, deserializeTree, flatToTree, mapDictionaryColorToTone, serializeTree } from '@open-mercato/shared/lib/query/advanced-filter'
|
|
26
|
+
import { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'
|
|
25
27
|
import {
|
|
26
28
|
DictionaryValue,
|
|
27
29
|
createEmptyCustomerDictionaryMaps,
|
|
@@ -38,10 +40,51 @@ import {
|
|
|
38
40
|
normalizeCustomFieldFilterOptions,
|
|
39
41
|
supportsCustomFieldColumn,
|
|
40
42
|
} from '@open-mercato/ui/backend/utils/customFieldColumns'
|
|
43
|
+
import { useAutoDiscoveredFields } from '@open-mercato/ui/backend/utils/useAutoDiscoveredFields'
|
|
44
|
+
import { useAdvancedFilterTree } from '@open-mercato/ui/backend/hooks/useAdvancedFilter'
|
|
45
|
+
import { AdvancedFilterPanel } from '@open-mercato/ui/backend/filters/AdvancedFilterPanel'
|
|
46
|
+
import { ActiveFilterChips } from '@open-mercato/ui/backend/filters/ActiveFilterChips'
|
|
47
|
+
import type { FilterPreset } from '@open-mercato/ui/backend/filters/QuickFilters'
|
|
41
48
|
import { useQueryClient } from '@tanstack/react-query'
|
|
42
49
|
import { ensureCustomerDictionary } from '../../../components/detail/hooks/useCustomerDictionary'
|
|
50
|
+
import {
|
|
51
|
+
ensureCurrentUserFilterOption,
|
|
52
|
+
fetchAssignableStaffMembers,
|
|
53
|
+
mapAssignableStaffToFilterOptions,
|
|
54
|
+
} from '../../../components/detail/assignableStaff'
|
|
43
55
|
import { CollectionPreviewCell, normalizeCollectionLabels } from '../../../components/list/CollectionPreviewCell'
|
|
44
56
|
|
|
57
|
+
type DictionaryOptionWithTone = AdvancedFilterOption & FilterOption
|
|
58
|
+
|
|
59
|
+
function makeCompaniesPresets(): FilterPreset[] {
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
id: 'my-accounts',
|
|
63
|
+
labelKey: 'customers.companies.presets.myAccounts',
|
|
64
|
+
requiresUser: true,
|
|
65
|
+
build: ({ userId }) => makeRuleTree({ field: 'owner_user_id', operator: 'is', value: userId }),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'recently-created',
|
|
69
|
+
labelKey: 'customers.companies.presets.recentlyCreated',
|
|
70
|
+
iconName: 'clock',
|
|
71
|
+
build: ({ now }) => {
|
|
72
|
+
const cutoff = new Date(now.getTime() - 7 * 24 * 3600 * 1000).toISOString().slice(0, 10)
|
|
73
|
+
return makeRuleTree({ field: 'created_at', operator: 'is_after', value: cutoff })
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'inactive-60',
|
|
78
|
+
labelKey: 'customers.companies.presets.inactive60',
|
|
79
|
+
build: ({ now }) => {
|
|
80
|
+
const cutoff = new Date(now.getTime() - 60 * 24 * 3600 * 1000).toISOString().slice(0, 10)
|
|
81
|
+
return makeRuleTree({ field: 'next_interaction_at', operator: 'is_before', value: cutoff })
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
45
88
|
type CompanyRow = {
|
|
46
89
|
id: string
|
|
47
90
|
name: string
|
|
@@ -63,6 +106,7 @@ type CompanyRow = {
|
|
|
63
106
|
nextInteractionColor?: string | null
|
|
64
107
|
organizationId?: string | null
|
|
65
108
|
source?: string | null
|
|
109
|
+
ownerUserId?: string | null
|
|
66
110
|
} & Record<string, unknown>
|
|
67
111
|
|
|
68
112
|
type CompaniesResponse = {
|
|
@@ -109,6 +153,7 @@ function mapApiItem(item: Record<string, unknown>): CompanyRow | null {
|
|
|
109
153
|
const nextInteractionColor = typeof item.next_interaction_color === 'string' ? item.next_interaction_color : null
|
|
110
154
|
const organizationId = typeof item.organization_id === 'string' ? item.organization_id : null
|
|
111
155
|
const source = typeof item.source === 'string' ? item.source : null
|
|
156
|
+
const ownerUserId = typeof item.owner_user_id === 'string' ? item.owner_user_id : null
|
|
112
157
|
const customFields: Record<string, unknown> = {}
|
|
113
158
|
for (const [key, value] of Object.entries(item)) {
|
|
114
159
|
if (key.startsWith('cf_')) {
|
|
@@ -136,6 +181,7 @@ function mapApiItem(item: Record<string, unknown>): CompanyRow | null {
|
|
|
136
181
|
nextInteractionColor,
|
|
137
182
|
organizationId,
|
|
138
183
|
source,
|
|
184
|
+
ownerUserId,
|
|
139
185
|
...customFields,
|
|
140
186
|
}, item)
|
|
141
187
|
}
|
|
@@ -149,13 +195,49 @@ export default function CustomersCompaniesPage() {
|
|
|
149
195
|
const [total, setTotal] = React.useState(0)
|
|
150
196
|
const [totalPages, setTotalPages] = React.useState(1)
|
|
151
197
|
const [search, setSearch] = React.useState('')
|
|
152
|
-
const
|
|
153
|
-
const
|
|
198
|
+
const pathname = usePathname()
|
|
199
|
+
const searchParams = useSearchParams()
|
|
200
|
+
// One-shot URL hydration used as the hook's initial value. The hook is the
|
|
201
|
+
// single source of truth from this point on — the page MUST NOT keep a
|
|
202
|
+
// parallel `useState<AdvancedFilterTree>` (see spec "Migration & Backward
|
|
203
|
+
// Compatibility" → state ownership).
|
|
204
|
+
const initialFilterTree = React.useMemo<AdvancedFilterTree>(() => {
|
|
205
|
+
if (!searchParams) return createEmptyTree()
|
|
206
|
+
const record: Record<string, string> = {}
|
|
207
|
+
searchParams.forEach((value, key) => {
|
|
208
|
+
if (key.startsWith('filter[')) record[key] = value
|
|
209
|
+
})
|
|
210
|
+
const v2 = deserializeTree(record)
|
|
211
|
+
if (v2) return v2
|
|
212
|
+
const flat = deserializeAdvancedFilter(record)
|
|
213
|
+
if (flat) return flatToTree(flat)
|
|
214
|
+
return createEmptyTree()
|
|
215
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
216
|
+
}, [])
|
|
217
|
+
// `filterPanel` lives at the top of the component so derived state below
|
|
218
|
+
// (URL params, data fetch, export config) can read `filterPanel.appliedTree`
|
|
219
|
+
// directly. Real `FilterFieldDef[]` arrives later from `useAutoDiscoveredFields`
|
|
220
|
+
// (it depends on columns) and is synced into the hook via a small effect at
|
|
221
|
+
// the bottom of the component. The hook reads fields through a ref at
|
|
222
|
+
// validation time only — first validation cannot fire before user input, by
|
|
223
|
+
// which point fields have settled, so the empty initial value is safe.
|
|
224
|
+
const [panelFields, setPanelFields] = React.useState<FilterFieldDef[]>([])
|
|
225
|
+
const [filtersOpen, setFiltersOpen] = React.useState(false)
|
|
226
|
+
const filtersTriggerRef = React.useRef<HTMLButtonElement | null>(null)
|
|
227
|
+
const filterPanel = useAdvancedFilterTree({
|
|
228
|
+
initial: initialFilterTree,
|
|
229
|
+
fields: panelFields,
|
|
230
|
+
onApply: () => setPage(1),
|
|
231
|
+
})
|
|
232
|
+
const advancedFilterState = filterPanel.appliedTree
|
|
233
|
+
const handleAdvancedFilterClear = React.useCallback(() => {
|
|
234
|
+
filterPanel.clear()
|
|
235
|
+
setPage(1)
|
|
236
|
+
}, [filterPanel])
|
|
154
237
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
155
238
|
const [reloadToken, setReloadToken] = React.useState(0)
|
|
156
239
|
const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)
|
|
157
240
|
const [dictionaryMaps, setDictionaryMaps] = React.useState<Record<DictionaryKindKey, DictionaryMap>>(createEmptyCustomerDictionaryMaps())
|
|
158
|
-
const [tagIdToLabel, setTagIdToLabel] = React.useState<Record<string, string>>({})
|
|
159
241
|
const scopeVersion = useOrganizationScopeVersion()
|
|
160
242
|
const queryClient = useQueryClient()
|
|
161
243
|
const t = useT()
|
|
@@ -196,15 +278,15 @@ export default function CustomersCompaniesPage() {
|
|
|
196
278
|
return []
|
|
197
279
|
}
|
|
198
280
|
}, [queryClient, scopeVersion])
|
|
199
|
-
const loadDictionaryOptions = React.useCallback(async (kind: 'statuses' | 'sources' | 'lifecycle-stages') => {
|
|
200
|
-
const entries = await fetchDictionaryEntries(kind)
|
|
201
|
-
return entries.map((entry) => ({ value: entry.value, label: entry.label }))
|
|
202
|
-
}, [fetchDictionaryEntries])
|
|
203
|
-
|
|
204
281
|
const dictionaryOptions = React.useMemo(() => {
|
|
205
|
-
const toOptions = (map?: DictionaryMap | null):
|
|
282
|
+
const toOptions = (map?: DictionaryMap | null): DictionaryOptionWithTone[] =>
|
|
206
283
|
Object.values(map ?? {})
|
|
207
|
-
.map((entry) =>
|
|
284
|
+
.map((entry) => {
|
|
285
|
+
const tone = mapDictionaryColorToTone(entry.color)
|
|
286
|
+
const option: DictionaryOptionWithTone = { value: entry.value, label: entry.label }
|
|
287
|
+
if (tone) option.tone = tone
|
|
288
|
+
return option
|
|
289
|
+
})
|
|
208
290
|
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))
|
|
209
291
|
return {
|
|
210
292
|
statuses: toOptions(dictionaryMaps.statuses),
|
|
@@ -213,63 +295,6 @@ export default function CustomersCompaniesPage() {
|
|
|
213
295
|
}
|
|
214
296
|
}, [dictionaryMaps])
|
|
215
297
|
|
|
216
|
-
const loadTagOptions = React.useCallback(async (query?: string): Promise<FilterOption[]> => {
|
|
217
|
-
try {
|
|
218
|
-
const params = new URLSearchParams({ pageSize: '100' })
|
|
219
|
-
const trimmedQuery = typeof query === 'string' ? query.trim() : ''
|
|
220
|
-
if (trimmedQuery) params.set('search', trimmedQuery)
|
|
221
|
-
const payload = await readApiResultOrThrow<{ items?: unknown[] }>(
|
|
222
|
-
`/api/customers/tags?${params.toString()}`,
|
|
223
|
-
undefined,
|
|
224
|
-
{ errorMessage: t('customers.companies.list.tags.loadError', 'Failed to load tags.') },
|
|
225
|
-
)
|
|
226
|
-
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
227
|
-
const options: FilterOption[] = []
|
|
228
|
-
for (const item of items) {
|
|
229
|
-
if (!item || typeof item !== 'object') continue
|
|
230
|
-
const raw = item as { id?: unknown; tagId?: unknown; label?: unknown; slug?: unknown }
|
|
231
|
-
const rawId = typeof raw.id === 'string'
|
|
232
|
-
? raw.id
|
|
233
|
-
: typeof raw.tagId === 'string'
|
|
234
|
-
? raw.tagId
|
|
235
|
-
: null
|
|
236
|
-
if (!rawId) continue
|
|
237
|
-
const label = typeof raw.label === 'string' && raw.label.trim().length
|
|
238
|
-
? raw.label.trim()
|
|
239
|
-
: typeof raw.slug === 'string' && raw.slug.trim().length
|
|
240
|
-
? raw.slug.trim()
|
|
241
|
-
: rawId
|
|
242
|
-
options.push({ value: rawId, label })
|
|
243
|
-
}
|
|
244
|
-
if (options.length) {
|
|
245
|
-
setTagIdToLabel((prev) => {
|
|
246
|
-
let changed = false
|
|
247
|
-
const next = { ...prev }
|
|
248
|
-
for (const option of options) {
|
|
249
|
-
if (next[option.value] !== option.label) {
|
|
250
|
-
next[option.value] = option.label
|
|
251
|
-
changed = true
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return changed ? next : prev
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
return options
|
|
258
|
-
} catch (err) {
|
|
259
|
-
console.error('customers.companies.list.loadTagOptions', err)
|
|
260
|
-
return []
|
|
261
|
-
}
|
|
262
|
-
}, [setTagIdToLabel, t])
|
|
263
|
-
|
|
264
|
-
const tagLabelToId = React.useMemo(() => {
|
|
265
|
-
const map: Record<string, string> = {}
|
|
266
|
-
for (const [id, label] of Object.entries(tagIdToLabel)) {
|
|
267
|
-
if (!label) continue
|
|
268
|
-
map[label] = id
|
|
269
|
-
}
|
|
270
|
-
return map
|
|
271
|
-
}, [tagIdToLabel])
|
|
272
|
-
|
|
273
298
|
React.useEffect(() => {
|
|
274
299
|
let cancelled = false
|
|
275
300
|
async function loadAll() {
|
|
@@ -291,63 +316,35 @@ export default function CustomersCompaniesPage() {
|
|
|
291
316
|
[E.customers.customer_entity, E.customers.customer_company_profile],
|
|
292
317
|
{ keyExtras: [scopeVersion, reloadToken] },
|
|
293
318
|
)
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
id: 'createdAt',
|
|
326
|
-
label: t('customers.companies.list.filters.createdAt'),
|
|
327
|
-
type: 'dateRange',
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
id: 'emailContains',
|
|
331
|
-
label: t('customers.companies.list.filters.emailContains'),
|
|
332
|
-
type: 'text',
|
|
333
|
-
placeholder: t('customers.companies.list.filters.emailContainsPlaceholder'),
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
id: 'hasEmail',
|
|
337
|
-
label: t('customers.companies.list.filters.hasEmail'),
|
|
338
|
-
type: 'checkbox',
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
id: 'hasPhone',
|
|
342
|
-
label: t('customers.companies.list.filters.hasPhone'),
|
|
343
|
-
type: 'checkbox',
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
id: 'hasNextInteraction',
|
|
347
|
-
label: t('customers.companies.list.filters.hasNextInteraction'),
|
|
348
|
-
type: 'checkbox',
|
|
349
|
-
},
|
|
350
|
-
], [dictionaryOptions.lifecycleStages, dictionaryOptions.sources, dictionaryOptions.statuses, loadDictionaryOptions, loadTagOptions, tagIdToLabel, t])
|
|
319
|
+
const currentUserId = useCurrentUserId()
|
|
320
|
+
const [ownerFilterOptions, setOwnerFilterOptions] = React.useState<AdvancedFilterOption[]>([])
|
|
321
|
+
React.useEffect(() => {
|
|
322
|
+
const controller = new AbortController()
|
|
323
|
+
let cancelled = false
|
|
324
|
+
void fetchAssignableStaffMembers('', { pageSize: 100, signal: controller.signal })
|
|
325
|
+
.then((items) => {
|
|
326
|
+
if (!cancelled) setOwnerFilterOptions(mapAssignableStaffToFilterOptions(items))
|
|
327
|
+
})
|
|
328
|
+
.catch(() => {
|
|
329
|
+
if (!cancelled) setOwnerFilterOptions([])
|
|
330
|
+
})
|
|
331
|
+
return () => {
|
|
332
|
+
cancelled = true
|
|
333
|
+
controller.abort()
|
|
334
|
+
}
|
|
335
|
+
}, [scopeVersion])
|
|
336
|
+
const resolvedOwnerFilterOptions = React.useMemo(
|
|
337
|
+
() => ensureCurrentUserFilterOption(
|
|
338
|
+
ownerFilterOptions,
|
|
339
|
+
currentUserId,
|
|
340
|
+
t('customers.filters.currentUser', 'Current user'),
|
|
341
|
+
),
|
|
342
|
+
[currentUserId, ownerFilterOptions, t],
|
|
343
|
+
)
|
|
344
|
+
const loadOwnerFilterOptions = React.useCallback(async (query?: string): Promise<AdvancedFilterOption[]> => {
|
|
345
|
+
const items = await fetchAssignableStaffMembers(query ?? '', { pageSize: 100 })
|
|
346
|
+
return mapAssignableStaffToFilterOptions(items)
|
|
347
|
+
}, [])
|
|
351
348
|
|
|
352
349
|
const queryParams = React.useMemo(() => {
|
|
353
350
|
const params = new URLSearchParams()
|
|
@@ -358,72 +355,39 @@ export default function CustomersCompaniesPage() {
|
|
|
358
355
|
params.set('order', sorting[0].desc ? 'desc' : 'asc')
|
|
359
356
|
}
|
|
360
357
|
if (search.trim()) params.set('search', search.trim())
|
|
361
|
-
const
|
|
362
|
-
if (typeof status === 'string' && status.trim()) params.set('status', status)
|
|
363
|
-
const source = filterValues.source
|
|
364
|
-
if (typeof source === 'string' && source.trim()) params.set('source', source)
|
|
365
|
-
const lifecycleStage = filterValues.lifecycleStage
|
|
366
|
-
if (typeof lifecycleStage === 'string' && lifecycleStage.trim()) params.set('lifecycleStage', lifecycleStage)
|
|
367
|
-
const createdAt = filterValues.createdAt
|
|
368
|
-
if (createdAt && typeof createdAt === 'object') {
|
|
369
|
-
if (createdAt.from) params.set('createdFrom', createdAt.from)
|
|
370
|
-
if (createdAt.to) params.set('createdTo', createdAt.to)
|
|
371
|
-
}
|
|
372
|
-
const emailContains = filterValues.emailContains
|
|
373
|
-
if (typeof emailContains === 'string' && emailContains.trim()) {
|
|
374
|
-
params.set('emailContains', emailContains.trim())
|
|
375
|
-
}
|
|
376
|
-
const tagValues = Array.isArray(filterValues.tagIds)
|
|
377
|
-
? filterValues.tagIds
|
|
378
|
-
.map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim()))
|
|
379
|
-
.filter((value) => value.length > 0)
|
|
380
|
-
: []
|
|
381
|
-
if (tagValues.length > 0) {
|
|
382
|
-
const normalizedTagIds = tagValues
|
|
383
|
-
.map((value) => (typeof tagIdToLabel[value] === 'string' ? value : tagLabelToId[value]))
|
|
384
|
-
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
|
385
|
-
if (normalizedTagIds.length === tagValues.length && normalizedTagIds.length > 0) {
|
|
386
|
-
params.set('tagIds', normalizedTagIds.join(','))
|
|
387
|
-
} else {
|
|
388
|
-
params.set('tagIdsEmpty', 'true')
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const booleanFilters: Array<['hasEmail' | 'hasPhone' | 'hasNextInteraction', string]> = [
|
|
392
|
-
['hasEmail', 'hasEmail'],
|
|
393
|
-
['hasPhone', 'hasPhone'],
|
|
394
|
-
['hasNextInteraction', 'hasNextInteraction'],
|
|
395
|
-
]
|
|
396
|
-
for (const [key, queryKey] of booleanFilters) {
|
|
397
|
-
const value = filterValues[key]
|
|
398
|
-
if (value === true) params.set(queryKey, 'true')
|
|
399
|
-
if (value === false) params.set(queryKey, 'false')
|
|
400
|
-
}
|
|
401
|
-
Object.entries(filterValues).forEach(([key, value]) => {
|
|
402
|
-
if (!key.startsWith('cf_') || value == null) return
|
|
403
|
-
if (Array.isArray(value)) {
|
|
404
|
-
const normalized = value
|
|
405
|
-
.map((item) => {
|
|
406
|
-
if (item == null) return ''
|
|
407
|
-
if (typeof item === 'string') return item.trim()
|
|
408
|
-
return String(item).trim()
|
|
409
|
-
})
|
|
410
|
-
.filter((item) => item.length > 0)
|
|
411
|
-
if (normalized.length) params.set(key, normalized.join(','))
|
|
412
|
-
} else if (typeof value === 'object') {
|
|
413
|
-
return
|
|
414
|
-
} else if (value !== '') {
|
|
415
|
-
const stringValue = typeof value === 'string' ? value.trim() : String(value)
|
|
416
|
-
if (stringValue) params.set(key, stringValue)
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
const advancedParams = serializeAdvancedFilter(advancedFilterState)
|
|
358
|
+
const advancedParams = serializeTree(advancedFilterState)
|
|
420
359
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
421
360
|
params.set(key, val)
|
|
422
361
|
}
|
|
423
362
|
return params.toString()
|
|
424
|
-
}, [advancedFilterState,
|
|
363
|
+
}, [advancedFilterState, page, pageSize, search, sorting])
|
|
425
364
|
|
|
426
365
|
const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams])
|
|
366
|
+
|
|
367
|
+
// Mirror page state into the URL so refresh restores the same filter tree,
|
|
368
|
+
// including nested subgroups. Same pattern as the Deals page; without it
|
|
369
|
+
// refreshes silently dropped everything past whatever a stale localStorage
|
|
370
|
+
// perspective snapshot happened to contain.
|
|
371
|
+
const queryRef = React.useRef(searchParams?.toString() ?? '')
|
|
372
|
+
React.useEffect(() => {
|
|
373
|
+
if (!pathname) return
|
|
374
|
+
const params = new URLSearchParams()
|
|
375
|
+
if (search.trim().length) params.set('search', search.trim())
|
|
376
|
+
if (page > 1) params.set('page', String(page))
|
|
377
|
+
if (sorting.length > 0) {
|
|
378
|
+
params.set('sort', sorting[0].id)
|
|
379
|
+
params.set('order', sorting[0].desc ? 'desc' : 'asc')
|
|
380
|
+
}
|
|
381
|
+
const advancedParams = serializeTree(advancedFilterState)
|
|
382
|
+
for (const [key, val] of Object.entries(advancedParams)) {
|
|
383
|
+
params.set(key, val)
|
|
384
|
+
}
|
|
385
|
+
const next = params.toString()
|
|
386
|
+
if (queryRef.current === next) return
|
|
387
|
+
queryRef.current = next
|
|
388
|
+
router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false })
|
|
389
|
+
}, [pathname, router, page, search, sorting, advancedFilterState])
|
|
390
|
+
|
|
427
391
|
const exportConfig = React.useMemo(() => ({
|
|
428
392
|
view: {
|
|
429
393
|
getUrl: (format: DataTableExportFormat) =>
|
|
@@ -584,30 +548,6 @@ export default function CustomersCompaniesPage() {
|
|
|
584
548
|
return succeeded.length > 0
|
|
585
549
|
}, [bulkMutationContextId, confirm, retryBulkMutation, runBulkMutation, t])
|
|
586
550
|
|
|
587
|
-
const handleFiltersApply = React.useCallback((values: FilterValues) => {
|
|
588
|
-
const next: FilterValues = {}
|
|
589
|
-
Object.entries(values).forEach(([key, value]) => {
|
|
590
|
-
if (value !== undefined) next[key] = value
|
|
591
|
-
})
|
|
592
|
-
const rawTags = Array.isArray(values.tagIds) ? (values.tagIds as string[]) : []
|
|
593
|
-
const sanitizedTags = rawTags
|
|
594
|
-
.map((tag) => {
|
|
595
|
-
const normalized = typeof tag === 'string' ? tag.trim() : ''
|
|
596
|
-
if (!normalized) return ''
|
|
597
|
-
return tagIdToLabel[normalized] ?? normalized
|
|
598
|
-
})
|
|
599
|
-
.filter((tag) => tag.length > 0)
|
|
600
|
-
if (sanitizedTags.length) next.tagIds = sanitizedTags
|
|
601
|
-
else delete next.tagIds
|
|
602
|
-
setFilterValues(next)
|
|
603
|
-
setPage(1)
|
|
604
|
-
}, [setFilterValues, setPage, tagIdToLabel])
|
|
605
|
-
|
|
606
|
-
const handleFiltersClear = React.useCallback(() => {
|
|
607
|
-
setFilterValues({})
|
|
608
|
-
setPage(1)
|
|
609
|
-
}, [setFilterValues, setPage])
|
|
610
|
-
|
|
611
551
|
const columns = React.useMemo<ColumnDef<CompanyRow>[]>(() => {
|
|
612
552
|
const noValue = <span className="text-muted-foreground text-sm">{t('customers.companies.list.noValue')}</span>
|
|
613
553
|
const renderDictionaryCell = (kind: DictionaryKindKey, rawValue: string | null | undefined) => (
|
|
@@ -654,7 +594,13 @@ export default function CustomersCompaniesPage() {
|
|
|
654
594
|
{
|
|
655
595
|
accessorKey: 'name',
|
|
656
596
|
header: t('customers.companies.list.columns.name'),
|
|
657
|
-
meta: {
|
|
597
|
+
meta: {
|
|
598
|
+
alwaysVisible: true,
|
|
599
|
+
columnChooserGroup: 'Basic Info',
|
|
600
|
+
filterKey: 'display_name',
|
|
601
|
+
filterGroup: 'CRM',
|
|
602
|
+
maxWidth: '260px',
|
|
603
|
+
},
|
|
658
604
|
cell: ({ row }) => (
|
|
659
605
|
<Link href={`/backend/customers/companies-v2/${row.original.id}`} className="font-medium hover:underline">
|
|
660
606
|
{row.original.name}
|
|
@@ -664,19 +610,37 @@ export default function CustomersCompaniesPage() {
|
|
|
664
610
|
{
|
|
665
611
|
accessorKey: 'email',
|
|
666
612
|
header: t('customers.companies.list.columns.email'),
|
|
667
|
-
meta: {
|
|
613
|
+
meta: {
|
|
614
|
+
columnChooserGroup: 'Contact',
|
|
615
|
+
filterKey: 'primary_email',
|
|
616
|
+
filterGroup: 'Contact',
|
|
617
|
+
filterIconName: 'mail',
|
|
618
|
+
maxWidth: '220px',
|
|
619
|
+
},
|
|
668
620
|
cell: ({ row }) => row.original.email || noValue,
|
|
669
621
|
},
|
|
670
622
|
{
|
|
671
623
|
accessorKey: 'phone',
|
|
672
624
|
header: t('customers.companies.detail.highlights.primaryPhone', 'Primary phone'),
|
|
673
|
-
meta: {
|
|
625
|
+
meta: {
|
|
626
|
+
columnChooserGroup: 'Contact',
|
|
627
|
+
hidden: true,
|
|
628
|
+
filterKey: 'primary_phone',
|
|
629
|
+
filterGroup: 'Contact',
|
|
630
|
+
filterIconName: 'phone',
|
|
631
|
+
maxWidth: '180px',
|
|
632
|
+
},
|
|
674
633
|
cell: ({ row }) => row.original.phone || noValue,
|
|
675
634
|
},
|
|
676
635
|
{
|
|
677
636
|
accessorKey: 'status',
|
|
678
637
|
header: t('customers.companies.list.columns.status'),
|
|
679
|
-
meta: {
|
|
638
|
+
meta: {
|
|
639
|
+
filterType: 'select' as const,
|
|
640
|
+
filterOptions: dictionaryOptions.statuses,
|
|
641
|
+
columnChooserGroup: 'Basic Info',
|
|
642
|
+
filterGroup: 'CRM',
|
|
643
|
+
},
|
|
680
644
|
cell: ({ row }) => renderDictionaryCell('statuses', row.original.status),
|
|
681
645
|
},
|
|
682
646
|
{
|
|
@@ -687,13 +651,19 @@ export default function CustomersCompaniesPage() {
|
|
|
687
651
|
filterOptions: dictionaryOptions.lifecycleStages,
|
|
688
652
|
columnChooserGroup: 'Basic Info',
|
|
689
653
|
filterKey: 'lifecycle_stage',
|
|
654
|
+
filterGroup: 'CRM',
|
|
690
655
|
},
|
|
691
656
|
cell: ({ row }) => renderDictionaryCell('lifecycle-stages', row.original.lifecycleStage),
|
|
692
657
|
},
|
|
693
658
|
{
|
|
694
659
|
accessorKey: 'nextInteractionAt',
|
|
695
660
|
header: t('customers.companies.list.columns.nextInteraction'),
|
|
696
|
-
meta: {
|
|
661
|
+
meta: {
|
|
662
|
+
columnChooserGroup: 'Dates',
|
|
663
|
+
filterKey: 'next_interaction_at',
|
|
664
|
+
filterGroup: 'Activity',
|
|
665
|
+
filterIconName: 'calendar',
|
|
666
|
+
},
|
|
697
667
|
cell: ({ row }) =>
|
|
698
668
|
row.original.nextInteractionAt
|
|
699
669
|
? (
|
|
@@ -721,43 +691,93 @@ export default function CustomersCompaniesPage() {
|
|
|
721
691
|
{
|
|
722
692
|
accessorKey: 'source',
|
|
723
693
|
header: t('customers.companies.list.columns.source'),
|
|
724
|
-
meta: {
|
|
694
|
+
meta: {
|
|
695
|
+
filterType: 'select' as const,
|
|
696
|
+
filterOptions: dictionaryOptions.sources,
|
|
697
|
+
columnChooserGroup: 'Basic Info',
|
|
698
|
+
filterGroup: 'CRM',
|
|
699
|
+
},
|
|
725
700
|
cell: ({ row }) => renderDictionaryCell('sources', row.original.source),
|
|
726
701
|
},
|
|
702
|
+
{
|
|
703
|
+
accessorKey: 'ownerUserId',
|
|
704
|
+
header: t('customers.companies.list.columns.owner', 'Owner'),
|
|
705
|
+
meta: {
|
|
706
|
+
columnChooserGroup: 'CRM',
|
|
707
|
+
filterType: 'select',
|
|
708
|
+
filterOptions: resolvedOwnerFilterOptions,
|
|
709
|
+
filterLoadOptions: loadOwnerFilterOptions,
|
|
710
|
+
filterGroup: 'CRM',
|
|
711
|
+
filterIconName: 'user-round',
|
|
712
|
+
filterKey: 'owner_user_id',
|
|
713
|
+
hidden: true,
|
|
714
|
+
},
|
|
715
|
+
cell: ({ row }) => row.original.ownerUserId ?? null,
|
|
716
|
+
},
|
|
727
717
|
{
|
|
728
718
|
accessorKey: 'legalName',
|
|
729
719
|
header: t('customers.companies.detail.fields.legalName', 'Legal name'),
|
|
730
|
-
meta: {
|
|
720
|
+
meta: {
|
|
721
|
+
columnChooserGroup: 'Profile',
|
|
722
|
+
hidden: true,
|
|
723
|
+
filterKey: 'company_profile.legal_name',
|
|
724
|
+
filterGroup: 'Profile',
|
|
725
|
+
},
|
|
731
726
|
cell: ({ row }) => row.original.legalName || noValue,
|
|
732
727
|
},
|
|
733
728
|
{
|
|
734
729
|
accessorKey: 'brandName',
|
|
735
730
|
header: t('customers.companies.detail.fields.brandName', 'Brand name'),
|
|
736
|
-
meta: {
|
|
731
|
+
meta: {
|
|
732
|
+
columnChooserGroup: 'Profile',
|
|
733
|
+
hidden: true,
|
|
734
|
+
filterKey: 'company_profile.brand_name',
|
|
735
|
+
filterGroup: 'Profile',
|
|
736
|
+
},
|
|
737
737
|
cell: ({ row }) => row.original.brandName || noValue,
|
|
738
738
|
},
|
|
739
739
|
{
|
|
740
740
|
accessorKey: 'domain',
|
|
741
741
|
header: t('customers.companies.detail.fields.domain', 'Domain'),
|
|
742
|
-
meta: {
|
|
742
|
+
meta: {
|
|
743
|
+
columnChooserGroup: 'Profile',
|
|
744
|
+
hidden: true,
|
|
745
|
+
filterKey: 'company_profile.domain',
|
|
746
|
+
filterGroup: 'Profile',
|
|
747
|
+
},
|
|
743
748
|
cell: ({ row }) => row.original.domain || noValue,
|
|
744
749
|
},
|
|
745
750
|
{
|
|
746
751
|
accessorKey: 'websiteUrl',
|
|
747
752
|
header: t('customers.companies.detail.fields.website', 'Website'),
|
|
748
|
-
meta: {
|
|
753
|
+
meta: {
|
|
754
|
+
columnChooserGroup: 'Profile',
|
|
755
|
+
hidden: true,
|
|
756
|
+
filterKey: 'company_profile.website_url',
|
|
757
|
+
filterGroup: 'Profile',
|
|
758
|
+
},
|
|
749
759
|
cell: ({ row }) => row.original.websiteUrl || noValue,
|
|
750
760
|
},
|
|
751
761
|
{
|
|
752
762
|
accessorKey: 'industry',
|
|
753
763
|
header: t('customers.companies.detail.fields.industry', 'Industry'),
|
|
754
|
-
meta: {
|
|
764
|
+
meta: {
|
|
765
|
+
columnChooserGroup: 'Profile',
|
|
766
|
+
hidden: true,
|
|
767
|
+
filterKey: 'company_profile.industry',
|
|
768
|
+
filterGroup: 'Profile',
|
|
769
|
+
},
|
|
755
770
|
cell: ({ row }) => row.original.industry || noValue,
|
|
756
771
|
},
|
|
757
772
|
{
|
|
758
773
|
accessorKey: 'sizeBucket',
|
|
759
774
|
header: t('customers.companies.detail.fields.sizeBucket', 'Company size'),
|
|
760
|
-
meta: {
|
|
775
|
+
meta: {
|
|
776
|
+
columnChooserGroup: 'Profile',
|
|
777
|
+
hidden: true,
|
|
778
|
+
filterKey: 'company_profile.size_bucket',
|
|
779
|
+
filterGroup: 'Profile',
|
|
780
|
+
},
|
|
761
781
|
cell: ({ row }) => row.original.sizeBucket || noValue,
|
|
762
782
|
},
|
|
763
783
|
{
|
|
@@ -768,13 +788,19 @@ export default function CustomersCompaniesPage() {
|
|
|
768
788
|
hidden: true,
|
|
769
789
|
filterKey: 'company_profile.annual_revenue',
|
|
770
790
|
filterType: 'number' as const,
|
|
791
|
+
filterGroup: 'Profile',
|
|
771
792
|
},
|
|
772
793
|
cell: ({ row }) => row.original.annualRevenue || noValue,
|
|
773
794
|
},
|
|
774
795
|
{
|
|
775
796
|
accessorKey: 'description',
|
|
776
797
|
header: t('customers.companies.detail.fields.description', 'Description'),
|
|
777
|
-
meta: {
|
|
798
|
+
meta: {
|
|
799
|
+
columnChooserGroup: 'Notes',
|
|
800
|
+
hidden: true,
|
|
801
|
+
filterKey: 'description',
|
|
802
|
+
filterGroup: 'Notes',
|
|
803
|
+
},
|
|
778
804
|
cell: ({ row }) => row.original.description || noValue,
|
|
779
805
|
},
|
|
780
806
|
]
|
|
@@ -796,7 +822,31 @@ export default function CustomersCompaniesPage() {
|
|
|
796
822
|
}))
|
|
797
823
|
|
|
798
824
|
return [...baseColumns, ...customColumns]
|
|
799
|
-
}, [customFieldDefs, dictionaryMaps, t])
|
|
825
|
+
}, [customFieldDefs, dictionaryMaps, dictionaryOptions, loadOwnerFilterOptions, resolvedOwnerFilterOptions, t])
|
|
826
|
+
|
|
827
|
+
const { advancedFilterFields } = useAutoDiscoveredFields({ columns, customFieldDefs })
|
|
828
|
+
|
|
829
|
+
// Sync auto-discovered fields into the `filterPanel` declared at the top of
|
|
830
|
+
// the component. See the comment on the `panelFields` state for why this
|
|
831
|
+
// late-binding is safe. Bail out by content (field-key list) — every render
|
|
832
|
+
// of `useAutoDiscoveredFields` produces fresh `FilterFieldDef` object refs
|
|
833
|
+
// even when the set of fields hasn't actually changed, so a naive reference
|
|
834
|
+
// setState would loop ("Maximum update depth exceeded").
|
|
835
|
+
React.useEffect(() => {
|
|
836
|
+
setPanelFields((prev) => {
|
|
837
|
+
if (prev === advancedFilterFields) return prev
|
|
838
|
+
if (prev.length === advancedFilterFields.length) {
|
|
839
|
+
let same = true
|
|
840
|
+
for (let i = 0; i < prev.length; i++) {
|
|
841
|
+
if (prev[i].key !== advancedFilterFields[i].key) { same = false; break }
|
|
842
|
+
}
|
|
843
|
+
if (same) return prev
|
|
844
|
+
}
|
|
845
|
+
return advancedFilterFields
|
|
846
|
+
})
|
|
847
|
+
}, [advancedFilterFields])
|
|
848
|
+
|
|
849
|
+
const companiesPresets = React.useMemo<FilterPreset[]>(() => makeCompaniesPresets(), [])
|
|
800
850
|
|
|
801
851
|
return (
|
|
802
852
|
<Page>
|
|
@@ -823,10 +873,6 @@ export default function CustomersCompaniesPage() {
|
|
|
823
873
|
searchValue={search}
|
|
824
874
|
onSearchChange={(value) => { setSearch(value); setPage(1) }}
|
|
825
875
|
searchPlaceholder={t('customers.companies.list.searchPlaceholder')}
|
|
826
|
-
filters={filters}
|
|
827
|
-
filterValues={filterValues}
|
|
828
|
-
onFiltersApply={handleFiltersApply}
|
|
829
|
-
onFiltersClear={handleFiltersClear}
|
|
830
876
|
entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}
|
|
831
877
|
onRowClick={(row) => router.push(`/backend/customers/companies-v2/${row.id}`)}
|
|
832
878
|
perspective={{ tableId: 'customers.companies.list' }}
|
|
@@ -864,16 +910,54 @@ export default function CustomersCompaniesPage() {
|
|
|
864
910
|
/>
|
|
865
911
|
)}
|
|
866
912
|
advancedFilter={{
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
913
|
+
auto: true,
|
|
914
|
+
value: filterPanel.tree,
|
|
915
|
+
onChange: filterPanel.setTree,
|
|
916
|
+
onApply: () => filterPanel.flush(),
|
|
917
|
+
onClear: handleAdvancedFilterClear,
|
|
918
|
+
triggerRef: filtersTriggerRef,
|
|
919
|
+
externalPopover: true,
|
|
920
|
+
onTriggerClick: () => setFiltersOpen((prev) => !prev),
|
|
921
|
+
onApplyTree: (tree) => {
|
|
922
|
+
filterPanel.replaceTree(tree)
|
|
923
|
+
setPage(1)
|
|
924
|
+
},
|
|
925
|
+
}}
|
|
926
|
+
activeFilterChips={(
|
|
927
|
+
<ActiveFilterChips
|
|
928
|
+
tree={filterPanel.tree}
|
|
929
|
+
fields={advancedFilterFields}
|
|
930
|
+
popoverOpen={filtersOpen}
|
|
931
|
+
onRemoveNode={(id) => filterPanel.dispatch({ type: 'removeNode', nodeId: id })}
|
|
932
|
+
onOpen={() => setFiltersOpen(true)}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
935
|
+
filterAwareEmptyState={{
|
|
936
|
+
active: advancedFilterState.root.children.length > 0,
|
|
937
|
+
entityNamePlural: t('customers.companies.entityPlural', 'companies'),
|
|
938
|
+
canRemoveLast: filterPanel.tree.root.children.length > 0,
|
|
939
|
+
onClearAll: handleAdvancedFilterClear,
|
|
940
|
+
onRemoveLast: () => filterPanel.dispatch({ type: 'removeLast' }),
|
|
941
|
+
}}
|
|
873
942
|
virtualized
|
|
874
943
|
pagination={{ page, pageSize, total, totalPages, onPageChange: setPage, pageSizeOptions: [10, 25, 50, 100], onPageSizeChange: handlePageSizeChange, cacheStatus }}
|
|
875
944
|
isLoading={isLoading}
|
|
876
945
|
/>
|
|
946
|
+
<AdvancedFilterPanel
|
|
947
|
+
fields={advancedFilterFields}
|
|
948
|
+
value={filterPanel.tree}
|
|
949
|
+
onChange={filterPanel.setTree}
|
|
950
|
+
onApply={filterPanel.flush}
|
|
951
|
+
onClear={handleAdvancedFilterClear}
|
|
952
|
+
onFlush={filterPanel.flush}
|
|
953
|
+
pendingErrors={filterPanel.pendingErrors}
|
|
954
|
+
userId={currentUserId}
|
|
955
|
+
presets={companiesPresets}
|
|
956
|
+
open={filtersOpen}
|
|
957
|
+
onOpenChange={setFiltersOpen}
|
|
958
|
+
triggerRef={filtersTriggerRef}
|
|
959
|
+
savedFilterStorageKey="customers.companies.list"
|
|
960
|
+
/>
|
|
877
961
|
</PageBody>
|
|
878
962
|
{ConfirmDialogElement}
|
|
879
963
|
</Page>
|