@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.
- 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/salesUi.js +18 -17
- package/dist/helpers/integration/salesUi.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/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/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/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/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 +1 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/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/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/salesUi.ts +27 -23
- 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/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/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/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/perspectives/services/perspectiveService.ts +30 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -0
- package/src/modules/query_index/lib/engine.ts +46 -8
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
6
6
|
import { useQueryClient } from "@tanstack/react-query";
|
|
7
7
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
8
8
|
import { DataTable, withDataTableNamespaces } from "@open-mercato/ui/backend/DataTable";
|
|
9
|
-
import {
|
|
9
|
+
import { createEmptyTree, makeRuleTree, makeMultiRuleTree } from "@open-mercato/shared/lib/query/advanced-filter-tree";
|
|
10
|
+
import { deserializeTree, deserializeAdvancedFilter, flatToTree, mapDictionaryColorToTone, serializeTree } from "@open-mercato/shared/lib/query/advanced-filter";
|
|
11
|
+
import { useCurrentUserId } from "@open-mercato/ui/backend/utils/useCurrentUserId";
|
|
10
12
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
13
|
import { buildCrudExportUrl, deleteCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
12
14
|
import { groupBulkDeleteFailures, runBulkDelete } from "@open-mercato/ui/backend/utils/bulkDelete";
|
|
@@ -35,31 +37,51 @@ import {
|
|
|
35
37
|
supportsCustomFieldColumn
|
|
36
38
|
} from "@open-mercato/ui/backend/utils/customFieldColumns";
|
|
37
39
|
import { CollectionPreviewCell, normalizeCollectionLabels } from "../../../components/list/CollectionPreviewCell.js";
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
import { useAutoDiscoveredFields } from "@open-mercato/ui/backend/utils/useAutoDiscoveredFields";
|
|
41
|
+
import { useAdvancedFilterTree } from "@open-mercato/ui/backend/hooks/useAdvancedFilter";
|
|
42
|
+
import { AdvancedFilterPanel } from "@open-mercato/ui/backend/filters/AdvancedFilterPanel";
|
|
43
|
+
import { ActiveFilterChips } from "@open-mercato/ui/backend/filters/ActiveFilterChips";
|
|
44
|
+
import {
|
|
45
|
+
ensureCurrentUserFilterOption,
|
|
46
|
+
fetchAssignableStaffMembers,
|
|
47
|
+
mapAssignableStaffToFilterOptions
|
|
48
|
+
} from "../../../components/detail/assignableStaff.js";
|
|
49
|
+
function makeDealsPresets() {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
id: "my-deals",
|
|
53
|
+
labelKey: "customers.deals.presets.myDeals",
|
|
54
|
+
requiresUser: true,
|
|
55
|
+
build: ({ userId }) => makeRuleTree({ field: "owner_user_id", operator: "is", value: userId })
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "closing-month",
|
|
59
|
+
labelKey: "customers.deals.presets.closingMonth",
|
|
60
|
+
iconName: "clock",
|
|
61
|
+
build: ({ now }) => {
|
|
62
|
+
const start = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);
|
|
63
|
+
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().slice(0, 10);
|
|
64
|
+
return makeRuleTree({ field: "expected_close_at", operator: "between", value: [start, end] });
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
// The Deal entity has no dedicated "at risk" or health-score field — `customer_deals`
|
|
68
|
+
// exposes only `status` (open/win/loose/closed/in_progress, dictionary-driven) and
|
|
69
|
+
// `closure_outcome`. Rather than fabricate a mapping, the "At risk" preset is omitted
|
|
70
|
+
// until the data model exposes a first-class signal.
|
|
71
|
+
{
|
|
72
|
+
id: "won-quarter",
|
|
73
|
+
labelKey: "customers.deals.presets.wonQuarter",
|
|
74
|
+
build: ({ now }) => {
|
|
75
|
+
const quarter = Math.floor(now.getMonth() / 3);
|
|
76
|
+
const start = new Date(now.getFullYear(), quarter * 3, 1).toISOString().slice(0, 10);
|
|
77
|
+
return makeMultiRuleTree([
|
|
78
|
+
{ field: "status", operator: "is", value: "win" },
|
|
79
|
+
{ field: "expected_close_at", operator: "is_after", value: start }
|
|
80
|
+
], "and");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
];
|
|
57
84
|
}
|
|
58
|
-
const EMPTY_OPTIONS_STATE = {
|
|
59
|
-
options: [],
|
|
60
|
-
idToLabel: {},
|
|
61
|
-
labelToId: {}
|
|
62
|
-
};
|
|
63
85
|
const PAGE_SIZE = 20;
|
|
64
86
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
65
87
|
function isUuid(value) {
|
|
@@ -80,93 +102,6 @@ function extractIdsFromParams(params, key) {
|
|
|
80
102
|
const values = params.getAll(key);
|
|
81
103
|
return normalizeIdCandidates(values);
|
|
82
104
|
}
|
|
83
|
-
function ensureUniqueLabel(base, occupied) {
|
|
84
|
-
const trimmed = base.trim() || "Unnamed";
|
|
85
|
-
if (!occupied.has(trimmed)) {
|
|
86
|
-
occupied.add(trimmed);
|
|
87
|
-
return trimmed;
|
|
88
|
-
}
|
|
89
|
-
let counter = 2;
|
|
90
|
-
let candidate = `${trimmed} \u2022 ${counter}`;
|
|
91
|
-
while (occupied.has(candidate)) {
|
|
92
|
-
counter += 1;
|
|
93
|
-
candidate = `${trimmed} \u2022 ${counter}`;
|
|
94
|
-
}
|
|
95
|
-
occupied.add(candidate);
|
|
96
|
-
return candidate;
|
|
97
|
-
}
|
|
98
|
-
async function fetchPeopleLookup(query) {
|
|
99
|
-
const search = new URLSearchParams();
|
|
100
|
-
search.set("page", "1");
|
|
101
|
-
search.set("pageSize", "20");
|
|
102
|
-
if (query && query.trim().length) search.set("search", query.trim());
|
|
103
|
-
try {
|
|
104
|
-
const call = await apiCall(`/api/customers/people?${search.toString()}`);
|
|
105
|
-
if (!call.ok) return [];
|
|
106
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
107
|
-
return items.map((item) => parsePersonLookupRecord(item)).filter((record) => record !== null);
|
|
108
|
-
} catch {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async function fetchPeopleLookupByIds(ids) {
|
|
113
|
-
const unique = Array.from(new Set(ids.filter((id) => isUuid(id))));
|
|
114
|
-
if (!unique.length) return [];
|
|
115
|
-
const results = await Promise.all(
|
|
116
|
-
unique.map(async (id) => {
|
|
117
|
-
const search = new URLSearchParams();
|
|
118
|
-
search.set("id", id);
|
|
119
|
-
search.set("page", "1");
|
|
120
|
-
search.set("pageSize", "1");
|
|
121
|
-
try {
|
|
122
|
-
const call = await apiCall(`/api/customers/people?${search.toString()}`);
|
|
123
|
-
if (!call.ok) return null;
|
|
124
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
125
|
-
const match = items.map((item) => parsePersonLookupRecord(item)).find((record) => record?.id === id);
|
|
126
|
-
return match ?? null;
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
return results.filter((record) => !!record);
|
|
133
|
-
}
|
|
134
|
-
async function fetchCompaniesLookup(query) {
|
|
135
|
-
const search = new URLSearchParams();
|
|
136
|
-
search.set("page", "1");
|
|
137
|
-
search.set("pageSize", "20");
|
|
138
|
-
if (query && query.trim().length) search.set("search", query.trim());
|
|
139
|
-
try {
|
|
140
|
-
const call = await apiCall(`/api/customers/companies?${search.toString()}`);
|
|
141
|
-
if (!call.ok) return [];
|
|
142
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
143
|
-
return items.map((item) => parseCompanyLookupRecord(item)).filter((record) => record !== null);
|
|
144
|
-
} catch {
|
|
145
|
-
return [];
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
async function fetchCompaniesLookupByIds(ids) {
|
|
149
|
-
const unique = Array.from(new Set(ids.filter((id) => isUuid(id))));
|
|
150
|
-
if (!unique.length) return [];
|
|
151
|
-
const results = await Promise.all(
|
|
152
|
-
unique.map(async (id) => {
|
|
153
|
-
const search = new URLSearchParams();
|
|
154
|
-
search.set("id", id);
|
|
155
|
-
search.set("page", "1");
|
|
156
|
-
search.set("pageSize", "1");
|
|
157
|
-
try {
|
|
158
|
-
const call = await apiCall(`/api/customers/companies?${search.toString()}`);
|
|
159
|
-
if (!call.ok) return null;
|
|
160
|
-
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
161
|
-
const match = items.map((item) => parseCompanyLookupRecord(item)).find((record) => record?.id === id);
|
|
162
|
-
return match ?? null;
|
|
163
|
-
} catch {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
);
|
|
168
|
-
return results.filter((record) => !!record);
|
|
169
|
-
}
|
|
170
105
|
function formatCurrency(amount, currency, fallback) {
|
|
171
106
|
if (typeof amount !== "number" || Number.isNaN(amount)) return fallback;
|
|
172
107
|
try {
|
|
@@ -186,13 +121,6 @@ function formatDateValue(value, fallback) {
|
|
|
186
121
|
if (Number.isNaN(date.getTime())) return fallback;
|
|
187
122
|
return date.toLocaleDateString();
|
|
188
123
|
}
|
|
189
|
-
function arraysEqual(a, b) {
|
|
190
|
-
if (a.length !== b.length) return false;
|
|
191
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
192
|
-
if (a[i] !== b[i]) return false;
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
124
|
function CustomersDealsPage() {
|
|
197
125
|
const t = useT();
|
|
198
126
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
@@ -214,17 +142,31 @@ function CustomersDealsPage() {
|
|
|
214
142
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
215
143
|
const [reloadToken, setReloadToken] = React.useState(0);
|
|
216
144
|
const [pendingDeleteId, setPendingDeleteId] = React.useState(null);
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
const params = searchParams;
|
|
220
|
-
if (!params) return { logic: "and", conditions: [] };
|
|
145
|
+
const initialFilterTree = React.useMemo(() => {
|
|
146
|
+
if (!searchParams) return createEmptyTree();
|
|
221
147
|
const record = {};
|
|
222
|
-
|
|
148
|
+
searchParams.forEach((value, key) => {
|
|
223
149
|
if (key.startsWith("filter[")) record[key] = value;
|
|
224
150
|
});
|
|
225
|
-
const
|
|
226
|
-
|
|
151
|
+
const v2 = deserializeTree(record);
|
|
152
|
+
if (v2) return v2;
|
|
153
|
+
const flat = deserializeAdvancedFilter(record);
|
|
154
|
+
if (flat) return flatToTree(flat);
|
|
155
|
+
return createEmptyTree();
|
|
156
|
+
}, []);
|
|
157
|
+
const [panelFields, setPanelFields] = React.useState([]);
|
|
158
|
+
const [filtersOpen, setFiltersOpen] = React.useState(false);
|
|
159
|
+
const filtersTriggerRef = React.useRef(null);
|
|
160
|
+
const filterPanel = useAdvancedFilterTree({
|
|
161
|
+
initial: initialFilterTree,
|
|
162
|
+
fields: panelFields,
|
|
163
|
+
onApply: () => setPage(1)
|
|
227
164
|
});
|
|
165
|
+
const advancedFilterState = filterPanel.appliedTree;
|
|
166
|
+
const handleAdvancedFilterClear = React.useCallback(() => {
|
|
167
|
+
filterPanel.clear();
|
|
168
|
+
setPage(1);
|
|
169
|
+
}, [filterPanel]);
|
|
228
170
|
const [cacheStatus, setCacheStatus] = React.useState(null);
|
|
229
171
|
const initialPersonIds = React.useMemo(
|
|
230
172
|
() => extractIdsFromParams(searchParams, "personId"),
|
|
@@ -236,142 +178,6 @@ function CustomersDealsPage() {
|
|
|
236
178
|
);
|
|
237
179
|
const [selectedPersonIds, setSelectedPersonIds] = React.useState(initialPersonIds);
|
|
238
180
|
const [selectedCompanyIds, setSelectedCompanyIds] = React.useState(initialCompanyIds);
|
|
239
|
-
const [peopleState, setPeopleState] = React.useState(EMPTY_OPTIONS_STATE);
|
|
240
|
-
const [companiesState, setCompaniesState] = React.useState(EMPTY_OPTIONS_STATE);
|
|
241
|
-
const peopleCacheRef = React.useRef(/* @__PURE__ */ new Map());
|
|
242
|
-
const companiesCacheRef = React.useRef(/* @__PURE__ */ new Map());
|
|
243
|
-
const buildPersonLabel = React.useCallback((record) => {
|
|
244
|
-
const parts = [];
|
|
245
|
-
const name = record.name?.trim();
|
|
246
|
-
if (name) parts.push(name);
|
|
247
|
-
const email = record.email?.trim();
|
|
248
|
-
if (email && !parts.includes(email)) parts.push(email);
|
|
249
|
-
const phone = record.phone?.trim();
|
|
250
|
-
if (!parts.length && phone) parts.push(phone);
|
|
251
|
-
if (!parts.length) parts.push(t("customers.deals.list.unnamedPerson", "Unnamed person"));
|
|
252
|
-
return parts.join(" \u2022 ");
|
|
253
|
-
}, [t]);
|
|
254
|
-
const buildCompanyLabel = React.useCallback((record) => {
|
|
255
|
-
const parts = [];
|
|
256
|
-
const name = record.name?.trim();
|
|
257
|
-
if (name) parts.push(name);
|
|
258
|
-
const domain = record.domain?.trim();
|
|
259
|
-
if (domain && !parts.includes(domain)) parts.push(domain);
|
|
260
|
-
const email = record.email?.trim();
|
|
261
|
-
if (!parts.length && email) parts.push(email);
|
|
262
|
-
if (!parts.length) parts.push(t("customers.deals.list.unnamedCompany", "Unnamed company"));
|
|
263
|
-
return parts.join(" \u2022 ");
|
|
264
|
-
}, [t]);
|
|
265
|
-
const ingestPeopleRecords = React.useCallback((records) => {
|
|
266
|
-
if (!records.length) return [];
|
|
267
|
-
const queryMap = /* @__PURE__ */ new Map();
|
|
268
|
-
setPeopleState((prev) => {
|
|
269
|
-
const idToLabel = { ...prev.idToLabel };
|
|
270
|
-
const labelToId = {};
|
|
271
|
-
const merged = new Map(prev.options.map((opt) => [opt.value, opt]));
|
|
272
|
-
const occupied = /* @__PURE__ */ new Set();
|
|
273
|
-
Object.entries(prev.labelToId).forEach(([label, id]) => {
|
|
274
|
-
occupied.add(label);
|
|
275
|
-
labelToId[label] = id;
|
|
276
|
-
});
|
|
277
|
-
records.forEach((record) => {
|
|
278
|
-
if (!isUuid(record.id)) return;
|
|
279
|
-
const base = buildPersonLabel(record);
|
|
280
|
-
let previousLabel = idToLabel[record.id];
|
|
281
|
-
if (previousLabel) {
|
|
282
|
-
delete labelToId[previousLabel];
|
|
283
|
-
occupied.delete(previousLabel);
|
|
284
|
-
}
|
|
285
|
-
const label = ensureUniqueLabel(base, occupied);
|
|
286
|
-
idToLabel[record.id] = label;
|
|
287
|
-
labelToId[label] = record.id;
|
|
288
|
-
const option = { value: record.id, label };
|
|
289
|
-
merged.set(record.id, option);
|
|
290
|
-
queryMap.set(record.id, option);
|
|
291
|
-
});
|
|
292
|
-
const nextOptions = Array.from(merged.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
293
|
-
return { options: nextOptions, idToLabel, labelToId };
|
|
294
|
-
});
|
|
295
|
-
return Array.from(queryMap.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
296
|
-
}, [buildPersonLabel]);
|
|
297
|
-
const ingestCompanyRecords = React.useCallback((records) => {
|
|
298
|
-
if (!records.length) return [];
|
|
299
|
-
const queryMap = /* @__PURE__ */ new Map();
|
|
300
|
-
setCompaniesState((prev) => {
|
|
301
|
-
const idToLabel = { ...prev.idToLabel };
|
|
302
|
-
const labelToId = {};
|
|
303
|
-
const merged = new Map(prev.options.map((opt) => [opt.value, opt]));
|
|
304
|
-
const occupied = /* @__PURE__ */ new Set();
|
|
305
|
-
Object.entries(prev.labelToId).forEach(([label, id]) => {
|
|
306
|
-
occupied.add(label);
|
|
307
|
-
labelToId[label] = id;
|
|
308
|
-
});
|
|
309
|
-
records.forEach((record) => {
|
|
310
|
-
if (!isUuid(record.id)) return;
|
|
311
|
-
const base = buildCompanyLabel(record);
|
|
312
|
-
let previousLabel = idToLabel[record.id];
|
|
313
|
-
if (previousLabel) {
|
|
314
|
-
delete labelToId[previousLabel];
|
|
315
|
-
occupied.delete(previousLabel);
|
|
316
|
-
}
|
|
317
|
-
const label = ensureUniqueLabel(base, occupied);
|
|
318
|
-
idToLabel[record.id] = label;
|
|
319
|
-
labelToId[label] = record.id;
|
|
320
|
-
const option = { value: record.id, label };
|
|
321
|
-
merged.set(record.id, option);
|
|
322
|
-
queryMap.set(record.id, option);
|
|
323
|
-
});
|
|
324
|
-
const nextOptions = Array.from(merged.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
325
|
-
return { options: nextOptions, idToLabel, labelToId };
|
|
326
|
-
});
|
|
327
|
-
return Array.from(queryMap.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
328
|
-
}, [buildCompanyLabel]);
|
|
329
|
-
const loadPeopleOptions = React.useCallback(async (query) => {
|
|
330
|
-
const normalizedQuery = (query || "").trim().toLowerCase();
|
|
331
|
-
const cacheKey = `${scopeVersion}|${normalizedQuery}`;
|
|
332
|
-
const cached = peopleCacheRef.current.get(cacheKey);
|
|
333
|
-
if (cached) return cached;
|
|
334
|
-
const records = await fetchPeopleLookup(query);
|
|
335
|
-
const options = ingestPeopleRecords(records);
|
|
336
|
-
peopleCacheRef.current.set(cacheKey, options);
|
|
337
|
-
return options;
|
|
338
|
-
}, [scopeVersion, ingestPeopleRecords]);
|
|
339
|
-
const loadCompanyOptions = React.useCallback(async (query) => {
|
|
340
|
-
const normalizedQuery = (query || "").trim().toLowerCase();
|
|
341
|
-
const cacheKey = `${scopeVersion}|${normalizedQuery}`;
|
|
342
|
-
const cached = companiesCacheRef.current.get(cacheKey);
|
|
343
|
-
if (cached) return cached;
|
|
344
|
-
const records = await fetchCompaniesLookup(query);
|
|
345
|
-
const options = ingestCompanyRecords(records);
|
|
346
|
-
companiesCacheRef.current.set(cacheKey, options);
|
|
347
|
-
return options;
|
|
348
|
-
}, [scopeVersion, ingestCompanyRecords]);
|
|
349
|
-
React.useEffect(() => {
|
|
350
|
-
let cancelled = false;
|
|
351
|
-
if (!selectedPersonIds.length) return;
|
|
352
|
-
const missing = selectedPersonIds.filter((id) => !peopleState.idToLabel[id]);
|
|
353
|
-
if (!missing.length) return;
|
|
354
|
-
fetchPeopleLookupByIds(missing).then((records) => {
|
|
355
|
-
if (cancelled) return;
|
|
356
|
-
ingestPeopleRecords(records);
|
|
357
|
-
});
|
|
358
|
-
return () => {
|
|
359
|
-
cancelled = true;
|
|
360
|
-
};
|
|
361
|
-
}, [selectedPersonIds, peopleState.idToLabel, ingestPeopleRecords]);
|
|
362
|
-
React.useEffect(() => {
|
|
363
|
-
let cancelled = false;
|
|
364
|
-
if (!selectedCompanyIds.length) return;
|
|
365
|
-
const missing = selectedCompanyIds.filter((id) => !companiesState.idToLabel[id]);
|
|
366
|
-
if (!missing.length) return;
|
|
367
|
-
fetchCompaniesLookupByIds(missing).then((records) => {
|
|
368
|
-
if (cancelled) return;
|
|
369
|
-
ingestCompanyRecords(records);
|
|
370
|
-
});
|
|
371
|
-
return () => {
|
|
372
|
-
cancelled = true;
|
|
373
|
-
};
|
|
374
|
-
}, [selectedCompanyIds, companiesState.idToLabel, ingestCompanyRecords]);
|
|
375
181
|
const [dictionaryMaps, setDictionaryMaps] = React.useState({
|
|
376
182
|
"deal-statuses": {},
|
|
377
183
|
"pipeline-stages": {}
|
|
@@ -400,6 +206,18 @@ function CustomersDealsPage() {
|
|
|
400
206
|
cancelled = true;
|
|
401
207
|
};
|
|
402
208
|
}, [fetchDictionaryEntries, reloadToken]);
|
|
209
|
+
const dictionaryOptions = React.useMemo(() => {
|
|
210
|
+
const toOptions = (map) => Object.values(map ?? {}).map((entry) => {
|
|
211
|
+
const tone = mapDictionaryColorToTone(entry.color);
|
|
212
|
+
const option = { value: entry.value, label: entry.label };
|
|
213
|
+
if (tone) option.tone = tone;
|
|
214
|
+
return option;
|
|
215
|
+
}).sort((a, b) => a.label.localeCompare(b.label, void 0, { sensitivity: "base" }));
|
|
216
|
+
return {
|
|
217
|
+
dealStatuses: toOptions(dictionaryMaps["deal-statuses"]),
|
|
218
|
+
pipelineStages: toOptions(dictionaryMaps["pipeline-stages"])
|
|
219
|
+
};
|
|
220
|
+
}, [dictionaryMaps]);
|
|
403
221
|
React.useEffect(() => {
|
|
404
222
|
let cancelled = false;
|
|
405
223
|
async function loadPipelines() {
|
|
@@ -423,70 +241,10 @@ function CustomersDealsPage() {
|
|
|
423
241
|
cancelled = true;
|
|
424
242
|
};
|
|
425
243
|
}, [reloadToken, scopeVersion]);
|
|
426
|
-
React.useEffect(() => {
|
|
427
|
-
peopleCacheRef.current.clear();
|
|
428
|
-
companiesCacheRef.current.clear();
|
|
429
|
-
setPeopleState((prev) => {
|
|
430
|
-
if (!prev.options.length && !Object.keys(prev.idToLabel).length) return prev;
|
|
431
|
-
return { ...EMPTY_OPTIONS_STATE };
|
|
432
|
-
});
|
|
433
|
-
setCompaniesState((prev) => {
|
|
434
|
-
if (!prev.options.length && !Object.keys(prev.idToLabel).length) return prev;
|
|
435
|
-
return { ...EMPTY_OPTIONS_STATE };
|
|
436
|
-
});
|
|
437
|
-
}, [scopeVersion, reloadToken]);
|
|
438
|
-
const syncFilterIds = React.useCallback((key, ids) => {
|
|
439
|
-
setFilterValues((prev) => {
|
|
440
|
-
const current = Array.isArray(prev[key]) ? prev[key] : [];
|
|
441
|
-
if (!ids.length) {
|
|
442
|
-
if (!current.length) return prev;
|
|
443
|
-
const next = { ...prev };
|
|
444
|
-
delete next[key];
|
|
445
|
-
return next;
|
|
446
|
-
}
|
|
447
|
-
if (arraysEqual(current, ids)) return prev;
|
|
448
|
-
return { ...prev, [key]: [...ids] };
|
|
449
|
-
});
|
|
450
|
-
}, []);
|
|
451
|
-
React.useEffect(() => {
|
|
452
|
-
syncFilterIds("people", selectedPersonIds);
|
|
453
|
-
}, [selectedPersonIds, syncFilterIds]);
|
|
454
|
-
React.useEffect(() => {
|
|
455
|
-
syncFilterIds("companies", selectedCompanyIds);
|
|
456
|
-
}, [selectedCompanyIds, syncFilterIds]);
|
|
457
244
|
const handleSearchChange = React.useCallback((value) => {
|
|
458
245
|
setSearch(value.trim());
|
|
459
246
|
setPage(1);
|
|
460
247
|
}, []);
|
|
461
|
-
const handleFiltersApply = React.useCallback((values) => {
|
|
462
|
-
const next = { ...values };
|
|
463
|
-
const rawPeople = Array.isArray(values.people) ? values.people : [];
|
|
464
|
-
const nextPersonIds = Array.from(
|
|
465
|
-
new Set(
|
|
466
|
-
rawPeople.map((value) => typeof value === "string" ? value.trim() : "").filter((value) => value.length > 0 && isUuid(value))
|
|
467
|
-
)
|
|
468
|
-
);
|
|
469
|
-
setSelectedPersonIds(nextPersonIds);
|
|
470
|
-
if (nextPersonIds.length) next.people = nextPersonIds;
|
|
471
|
-
else delete next.people;
|
|
472
|
-
const rawCompanies = Array.isArray(values.companies) ? values.companies : [];
|
|
473
|
-
const nextCompanyIds = Array.from(
|
|
474
|
-
new Set(
|
|
475
|
-
rawCompanies.map((value) => typeof value === "string" ? value.trim() : "").filter((value) => value.length > 0 && isUuid(value))
|
|
476
|
-
)
|
|
477
|
-
);
|
|
478
|
-
setSelectedCompanyIds(nextCompanyIds);
|
|
479
|
-
if (nextCompanyIds.length) next.companies = nextCompanyIds;
|
|
480
|
-
else delete next.companies;
|
|
481
|
-
setFilterValues(next);
|
|
482
|
-
setPage(1);
|
|
483
|
-
}, []);
|
|
484
|
-
const handleFiltersClear = React.useCallback(() => {
|
|
485
|
-
setFilterValues({});
|
|
486
|
-
setSelectedPersonIds([]);
|
|
487
|
-
setSelectedCompanyIds([]);
|
|
488
|
-
setPage(1);
|
|
489
|
-
}, []);
|
|
490
248
|
const queryParams = React.useMemo(() => {
|
|
491
249
|
const params = new URLSearchParams();
|
|
492
250
|
params.set("page", String(page));
|
|
@@ -498,33 +256,12 @@ function CustomersDealsPage() {
|
|
|
498
256
|
if (search.trim().length) params.set("search", search.trim());
|
|
499
257
|
if (selectedPersonIds.length) params.set("personId", selectedPersonIds.join(","));
|
|
500
258
|
if (selectedCompanyIds.length) params.set("companyId", selectedCompanyIds.join(","));
|
|
501
|
-
|
|
502
|
-
if (key === "people" || key === "companies") return;
|
|
503
|
-
if (value == null) return;
|
|
504
|
-
if (Array.isArray(value)) {
|
|
505
|
-
const normalized = value.map((item) => {
|
|
506
|
-
if (item == null) return "";
|
|
507
|
-
if (typeof item === "string") return item.trim();
|
|
508
|
-
return String(item).trim();
|
|
509
|
-
}).filter((item) => item.length > 0);
|
|
510
|
-
if (normalized.length) params.set(key, normalized.join(","));
|
|
511
|
-
} else if (typeof value === "object") {
|
|
512
|
-
const obj = value;
|
|
513
|
-
const from = typeof obj.from === "string" ? obj.from.trim() : "";
|
|
514
|
-
const to = typeof obj.to === "string" ? obj.to.trim() : "";
|
|
515
|
-
if (from) params.set(`${key}[from]`, from);
|
|
516
|
-
if (to) params.set(`${key}[to]`, to);
|
|
517
|
-
} else {
|
|
518
|
-
const stringValue = typeof value === "string" ? value.trim() : String(value);
|
|
519
|
-
if (stringValue) params.set(key, stringValue);
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
const advancedParams = serializeAdvancedFilter(advancedFilterState);
|
|
259
|
+
const advancedParams = serializeTree(advancedFilterState);
|
|
523
260
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
524
261
|
params.set(key, val);
|
|
525
262
|
}
|
|
526
263
|
return params.toString();
|
|
527
|
-
}, [advancedFilterState,
|
|
264
|
+
}, [advancedFilterState, page, pageSize, search, selectedCompanyIds, selectedPersonIds, sorting]);
|
|
528
265
|
const currentParams = React.useMemo(
|
|
529
266
|
() => Object.fromEntries(new URLSearchParams(queryParams)),
|
|
530
267
|
[queryParams]
|
|
@@ -587,7 +324,7 @@ function CustomersDealsPage() {
|
|
|
587
324
|
if (selectedPersonIds.length) selectedPersonIds.forEach((id) => params.append("personId", id));
|
|
588
325
|
if (selectedCompanyIds.length) selectedCompanyIds.forEach((id) => params.append("companyId", id));
|
|
589
326
|
if (page > 1) params.set("page", String(page));
|
|
590
|
-
const advancedParams =
|
|
327
|
+
const advancedParams = serializeTree(advancedFilterState);
|
|
591
328
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
592
329
|
params.set(key, val);
|
|
593
330
|
}
|
|
@@ -597,8 +334,6 @@ function CustomersDealsPage() {
|
|
|
597
334
|
router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false });
|
|
598
335
|
}, [pathname, router, page, search, selectedPersonIds, selectedCompanyIds, advancedFilterState]);
|
|
599
336
|
const handleRefresh = React.useCallback(() => {
|
|
600
|
-
peopleCacheRef.current.clear();
|
|
601
|
-
companiesCacheRef.current.clear();
|
|
602
337
|
void Promise.all([
|
|
603
338
|
invalidateCustomerDictionary(queryClient, "deal-statuses"),
|
|
604
339
|
invalidateCustomerDictionary(queryClient, "pipeline-stages")
|
|
@@ -724,33 +459,36 @@ function CustomersDealsPage() {
|
|
|
724
459
|
}
|
|
725
460
|
return succeeded.length > 0;
|
|
726
461
|
}, [bulkMutationContextId, confirm, retryBulkMutation, runBulkMutation, t]);
|
|
727
|
-
const personOptions = peopleState.options;
|
|
728
|
-
const companyOptions = companiesState.options;
|
|
729
|
-
const peopleIdToLabel = peopleState.idToLabel;
|
|
730
|
-
const companyIdToLabel = companiesState.idToLabel;
|
|
731
|
-
const filters = React.useMemo(() => [
|
|
732
|
-
{
|
|
733
|
-
id: "people",
|
|
734
|
-
label: t("customers.deals.list.filters.people"),
|
|
735
|
-
type: "tags",
|
|
736
|
-
options: personOptions,
|
|
737
|
-
loadOptions: loadPeopleOptions,
|
|
738
|
-
placeholder: t("customers.deals.list.filters.peoplePlaceholder"),
|
|
739
|
-
formatValue: (value) => peopleIdToLabel[value] ?? value
|
|
740
|
-
},
|
|
741
|
-
{
|
|
742
|
-
id: "companies",
|
|
743
|
-
label: t("customers.deals.list.filters.companies"),
|
|
744
|
-
type: "tags",
|
|
745
|
-
options: companyOptions,
|
|
746
|
-
loadOptions: loadCompanyOptions,
|
|
747
|
-
placeholder: t("customers.deals.list.filters.companiesPlaceholder"),
|
|
748
|
-
formatValue: (value) => companyIdToLabel[value] ?? value
|
|
749
|
-
}
|
|
750
|
-
], [companyIdToLabel, companyOptions, loadCompanyOptions, loadPeopleOptions, peopleIdToLabel, personOptions, t]);
|
|
751
462
|
const { data: customFieldDefs = [] } = useCustomFieldDefs([E.customers.customer_deal], {
|
|
752
463
|
keyExtras: [scopeVersion, reloadToken]
|
|
753
464
|
});
|
|
465
|
+
const currentUserId = useCurrentUserId();
|
|
466
|
+
const [ownerFilterOptions, setOwnerFilterOptions] = React.useState([]);
|
|
467
|
+
React.useEffect(() => {
|
|
468
|
+
const controller = new AbortController();
|
|
469
|
+
let cancelled = false;
|
|
470
|
+
void fetchAssignableStaffMembers("", { pageSize: 100, signal: controller.signal }).then((items) => {
|
|
471
|
+
if (!cancelled) setOwnerFilterOptions(mapAssignableStaffToFilterOptions(items));
|
|
472
|
+
}).catch(() => {
|
|
473
|
+
if (!cancelled) setOwnerFilterOptions([]);
|
|
474
|
+
});
|
|
475
|
+
return () => {
|
|
476
|
+
cancelled = true;
|
|
477
|
+
controller.abort();
|
|
478
|
+
};
|
|
479
|
+
}, [scopeVersion]);
|
|
480
|
+
const resolvedOwnerFilterOptions = React.useMemo(
|
|
481
|
+
() => ensureCurrentUserFilterOption(
|
|
482
|
+
ownerFilterOptions,
|
|
483
|
+
currentUserId,
|
|
484
|
+
t("customers.filters.currentUser", "Current user")
|
|
485
|
+
),
|
|
486
|
+
[currentUserId, ownerFilterOptions, t]
|
|
487
|
+
);
|
|
488
|
+
const loadOwnerFilterOptions = React.useCallback(async (query) => {
|
|
489
|
+
const items = await fetchAssignableStaffMembers(query ?? "", { pageSize: 100 });
|
|
490
|
+
return mapAssignableStaffToFilterOptions(items);
|
|
491
|
+
}, []);
|
|
754
492
|
const columns = React.useMemo(() => {
|
|
755
493
|
const noValue = /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: t("customers.deals.list.noValue") });
|
|
756
494
|
const renderDictionaryCell = (kind, value) => /* @__PURE__ */ jsx(
|
|
@@ -814,6 +552,7 @@ function CustomersDealsPage() {
|
|
|
814
552
|
alwaysVisible: true,
|
|
815
553
|
columnChooserGroup: "Basic Info",
|
|
816
554
|
filterKey: "title",
|
|
555
|
+
filterGroup: "Deal",
|
|
817
556
|
maxWidth: "280px"
|
|
818
557
|
},
|
|
819
558
|
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: row.original.title })
|
|
@@ -821,19 +560,36 @@ function CustomersDealsPage() {
|
|
|
821
560
|
{
|
|
822
561
|
accessorKey: "status",
|
|
823
562
|
header: t("customers.deals.list.columns.status"),
|
|
824
|
-
meta: {
|
|
563
|
+
meta: {
|
|
564
|
+
filterType: "select",
|
|
565
|
+
filterOptions: dictionaryOptions.dealStatuses,
|
|
566
|
+
columnChooserGroup: "Basic Info",
|
|
567
|
+
filterKey: "status",
|
|
568
|
+
filterGroup: "Deal"
|
|
569
|
+
},
|
|
825
570
|
cell: ({ row }) => renderDictionaryCell("deal-statuses", row.original.status)
|
|
826
571
|
},
|
|
827
572
|
{
|
|
828
573
|
accessorKey: "pipelineStage",
|
|
829
574
|
header: t("customers.deals.list.columns.pipelineStage"),
|
|
830
|
-
meta: {
|
|
575
|
+
meta: {
|
|
576
|
+
filterType: "select",
|
|
577
|
+
filterOptions: dictionaryOptions.pipelineStages,
|
|
578
|
+
columnChooserGroup: "Pipeline",
|
|
579
|
+
filterKey: "pipeline_stage",
|
|
580
|
+
filterGroup: "Deal"
|
|
581
|
+
},
|
|
831
582
|
cell: ({ row }) => renderDictionaryCell("pipeline-stages", row.original.pipelineStage)
|
|
832
583
|
},
|
|
833
584
|
{
|
|
834
585
|
accessorKey: "pipelineId",
|
|
835
586
|
header: t("customers.deals.list.columns.pipeline", "Pipeline"),
|
|
836
|
-
meta: {
|
|
587
|
+
meta: {
|
|
588
|
+
columnChooserGroup: "Pipeline",
|
|
589
|
+
filterKey: "pipeline_id",
|
|
590
|
+
filterGroup: "Deal",
|
|
591
|
+
maxWidth: "220px"
|
|
592
|
+
},
|
|
837
593
|
cell: ({ row }) => {
|
|
838
594
|
const name = row.original.pipelineId ? pipelineNames[row.original.pipelineId] : null;
|
|
839
595
|
return name ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: name }) : noValue;
|
|
@@ -842,13 +598,23 @@ function CustomersDealsPage() {
|
|
|
842
598
|
{
|
|
843
599
|
accessorKey: "valueAmount",
|
|
844
600
|
header: t("customers.deals.list.columns.value"),
|
|
845
|
-
meta: {
|
|
601
|
+
meta: {
|
|
602
|
+
filterType: "number",
|
|
603
|
+
columnChooserGroup: "Financial",
|
|
604
|
+
filterKey: "value_amount",
|
|
605
|
+
filterGroup: "Deal"
|
|
606
|
+
},
|
|
846
607
|
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: formatCurrency(row.original.valueAmount ?? null, row.original.valueCurrency ?? null, t("customers.deals.list.noValue")) })
|
|
847
608
|
},
|
|
848
609
|
{
|
|
849
610
|
accessorKey: "probability",
|
|
850
611
|
header: t("customers.deals.list.columns.probability"),
|
|
851
|
-
meta: {
|
|
612
|
+
meta: {
|
|
613
|
+
filterType: "number",
|
|
614
|
+
columnChooserGroup: "Financial",
|
|
615
|
+
filterKey: "probability",
|
|
616
|
+
filterGroup: "Deal"
|
|
617
|
+
},
|
|
852
618
|
cell: ({ row }) => {
|
|
853
619
|
const value = row.original.probability;
|
|
854
620
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -860,15 +626,37 @@ function CustomersDealsPage() {
|
|
|
860
626
|
{
|
|
861
627
|
accessorKey: "expectedCloseAt",
|
|
862
628
|
header: t("customers.deals.list.columns.expectedClose"),
|
|
863
|
-
meta: {
|
|
629
|
+
meta: {
|
|
630
|
+
columnChooserGroup: "Dates",
|
|
631
|
+
filterKey: "expected_close_at",
|
|
632
|
+
filterGroup: "Activity",
|
|
633
|
+
filterIconName: "calendar"
|
|
634
|
+
},
|
|
864
635
|
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: formatDateValue(row.original.expectedCloseAt ?? null, t("customers.deals.list.noValue")) })
|
|
865
636
|
},
|
|
637
|
+
{
|
|
638
|
+
accessorKey: "ownerUserId",
|
|
639
|
+
header: t("customers.deals.list.columns.owner", "Owner"),
|
|
640
|
+
meta: {
|
|
641
|
+
columnChooserGroup: "CRM",
|
|
642
|
+
filterType: "select",
|
|
643
|
+
filterOptions: resolvedOwnerFilterOptions,
|
|
644
|
+
filterLoadOptions: loadOwnerFilterOptions,
|
|
645
|
+
filterGroup: "CRM",
|
|
646
|
+
filterIconName: "user-round",
|
|
647
|
+
filterKey: "owner_user_id",
|
|
648
|
+
hidden: true
|
|
649
|
+
},
|
|
650
|
+
cell: ({ row }) => row.original.ownerUserId ?? null
|
|
651
|
+
},
|
|
866
652
|
{
|
|
867
653
|
accessorKey: "companies",
|
|
868
654
|
header: t("customers.deals.list.columns.companies"),
|
|
869
655
|
meta: {
|
|
870
656
|
columnChooserGroup: "Associations",
|
|
871
657
|
filterable: false,
|
|
658
|
+
filterGroup: "CRM",
|
|
659
|
+
filterIconName: "building-2",
|
|
872
660
|
maxWidth: "220px",
|
|
873
661
|
tooltipContent: (row) => normalizeCollectionLabels(
|
|
874
662
|
row.companies.map((entry) => entry.label && entry.label.trim().length ? entry.label : t("customers.deals.list.unnamedCompany"))
|
|
@@ -882,6 +670,8 @@ function CustomersDealsPage() {
|
|
|
882
670
|
meta: {
|
|
883
671
|
columnChooserGroup: "Associations",
|
|
884
672
|
filterable: false,
|
|
673
|
+
filterGroup: "CRM",
|
|
674
|
+
filterIconName: "user-round",
|
|
885
675
|
maxWidth: "220px",
|
|
886
676
|
tooltipContent: (row) => normalizeCollectionLabels(
|
|
887
677
|
row.people.map((entry) => entry.label && entry.label.trim().length ? entry.label : t("customers.deals.list.unnamedPerson"))
|
|
@@ -892,109 +682,222 @@ function CustomersDealsPage() {
|
|
|
892
682
|
{
|
|
893
683
|
accessorKey: "updatedAt",
|
|
894
684
|
header: t("customers.deals.list.columns.updatedAt"),
|
|
895
|
-
meta: {
|
|
685
|
+
meta: {
|
|
686
|
+
columnChooserGroup: "Dates",
|
|
687
|
+
filterKey: "updated_at",
|
|
688
|
+
filterGroup: "Activity",
|
|
689
|
+
filterIconName: "calendar"
|
|
690
|
+
},
|
|
896
691
|
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: formatDateValue(row.original.updatedAt ?? null, t("customers.deals.list.noValue")) })
|
|
897
692
|
},
|
|
898
693
|
...customColumns
|
|
899
694
|
];
|
|
900
|
-
}, [customFieldDefs, dictionaryMaps, pipelineNames, t]);
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
695
|
+
}, [customFieldDefs, dictionaryMaps, dictionaryOptions, loadOwnerFilterOptions, pipelineNames, resolvedOwnerFilterOptions, t]);
|
|
696
|
+
const { advancedFilterFields } = useAutoDiscoveredFields({ columns, customFieldDefs });
|
|
697
|
+
React.useEffect(() => {
|
|
698
|
+
setPanelFields((prev) => {
|
|
699
|
+
if (prev === advancedFilterFields) return prev;
|
|
700
|
+
if (prev.length === advancedFilterFields.length) {
|
|
701
|
+
let same = true;
|
|
702
|
+
for (let i = 0; i < prev.length; i++) {
|
|
703
|
+
if (prev[i].key !== advancedFilterFields[i].key) {
|
|
704
|
+
same = false;
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (same) return prev;
|
|
709
|
+
}
|
|
710
|
+
return advancedFilterFields;
|
|
711
|
+
});
|
|
712
|
+
}, [advancedFilterFields]);
|
|
713
|
+
const associationFilterFields = React.useMemo(() => {
|
|
714
|
+
const personLabels = /* @__PURE__ */ new Map();
|
|
715
|
+
const companyLabels = /* @__PURE__ */ new Map();
|
|
716
|
+
for (const row of rows) {
|
|
717
|
+
for (const person of row.people) {
|
|
718
|
+
if (selectedPersonIds.includes(person.id) && person.label.trim().length > 0) {
|
|
719
|
+
personLabels.set(person.id, person.label);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
for (const company of row.companies) {
|
|
723
|
+
if (selectedCompanyIds.includes(company.id) && company.label.trim().length > 0) {
|
|
724
|
+
companyLabels.set(company.id, company.label);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return [
|
|
904
729
|
{
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
730
|
+
key: "people",
|
|
731
|
+
label: t("customers.deals.list.columns.people", "People"),
|
|
732
|
+
type: "select",
|
|
733
|
+
options: selectedPersonIds.map((id) => ({ value: id, label: personLabels.get(id) ?? id }))
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
key: "companies",
|
|
737
|
+
label: t("customers.deals.list.columns.companies", "Companies"),
|
|
738
|
+
type: "select",
|
|
739
|
+
options: selectedCompanyIds.map((id) => ({ value: id, label: companyLabels.get(id) ?? id }))
|
|
740
|
+
}
|
|
741
|
+
];
|
|
742
|
+
}, [rows, selectedCompanyIds, selectedPersonIds, t]);
|
|
743
|
+
const associationFilterTree = React.useMemo(() => {
|
|
744
|
+
const rules = [];
|
|
745
|
+
if (selectedPersonIds.length) rules.push({ field: "people", operator: "is_any_of", value: selectedPersonIds });
|
|
746
|
+
if (selectedCompanyIds.length) rules.push({ field: "companies", operator: "is_any_of", value: selectedCompanyIds });
|
|
747
|
+
return rules.length ? makeMultiRuleTree(rules) : createEmptyTree();
|
|
748
|
+
}, [selectedCompanyIds, selectedPersonIds]);
|
|
749
|
+
const handleAssociationFilterRemove = React.useCallback((nodeId) => {
|
|
750
|
+
const node = associationFilterTree.root.children.find((child) => child.id === nodeId);
|
|
751
|
+
if (!node || node.type !== "rule") return;
|
|
752
|
+
if (node.field === "people") setSelectedPersonIds([]);
|
|
753
|
+
if (node.field === "companies") setSelectedCompanyIds([]);
|
|
754
|
+
setPage(1);
|
|
755
|
+
}, [associationFilterTree.root.children]);
|
|
756
|
+
const dealsPresets = React.useMemo(() => makeDealsPresets(), []);
|
|
757
|
+
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
758
|
+
/* @__PURE__ */ jsxs(PageBody, { children: [
|
|
759
|
+
/* @__PURE__ */ jsx(
|
|
760
|
+
DataTable,
|
|
761
|
+
{
|
|
762
|
+
stickyFirstColumn: true,
|
|
763
|
+
stickyActionsColumn: true,
|
|
764
|
+
title: t("customers.deals.list.title"),
|
|
765
|
+
actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/deals/create", children: t("customers.deals.list.actions.new", "New deal") }) }),
|
|
766
|
+
columns,
|
|
767
|
+
columnChooser: { auto: true },
|
|
768
|
+
data: rows,
|
|
769
|
+
onRowClick: (row) => {
|
|
770
|
+
router.push(`/backend/customers/deals/${row.id}`);
|
|
771
|
+
},
|
|
772
|
+
rowActions: (row) => {
|
|
773
|
+
const isDeleting = pendingDeleteId === row.id;
|
|
774
|
+
return /* @__PURE__ */ jsx(
|
|
775
|
+
RowActions,
|
|
776
|
+
{
|
|
777
|
+
items: [
|
|
778
|
+
{
|
|
779
|
+
id: "edit",
|
|
780
|
+
label: t("customers.deals.list.actions.edit", "Edit"),
|
|
781
|
+
onSelect: () => {
|
|
782
|
+
router.push(`/backend/customers/deals/${row.id}`);
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
id: "open-new-tab",
|
|
787
|
+
label: t("customers.deals.list.actions.openInNewTab", "Open in new tab"),
|
|
788
|
+
onSelect: () => {
|
|
789
|
+
if (typeof window !== "undefined") {
|
|
790
|
+
window.open(`/backend/customers/deals/${row.id}`, "_blank", "noopener");
|
|
791
|
+
}
|
|
934
792
|
}
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
id: "delete",
|
|
796
|
+
label: isDeleting ? t("customers.deals.list.actions.deleting", "Deleting\u2026") : t("customers.deals.list.actions.delete", "Delete"),
|
|
797
|
+
destructive: true,
|
|
798
|
+
onSelect: () => handleDeleteDeal(row.id)
|
|
935
799
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
800
|
+
]
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
},
|
|
804
|
+
sortable: true,
|
|
805
|
+
sorting,
|
|
806
|
+
onSortingChange: setSorting,
|
|
807
|
+
bulkActions: [
|
|
808
|
+
{
|
|
809
|
+
id: "delete",
|
|
810
|
+
label: t("customers.deals.list.actions.delete", "Delete"),
|
|
811
|
+
destructive: true,
|
|
812
|
+
onExecute: handleBulkDelete
|
|
944
813
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
searchValue: search,
|
|
959
|
-
onSearchChange: handleSearchChange,
|
|
960
|
-
searchPlaceholder: t("customers.deals.list.searchPlaceholder"),
|
|
961
|
-
filters,
|
|
962
|
-
filterValues,
|
|
963
|
-
onFiltersApply: handleFiltersApply,
|
|
964
|
-
onFiltersClear: handleFiltersClear,
|
|
965
|
-
pagination: {
|
|
966
|
-
page,
|
|
967
|
-
pageSize,
|
|
968
|
-
total,
|
|
969
|
-
totalPages,
|
|
970
|
-
onPageChange: (nextPage) => setPage(nextPage),
|
|
971
|
-
pageSizeOptions: [10, 25, 50, 100],
|
|
972
|
-
onPageSizeChange: handlePageSizeChange,
|
|
973
|
-
cacheStatus
|
|
974
|
-
},
|
|
975
|
-
isLoading,
|
|
976
|
-
refreshButton: {
|
|
977
|
-
label: t("customers.deals.list.refresh"),
|
|
978
|
-
onRefresh: handleRefresh
|
|
979
|
-
},
|
|
980
|
-
exporter: exportConfig,
|
|
981
|
-
entityId: E.customers.customer_deal,
|
|
982
|
-
perspective: { tableId: "customers.deals.list" },
|
|
983
|
-
advancedFilter: {
|
|
984
|
-
auto: true,
|
|
985
|
-
value: advancedFilterState,
|
|
986
|
-
onChange: setAdvancedFilterState,
|
|
987
|
-
onApply: () => {
|
|
988
|
-
setPage(1);
|
|
814
|
+
],
|
|
815
|
+
searchValue: search,
|
|
816
|
+
onSearchChange: handleSearchChange,
|
|
817
|
+
searchPlaceholder: t("customers.deals.list.searchPlaceholder"),
|
|
818
|
+
pagination: {
|
|
819
|
+
page,
|
|
820
|
+
pageSize,
|
|
821
|
+
total,
|
|
822
|
+
totalPages,
|
|
823
|
+
onPageChange: (nextPage) => setPage(nextPage),
|
|
824
|
+
pageSizeOptions: [10, 25, 50, 100],
|
|
825
|
+
onPageSizeChange: handlePageSizeChange,
|
|
826
|
+
cacheStatus
|
|
989
827
|
},
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
828
|
+
isLoading,
|
|
829
|
+
refreshButton: {
|
|
830
|
+
label: t("customers.deals.list.refresh"),
|
|
831
|
+
onRefresh: handleRefresh
|
|
832
|
+
},
|
|
833
|
+
exporter: exportConfig,
|
|
834
|
+
entityId: E.customers.customer_deal,
|
|
835
|
+
perspective: { tableId: "customers.deals.list" },
|
|
836
|
+
advancedFilter: {
|
|
837
|
+
auto: true,
|
|
838
|
+
value: filterPanel.tree,
|
|
839
|
+
onChange: filterPanel.setTree,
|
|
840
|
+
onApply: () => filterPanel.flush(),
|
|
841
|
+
onClear: handleAdvancedFilterClear,
|
|
842
|
+
triggerRef: filtersTriggerRef,
|
|
843
|
+
externalPopover: true,
|
|
844
|
+
onTriggerClick: () => setFiltersOpen((prev) => !prev),
|
|
845
|
+
onApplyTree: (tree) => {
|
|
846
|
+
filterPanel.replaceTree(tree);
|
|
847
|
+
setPage(1);
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
activeFilterChips: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
851
|
+
/* @__PURE__ */ jsx(
|
|
852
|
+
ActiveFilterChips,
|
|
853
|
+
{
|
|
854
|
+
tree: associationFilterTree,
|
|
855
|
+
fields: associationFilterFields,
|
|
856
|
+
popoverOpen: filtersOpen,
|
|
857
|
+
onRemoveNode: handleAssociationFilterRemove,
|
|
858
|
+
onOpen: () => setFiltersOpen(true)
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsx(
|
|
862
|
+
ActiveFilterChips,
|
|
863
|
+
{
|
|
864
|
+
tree: filterPanel.tree,
|
|
865
|
+
fields: advancedFilterFields,
|
|
866
|
+
popoverOpen: filtersOpen,
|
|
867
|
+
onRemoveNode: (id) => filterPanel.dispatch({ type: "removeNode", nodeId: id }),
|
|
868
|
+
onOpen: () => setFiltersOpen(true)
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
] }),
|
|
872
|
+
filterAwareEmptyState: {
|
|
873
|
+
active: advancedFilterState.root.children.length > 0,
|
|
874
|
+
entityNamePlural: t("customers.deals.entityPlural", "deals"),
|
|
875
|
+
canRemoveLast: filterPanel.tree.root.children.length > 0,
|
|
876
|
+
onClearAll: handleAdvancedFilterClear,
|
|
877
|
+
onRemoveLast: () => filterPanel.dispatch({ type: "removeLast" })
|
|
878
|
+
},
|
|
879
|
+
virtualized: true
|
|
880
|
+
}
|
|
881
|
+
),
|
|
882
|
+
/* @__PURE__ */ jsx(
|
|
883
|
+
AdvancedFilterPanel,
|
|
884
|
+
{
|
|
885
|
+
fields: advancedFilterFields,
|
|
886
|
+
value: filterPanel.tree,
|
|
887
|
+
onChange: filterPanel.setTree,
|
|
888
|
+
onApply: filterPanel.flush,
|
|
889
|
+
onClear: handleAdvancedFilterClear,
|
|
890
|
+
onFlush: filterPanel.flush,
|
|
891
|
+
pendingErrors: filterPanel.pendingErrors,
|
|
892
|
+
userId: currentUserId,
|
|
893
|
+
presets: dealsPresets,
|
|
894
|
+
open: filtersOpen,
|
|
895
|
+
onOpenChange: setFiltersOpen,
|
|
896
|
+
triggerRef: filtersTriggerRef,
|
|
897
|
+
savedFilterStorageKey: "customers.deals.list"
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
] }),
|
|
998
901
|
ConfirmDialogElement
|
|
999
902
|
] });
|
|
1000
903
|
}
|
|
@@ -1013,6 +916,7 @@ function mapDeal(item) {
|
|
|
1013
916
|
const probability = typeof probabilityRaw === "number" ? probabilityRaw : typeof probabilityRaw === "string" && probabilityRaw.trim().length ? Number(probabilityRaw) : null;
|
|
1014
917
|
const expectedCloseAt = typeof item.expected_close_at === "string" ? item.expected_close_at : null;
|
|
1015
918
|
const updatedAt = typeof item.updated_at === "string" ? item.updated_at : null;
|
|
919
|
+
const ownerUserId = typeof item.owner_user_id === "string" ? item.owner_user_id : null;
|
|
1016
920
|
const peopleRaw = Array.isArray(item.people) ? item.people : [];
|
|
1017
921
|
const companiesRaw = Array.isArray(item.companies) ? item.companies : [];
|
|
1018
922
|
const people = peopleRaw.map((entry) => {
|
|
@@ -1047,6 +951,7 @@ function mapDeal(item) {
|
|
|
1047
951
|
probability,
|
|
1048
952
|
expectedCloseAt,
|
|
1049
953
|
updatedAt,
|
|
954
|
+
ownerUserId,
|
|
1050
955
|
people,
|
|
1051
956
|
companies,
|
|
1052
957
|
...customFields
|