@open-mercato/core 0.6.5-develop.5382.1.f542de69af → 0.6.5
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 +1 -1
- package/dist/bootstrap.js +46 -6
- package/dist/bootstrap.js.map +2 -2
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/crmFixtures.js +4 -0
- package/dist/helpers/integration/crmFixtures.js.map +2 -2
- package/dist/modules/attachments/api/route.js +2 -0
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/lib/access.js +18 -0
- package/dist/modules/attachments/lib/access.js.map +2 -2
- package/dist/modules/audit_logs/data/entities.js +2 -1
- package/dist/modules/audit_logs/data/entities.js.map +2 -2
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
- package/dist/modules/audit_logs/services/accessLogService.js +10 -0
- package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +9 -0
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/login.js +4 -13
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/data/entities.js +3 -1
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +35 -2
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/consentIntegrity.js +3 -3
- package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
- package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
- package/dist/modules/auth/services/authService.js +5 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +3 -2
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +43 -2
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/deals/summary/route.js +402 -0
- package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +221 -56
- package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/cli.js +15 -9
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
- package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +100 -17
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
- package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
- package/dist/modules/customers/lib/dealsMetrics.js +82 -0
- package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
- package/dist/modules/directory/api/organization-branding/route.js +214 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
- package/dist/modules/directory/api/organizations/route.js +7 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/branding/page.js +214 -0
- package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
- package/dist/modules/directory/commands/organizations.js +8 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +3 -0
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +9 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +59 -27
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +2 -1
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/entities.js +7 -0
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +26 -15
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
- package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
- package/dist/modules/query_index/data/entities.js +2 -1
- package/dist/modules/query_index/data/entities.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +4 -2
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +7 -5
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
- package/dist/modules/staff/api/team-members.js +9 -2
- package/dist/modules/staff/api/team-members.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/team-members.js +1 -1
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
- package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
- package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
- package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +11 -12
- package/src/bootstrap.ts +65 -7
- package/src/helpers/integration/crmFixtures.ts +21 -1
- package/src/modules/attachments/AGENTS.md +79 -0
- package/src/modules/attachments/api/route.ts +2 -0
- package/src/modules/attachments/lib/access.ts +36 -0
- package/src/modules/audit_logs/data/entities.ts +1 -0
- package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
- package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
- package/src/modules/audit_logs/services/accessLogService.ts +15 -0
- package/src/modules/auth/api/admin/nav.ts +9 -0
- package/src/modules/auth/api/login.ts +13 -13
- package/src/modules/auth/data/entities.ts +2 -0
- package/src/modules/auth/i18n/de.json +0 -1
- package/src/modules/auth/i18n/en.json +0 -1
- package/src/modules/auth/i18n/es.json +0 -1
- package/src/modules/auth/i18n/pl.json +0 -1
- package/src/modules/auth/lib/backendChrome.tsx +37 -1
- package/src/modules/auth/lib/consentIntegrity.ts +6 -3
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -0
- package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
- package/src/modules/auth/services/authService.ts +24 -4
- package/src/modules/auth/services/rbacService.ts +11 -2
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
- package/src/modules/customers/api/deals/route.ts +51 -2
- package/src/modules/customers/api/deals/summary/route.ts +496 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
- package/src/modules/customers/cli.ts +15 -15
- package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
- package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
- package/src/modules/customers/components/detail/DealForm.tsx +121 -19
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
- package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
- package/src/modules/customers/i18n/de.json +43 -0
- package/src/modules/customers/i18n/en.json +43 -0
- package/src/modules/customers/i18n/es.json +43 -0
- package/src/modules/customers/i18n/pl.json +43 -0
- package/src/modules/customers/lib/dealsMetrics.ts +159 -0
- package/src/modules/directory/api/organization-branding/route.ts +238 -0
- package/src/modules/directory/api/organizations/route.ts +7 -0
- package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
- package/src/modules/directory/commands/organizations.ts +9 -1
- package/src/modules/directory/data/entities.ts +3 -0
- package/src/modules/directory/data/validators.ts +12 -0
- package/src/modules/directory/i18n/de.json +21 -0
- package/src/modules/directory/i18n/en.json +21 -0
- package/src/modules/directory/i18n/es.json +21 -0
- package/src/modules/directory/i18n/pl.json +21 -0
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
- package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
- package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
- package/src/modules/directory/utils/organizationScope.ts +85 -30
- package/src/modules/entities/api/definitions.batch.ts +11 -7
- package/src/modules/entities/api/entities.ts +11 -0
- package/src/modules/entities/api/records.ts +46 -25
- package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
- package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
- package/src/modules/entities/i18n/de.json +1 -0
- package/src/modules/entities/i18n/en.json +1 -0
- package/src/modules/entities/i18n/es.json +1 -0
- package/src/modules/entities/i18n/pl.json +1 -0
- package/src/modules/query_index/data/entities.ts +1 -0
- package/src/modules/query_index/lib/engine.ts +11 -5
- package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
- package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
- package/src/modules/sales/commands/documents.ts +7 -5
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
- package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
- package/src/modules/staff/api/team-members.ts +9 -2
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
- package/src/modules/staff/commands/team-members.ts +5 -2
- package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
- package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
- package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
|
@@ -19,14 +19,19 @@ import { coalesceLastOperations } from "@open-mercato/ui/backend/operations/stor
|
|
|
19
19
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
20
20
|
import { RowActions } from "@open-mercato/ui/backend/RowActions";
|
|
21
21
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
22
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
23
|
+
import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
24
|
+
import { Avatar, AvatarStack } from "@open-mercato/ui/primitives/avatar";
|
|
25
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
26
|
+
import { SimpleTooltip } from "@open-mercato/ui/primitives/tooltip";
|
|
27
|
+
import { Briefcase, AlertTriangle, X } from "lucide-react";
|
|
28
|
+
import { formatRelativeTime } from "@open-mercato/shared/lib/time";
|
|
22
29
|
import { ViewTabsRow } from "./pipeline/components/ViewTabsRow.js";
|
|
30
|
+
import { DealsKpiStrip } from "../../../components/DealsKpiStrip.js";
|
|
23
31
|
import { E } from "../../../../../generated/entities.ids.generated.js";
|
|
24
32
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
25
33
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
26
34
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
27
|
-
import {
|
|
28
|
-
DictionaryValue
|
|
29
|
-
} from "../../../lib/dictionaries.js";
|
|
30
35
|
import {
|
|
31
36
|
ensureCustomerDictionary,
|
|
32
37
|
invalidateCustomerDictionary
|
|
@@ -106,25 +111,30 @@ function extractIdsFromParams(params, key) {
|
|
|
106
111
|
const values = params.getAll(key);
|
|
107
112
|
return normalizeIdCandidates(values);
|
|
108
113
|
}
|
|
109
|
-
function formatCurrency(amount, currency, fallback) {
|
|
110
|
-
if (typeof amount !== "number" || Number.isNaN(amount)) return fallback;
|
|
111
|
-
try {
|
|
112
|
-
if (currency && currency.trim().length) {
|
|
113
|
-
const formatter2 = new Intl.NumberFormat(void 0, { style: "currency", currency });
|
|
114
|
-
return formatter2.format(amount);
|
|
115
|
-
}
|
|
116
|
-
const formatter = new Intl.NumberFormat(void 0, { style: "decimal", maximumFractionDigits: 2 });
|
|
117
|
-
return formatter.format(amount);
|
|
118
|
-
} catch {
|
|
119
|
-
return currency ? `${amount} ${currency}` : String(amount);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
114
|
function formatDateValue(value, fallback) {
|
|
123
115
|
if (!value) return fallback;
|
|
124
116
|
const date = new Date(value);
|
|
125
117
|
if (Number.isNaN(date.getTime())) return fallback;
|
|
126
118
|
return date.toLocaleDateString();
|
|
127
119
|
}
|
|
120
|
+
const STATUS_BADGE_VARIANTS = /* @__PURE__ */ new Set([
|
|
121
|
+
"success",
|
|
122
|
+
"warning",
|
|
123
|
+
"error",
|
|
124
|
+
"info",
|
|
125
|
+
"neutral"
|
|
126
|
+
]);
|
|
127
|
+
function coerceStatusBadgeVariant(tone) {
|
|
128
|
+
if (tone && STATUS_BADGE_VARIANTS.has(tone)) {
|
|
129
|
+
return tone;
|
|
130
|
+
}
|
|
131
|
+
return "neutral";
|
|
132
|
+
}
|
|
133
|
+
const groupedAmountFormatter = new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 });
|
|
134
|
+
function formatGroupedAmount(amount) {
|
|
135
|
+
if (typeof amount !== "number" || Number.isNaN(amount)) return null;
|
|
136
|
+
return groupedAmountFormatter.format(amount);
|
|
137
|
+
}
|
|
128
138
|
function CustomersDealsPage() {
|
|
129
139
|
const t = useT();
|
|
130
140
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
@@ -146,6 +156,7 @@ function CustomersDealsPage() {
|
|
|
146
156
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
147
157
|
const [reloadToken, setReloadToken] = React.useState(0);
|
|
148
158
|
const [pendingDeleteId, setPendingDeleteId] = React.useState(null);
|
|
159
|
+
const [needsAttentionOnly, setNeedsAttentionOnly] = React.useState(() => searchParams?.get("needsAttention") === "true");
|
|
149
160
|
const initialFilterTree = React.useMemo(() => {
|
|
150
161
|
if (!searchParams) return createEmptyTree();
|
|
151
162
|
const record = {};
|
|
@@ -260,12 +271,13 @@ function CustomersDealsPage() {
|
|
|
260
271
|
if (search.trim().length) params.set("search", search.trim());
|
|
261
272
|
if (selectedPersonIds.length) params.set("personId", selectedPersonIds.join(","));
|
|
262
273
|
if (selectedCompanyIds.length) params.set("companyId", selectedCompanyIds.join(","));
|
|
274
|
+
if (needsAttentionOnly) params.set("needsAttention", "true");
|
|
263
275
|
const advancedParams = serializeTree(advancedFilterState);
|
|
264
276
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
265
277
|
params.set(key, val);
|
|
266
278
|
}
|
|
267
279
|
return params.toString();
|
|
268
|
-
}, [advancedFilterState, page, pageSize, search, selectedCompanyIds, selectedPersonIds, sorting]);
|
|
280
|
+
}, [advancedFilterState, needsAttentionOnly, page, pageSize, search, selectedCompanyIds, selectedPersonIds, sorting]);
|
|
269
281
|
const currentParams = React.useMemo(
|
|
270
282
|
() => Object.fromEntries(new URLSearchParams(queryParams)),
|
|
271
283
|
[queryParams]
|
|
@@ -327,6 +339,7 @@ function CustomersDealsPage() {
|
|
|
327
339
|
if (search.trim().length) params.set("search", search.trim());
|
|
328
340
|
if (selectedPersonIds.length) selectedPersonIds.forEach((id) => params.append("personId", id));
|
|
329
341
|
if (selectedCompanyIds.length) selectedCompanyIds.forEach((id) => params.append("companyId", id));
|
|
342
|
+
if (needsAttentionOnly) params.set("needsAttention", "true");
|
|
330
343
|
if (page > 1) params.set("page", String(page));
|
|
331
344
|
const advancedParams = serializeTree(advancedFilterState);
|
|
332
345
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
@@ -336,7 +349,7 @@ function CustomersDealsPage() {
|
|
|
336
349
|
if (queryRef.current === next) return;
|
|
337
350
|
queryRef.current = next;
|
|
338
351
|
router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false });
|
|
339
|
-
}, [pathname, router, page, search, selectedPersonIds, selectedCompanyIds, advancedFilterState]);
|
|
352
|
+
}, [pathname, router, page, search, selectedPersonIds, selectedCompanyIds, needsAttentionOnly, advancedFilterState]);
|
|
340
353
|
const handleRefresh = React.useCallback(() => {
|
|
341
354
|
void Promise.all([
|
|
342
355
|
invalidateCustomerDictionary(queryClient, "deal-statuses"),
|
|
@@ -406,6 +419,14 @@ function CustomersDealsPage() {
|
|
|
406
419
|
setPageSize(newSize);
|
|
407
420
|
setPage(1);
|
|
408
421
|
}, []);
|
|
422
|
+
const handleNeedsAttentionFilter = React.useCallback(() => {
|
|
423
|
+
setNeedsAttentionOnly(true);
|
|
424
|
+
setPage(1);
|
|
425
|
+
}, []);
|
|
426
|
+
const handleNeedsAttentionClear = React.useCallback(() => {
|
|
427
|
+
setNeedsAttentionOnly(false);
|
|
428
|
+
setPage(1);
|
|
429
|
+
}, []);
|
|
409
430
|
const handleBulkDelete = React.useCallback(async (selectedRows) => {
|
|
410
431
|
const confirmed = await confirm({
|
|
411
432
|
title: t("customers.deals.list.bulkDelete.title", "Delete {count} deals?", { count: selectedRows.length }),
|
|
@@ -486,13 +507,22 @@ function CustomersDealsPage() {
|
|
|
486
507
|
});
|
|
487
508
|
const currentUserId = useCurrentUserId();
|
|
488
509
|
const [ownerFilterOptions, setOwnerFilterOptions] = React.useState([]);
|
|
510
|
+
const [ownerNames, setOwnerNames] = React.useState({});
|
|
489
511
|
React.useEffect(() => {
|
|
490
512
|
const controller = new AbortController();
|
|
491
513
|
let cancelled = false;
|
|
492
514
|
void fetchAssignableStaffMembers("", { pageSize: 100, signal: controller.signal }).then((items) => {
|
|
493
|
-
if (
|
|
515
|
+
if (cancelled) return;
|
|
516
|
+
setOwnerFilterOptions(mapAssignableStaffToFilterOptions(items));
|
|
517
|
+
const names = {};
|
|
518
|
+
for (const item of items) {
|
|
519
|
+
if (item.userId) names[item.userId] = item.displayName;
|
|
520
|
+
}
|
|
521
|
+
setOwnerNames(names);
|
|
494
522
|
}).catch(() => {
|
|
495
|
-
if (
|
|
523
|
+
if (cancelled) return;
|
|
524
|
+
setOwnerFilterOptions([]);
|
|
525
|
+
setOwnerNames({});
|
|
496
526
|
});
|
|
497
527
|
return () => {
|
|
498
528
|
cancelled = true;
|
|
@@ -511,28 +541,18 @@ function CustomersDealsPage() {
|
|
|
511
541
|
const items = await fetchAssignableStaffMembers(query ?? "", { pageSize: 100 });
|
|
512
542
|
return mapAssignableStaffToFilterOptions(items);
|
|
513
543
|
}, []);
|
|
544
|
+
const startOfToday = React.useMemo(() => {
|
|
545
|
+
const today = /* @__PURE__ */ new Date();
|
|
546
|
+
today.setHours(0, 0, 0, 0);
|
|
547
|
+
return today;
|
|
548
|
+
}, []);
|
|
549
|
+
const isDealOverdue = React.useCallback(
|
|
550
|
+
(row) => !!row.expectedCloseAt && new Date(row.expectedCloseAt) < startOfToday && row.status === "open",
|
|
551
|
+
[startOfToday]
|
|
552
|
+
);
|
|
514
553
|
const columns = React.useMemo(() => {
|
|
515
554
|
const noValue = /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: t("customers.deals.list.noValue") });
|
|
516
|
-
const
|
|
517
|
-
DictionaryValue,
|
|
518
|
-
{
|
|
519
|
-
value,
|
|
520
|
-
map: dictionaryMaps[kind],
|
|
521
|
-
fallback: value ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: value }) : noValue,
|
|
522
|
-
className: "text-sm",
|
|
523
|
-
iconWrapperClassName: "inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card",
|
|
524
|
-
iconClassName: "h-4 w-4",
|
|
525
|
-
colorClassName: "h-3 w-3 rounded-full"
|
|
526
|
-
}
|
|
527
|
-
);
|
|
528
|
-
const renderAssociationSummary = (items, fallbackLabel) => {
|
|
529
|
-
if (!items.length) return noValue;
|
|
530
|
-
const labels = normalizeCollectionLabels(
|
|
531
|
-
items.map((entry) => entry.label && entry.label.trim().length ? entry.label : fallbackLabel)
|
|
532
|
-
);
|
|
533
|
-
if (!labels.length) return noValue;
|
|
534
|
-
return /* @__PURE__ */ jsx(CollectionPreviewCell, { labels, maxVisible: 1 });
|
|
535
|
-
};
|
|
555
|
+
const unknownOwner = t("customers.deals.list.unknownOwner");
|
|
536
556
|
const customColumns = customFieldDefs.filter((def) => supportsCustomFieldColumn(def)).map((def) => ({
|
|
537
557
|
accessorKey: `cf_${def.key}`,
|
|
538
558
|
header: def.label || def.key,
|
|
@@ -577,7 +597,17 @@ function CustomersDealsPage() {
|
|
|
577
597
|
filterGroup: "Deal",
|
|
578
598
|
maxWidth: "280px"
|
|
579
599
|
},
|
|
580
|
-
cell: ({ row }) => /* @__PURE__ */
|
|
600
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
601
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Briefcase, { className: "h-4 w-4" }) }),
|
|
602
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground truncate", children: row.original.title }),
|
|
603
|
+
isDealOverdue(row.original) ? /* @__PURE__ */ jsx(
|
|
604
|
+
AlertTriangle,
|
|
605
|
+
{
|
|
606
|
+
className: "h-4 w-4 shrink-0 text-status-warning-text",
|
|
607
|
+
"aria-label": t("customers.deals.list.close.overdue")
|
|
608
|
+
}
|
|
609
|
+
) : null
|
|
610
|
+
] })
|
|
581
611
|
},
|
|
582
612
|
{
|
|
583
613
|
accessorKey: "status",
|
|
@@ -589,7 +619,14 @@ function CustomersDealsPage() {
|
|
|
589
619
|
filterKey: "status",
|
|
590
620
|
filterGroup: "Deal"
|
|
591
621
|
},
|
|
592
|
-
cell: ({ row }) =>
|
|
622
|
+
cell: ({ row }) => {
|
|
623
|
+
const status = row.original.status;
|
|
624
|
+
if (!status) return noValue;
|
|
625
|
+
const entry = dictionaryMaps["deal-statuses"]?.[status];
|
|
626
|
+
const label = entry?.label ?? status;
|
|
627
|
+
const variant = coerceStatusBadgeVariant(mapDictionaryColorToTone(entry?.color));
|
|
628
|
+
return /* @__PURE__ */ jsx(StatusBadge, { variant, dot: true, children: label });
|
|
629
|
+
}
|
|
593
630
|
},
|
|
594
631
|
{
|
|
595
632
|
accessorKey: "pipelineStage",
|
|
@@ -601,7 +638,12 @@ function CustomersDealsPage() {
|
|
|
601
638
|
filterKey: "pipeline_stage",
|
|
602
639
|
filterGroup: "Deal"
|
|
603
640
|
},
|
|
604
|
-
cell: ({ row }) =>
|
|
641
|
+
cell: ({ row }) => {
|
|
642
|
+
const stage = row.original.pipelineStage;
|
|
643
|
+
if (!stage) return noValue;
|
|
644
|
+
const label = dictionaryMaps["pipeline-stages"]?.[stage]?.label ?? stage;
|
|
645
|
+
return /* @__PURE__ */ jsx("span", { className: "text-foreground", children: label });
|
|
646
|
+
}
|
|
605
647
|
},
|
|
606
648
|
{
|
|
607
649
|
accessorKey: "pipelineId",
|
|
@@ -626,7 +668,15 @@ function CustomersDealsPage() {
|
|
|
626
668
|
filterKey: "value_amount",
|
|
627
669
|
filterGroup: "Deal"
|
|
628
670
|
},
|
|
629
|
-
cell: ({ row }) =>
|
|
671
|
+
cell: ({ row }) => {
|
|
672
|
+
const amount = formatGroupedAmount(row.original.valueAmount ?? null);
|
|
673
|
+
if (amount === null) return noValue;
|
|
674
|
+
const currency = row.original.valueCurrency;
|
|
675
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
676
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: amount }),
|
|
677
|
+
currency ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: currency }) : null
|
|
678
|
+
] });
|
|
679
|
+
}
|
|
630
680
|
},
|
|
631
681
|
{
|
|
632
682
|
accessorKey: "probability",
|
|
@@ -640,7 +690,7 @@ function CustomersDealsPage() {
|
|
|
640
690
|
cell: ({ row }) => {
|
|
641
691
|
const value = row.original.probability;
|
|
642
692
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
643
|
-
return /* @__PURE__ */ jsx("span", { className: "text-
|
|
693
|
+
return /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: `${Math.min(Math.max(value, 0), 100)}%` });
|
|
644
694
|
}
|
|
645
695
|
return noValue;
|
|
646
696
|
}
|
|
@@ -654,7 +704,27 @@ function CustomersDealsPage() {
|
|
|
654
704
|
filterGroup: "Activity",
|
|
655
705
|
filterIconName: "calendar"
|
|
656
706
|
},
|
|
657
|
-
cell: ({ row }) =>
|
|
707
|
+
cell: ({ row }) => {
|
|
708
|
+
const expectedCloseAt = row.original.expectedCloseAt;
|
|
709
|
+
if (!expectedCloseAt) return noValue;
|
|
710
|
+
let subtitle = null;
|
|
711
|
+
if (isDealOverdue(row.original)) {
|
|
712
|
+
subtitle = /* @__PURE__ */ jsx("span", { className: "text-xs text-status-error-text", children: t("customers.deals.list.close.overdue") });
|
|
713
|
+
} else if (row.original.status === "win") {
|
|
714
|
+
subtitle = /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("customers.deals.list.close.won") });
|
|
715
|
+
} else if (row.original.status === "loose") {
|
|
716
|
+
subtitle = /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("customers.deals.list.close.lost") });
|
|
717
|
+
} else {
|
|
718
|
+
const relative = formatRelativeTime(expectedCloseAt, { translate: t });
|
|
719
|
+
if (relative) {
|
|
720
|
+
subtitle = /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: relative });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
724
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground", children: formatDateValue(expectedCloseAt, t("customers.deals.list.noValue")) }),
|
|
725
|
+
subtitle
|
|
726
|
+
] });
|
|
727
|
+
}
|
|
658
728
|
},
|
|
659
729
|
{
|
|
660
730
|
accessorKey: "ownerUserId",
|
|
@@ -666,10 +736,17 @@ function CustomersDealsPage() {
|
|
|
666
736
|
filterLoadOptions: loadOwnerFilterOptions,
|
|
667
737
|
filterGroup: "CRM",
|
|
668
738
|
filterIconName: "user-round",
|
|
669
|
-
filterKey: "owner_user_id"
|
|
670
|
-
hidden: true
|
|
739
|
+
filterKey: "owner_user_id"
|
|
671
740
|
},
|
|
672
|
-
cell: ({ row }) =>
|
|
741
|
+
cell: ({ row }) => {
|
|
742
|
+
const ownerUserId = row.original.ownerUserId;
|
|
743
|
+
if (!ownerUserId) return noValue;
|
|
744
|
+
const label = ownerNames[ownerUserId]?.trim() || unknownOwner;
|
|
745
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
746
|
+
/* @__PURE__ */ jsx(Avatar, { label, size: "sm" }),
|
|
747
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground truncate", children: label })
|
|
748
|
+
] });
|
|
749
|
+
}
|
|
673
750
|
},
|
|
674
751
|
{
|
|
675
752
|
accessorKey: "companies",
|
|
@@ -684,7 +761,17 @@ function CustomersDealsPage() {
|
|
|
684
761
|
row.companies.map((entry) => entry.label && entry.label.trim().length ? entry.label : t("customers.deals.list.unnamedCompany"))
|
|
685
762
|
).join(", ")
|
|
686
763
|
},
|
|
687
|
-
cell: ({ row }) =>
|
|
764
|
+
cell: ({ row }) => {
|
|
765
|
+
const companies = row.original.companies;
|
|
766
|
+
if (!companies.length) return noValue;
|
|
767
|
+
const first = companies[0];
|
|
768
|
+
const firstLabel = first.label && first.label.trim().length ? first.label : t("customers.deals.list.unnamedCompany");
|
|
769
|
+
const overflow = companies.length - 1;
|
|
770
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
771
|
+
/* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "max-w-36", children: /* @__PURE__ */ jsx("span", { className: "truncate", children: firstLabel }) }),
|
|
772
|
+
overflow > 0 ? /* @__PURE__ */ jsx(Tag, { variant: "neutral", children: `+${overflow}` }) : null
|
|
773
|
+
] });
|
|
774
|
+
}
|
|
688
775
|
},
|
|
689
776
|
{
|
|
690
777
|
accessorKey: "people",
|
|
@@ -699,7 +786,22 @@ function CustomersDealsPage() {
|
|
|
699
786
|
row.people.map((entry) => entry.label && entry.label.trim().length ? entry.label : t("customers.deals.list.unnamedPerson"))
|
|
700
787
|
).join(", ")
|
|
701
788
|
},
|
|
702
|
-
cell: ({ row }) =>
|
|
789
|
+
cell: ({ row }) => {
|
|
790
|
+
const people = row.original.people;
|
|
791
|
+
if (!people.length) return noValue;
|
|
792
|
+
const labels = normalizeCollectionLabels(
|
|
793
|
+
people.map((person) => person.label && person.label.trim().length ? person.label : t("customers.deals.list.unnamedPerson"))
|
|
794
|
+
);
|
|
795
|
+
const tooltip = labels.join(", ");
|
|
796
|
+
return /* @__PURE__ */ jsx(SimpleTooltip, { content: tooltip, side: "top", children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(AvatarStack, { max: 4, size: "sm", children: people.map((person) => /* @__PURE__ */ jsx(
|
|
797
|
+
Avatar,
|
|
798
|
+
{
|
|
799
|
+
label: person.label || t("customers.deals.list.unnamedPerson"),
|
|
800
|
+
size: "sm"
|
|
801
|
+
},
|
|
802
|
+
person.id
|
|
803
|
+
)) }) }) });
|
|
804
|
+
}
|
|
703
805
|
},
|
|
704
806
|
{
|
|
705
807
|
accessorKey: "updatedAt",
|
|
@@ -714,7 +816,7 @@ function CustomersDealsPage() {
|
|
|
714
816
|
},
|
|
715
817
|
...customColumns
|
|
716
818
|
];
|
|
717
|
-
}, [customFieldDefs, dictionaryMaps, dictionaryOptions, loadOwnerFilterOptions, pipelineNames, resolvedOwnerFilterOptions, t]);
|
|
819
|
+
}, [customFieldDefs, dictionaryMaps, dictionaryOptions, isDealOverdue, loadOwnerFilterOptions, ownerNames, pipelineNames, resolvedOwnerFilterOptions, t]);
|
|
718
820
|
const { advancedFilterFields } = useAutoDiscoveredFields({ columns, customFieldDefs });
|
|
719
821
|
React.useEffect(() => {
|
|
720
822
|
setPanelFields((prev) => {
|
|
@@ -779,11 +881,24 @@ function CustomersDealsPage() {
|
|
|
779
881
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
780
882
|
/* @__PURE__ */ jsxs(PageBody, { children: [
|
|
781
883
|
/* @__PURE__ */ jsx(ViewTabsRow, { active: "list", className: "mb-4" }),
|
|
884
|
+
/* @__PURE__ */ jsx(
|
|
885
|
+
DealsKpiStrip,
|
|
886
|
+
{
|
|
887
|
+
ownerNames,
|
|
888
|
+
stageDictionary: dictionaryMaps["pipeline-stages"] ?? {},
|
|
889
|
+
pipelineCount: Object.keys(pipelineNames).length,
|
|
890
|
+
scopeVersion,
|
|
891
|
+
reloadToken,
|
|
892
|
+
onNeedsAttentionClick: handleNeedsAttentionFilter,
|
|
893
|
+
className: "mb-4"
|
|
894
|
+
}
|
|
895
|
+
),
|
|
782
896
|
/* @__PURE__ */ jsx(
|
|
783
897
|
DataTable,
|
|
784
898
|
{
|
|
785
899
|
stickyFirstColumn: true,
|
|
786
900
|
stickyActionsColumn: true,
|
|
901
|
+
actionsColumnAlign: "center",
|
|
787
902
|
title: t("customers.deals.list.title"),
|
|
788
903
|
actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/deals/create", children: t("customers.deals.list.actions.new", "New deal") }) }),
|
|
789
904
|
columns,
|
|
@@ -871,6 +986,35 @@ function CustomersDealsPage() {
|
|
|
871
986
|
}
|
|
872
987
|
},
|
|
873
988
|
activeFilterChips: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
989
|
+
needsAttentionOnly ? /* @__PURE__ */ jsx(
|
|
990
|
+
"div",
|
|
991
|
+
{
|
|
992
|
+
className: "flex items-center gap-2 overflow-x-auto border-b border-border bg-background px-4 py-2",
|
|
993
|
+
"data-testid": "active-filter-chips",
|
|
994
|
+
children: /* @__PURE__ */ jsxs(
|
|
995
|
+
"div",
|
|
996
|
+
{
|
|
997
|
+
className: "inline-flex items-center gap-1",
|
|
998
|
+
"data-testid": "active-filter-chip",
|
|
999
|
+
"aria-label": t("customers.deals.list.filters.needsAttention"),
|
|
1000
|
+
children: [
|
|
1001
|
+
/* @__PURE__ */ jsx(Tag, { variant: "warning", dot: true, children: t("customers.deals.list.filters.needsAttention") }),
|
|
1002
|
+
/* @__PURE__ */ jsx(
|
|
1003
|
+
IconButton,
|
|
1004
|
+
{
|
|
1005
|
+
type: "button",
|
|
1006
|
+
variant: "ghost",
|
|
1007
|
+
size: "xs",
|
|
1008
|
+
"aria-label": t("customers.deals.list.filters.needsAttentionRemove"),
|
|
1009
|
+
onClick: handleNeedsAttentionClear,
|
|
1010
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3" })
|
|
1011
|
+
}
|
|
1012
|
+
)
|
|
1013
|
+
]
|
|
1014
|
+
}
|
|
1015
|
+
)
|
|
1016
|
+
}
|
|
1017
|
+
) : null,
|
|
874
1018
|
/* @__PURE__ */ jsx(
|
|
875
1019
|
ActiveFilterChips,
|
|
876
1020
|
{
|
|
@@ -893,11 +1037,32 @@ function CustomersDealsPage() {
|
|
|
893
1037
|
)
|
|
894
1038
|
] }),
|
|
895
1039
|
filterAwareEmptyState: {
|
|
896
|
-
active: advancedFilterState.root.children.length > 0,
|
|
1040
|
+
active: needsAttentionOnly || associationFilterTree.root.children.length > 0 || advancedFilterState.root.children.length > 0,
|
|
897
1041
|
entityNamePlural: t("customers.deals.entityPlural", "deals"),
|
|
898
|
-
canRemoveLast: filterPanel.tree.root.children.length > 0,
|
|
899
|
-
onClearAll:
|
|
900
|
-
|
|
1042
|
+
canRemoveLast: needsAttentionOnly || associationFilterTree.root.children.length > 0 || filterPanel.tree.root.children.length > 0,
|
|
1043
|
+
onClearAll: () => {
|
|
1044
|
+
handleAdvancedFilterClear();
|
|
1045
|
+
setSelectedPersonIds([]);
|
|
1046
|
+
setSelectedCompanyIds([]);
|
|
1047
|
+
setNeedsAttentionOnly(false);
|
|
1048
|
+
},
|
|
1049
|
+
onRemoveLast: () => {
|
|
1050
|
+
if (needsAttentionOnly) {
|
|
1051
|
+
handleNeedsAttentionClear();
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (selectedCompanyIds.length > 0) {
|
|
1055
|
+
setSelectedCompanyIds([]);
|
|
1056
|
+
setPage(1);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (selectedPersonIds.length > 0) {
|
|
1060
|
+
setSelectedPersonIds([]);
|
|
1061
|
+
setPage(1);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
filterPanel.dispatch({ type: "removeLast" });
|
|
1065
|
+
}
|
|
901
1066
|
},
|
|
902
1067
|
emptyState: /* @__PURE__ */ jsx(
|
|
903
1068
|
ListEmptyState,
|