@open-mercato/core 0.6.6-develop.6299.1.29224e2d86 → 0.6.6-develop.6300.1.08ff40c897
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/modules/customers/backend/customers/deals/pipeline/page.js +26 -22
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyKpiBar.js +8 -7
- package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +6 -5
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +29 -22
- package/src/modules/customers/components/detail/CompanyKpiBar.tsx +8 -7
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +6 -5
- package/dist/modules/customers/components/detail/CompanyDashboardTab.js +0 -133
- package/dist/modules/customers/components/detail/CompanyDashboardTab.js.map +0 -7
- package/src/modules/customers/components/detail/CompanyDashboardTab.tsx +0 -161
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { EyeOff } from "lucide-react";
|
|
5
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
-
import { KpiCard } from "@open-mercato/ui/backend/charts/KpiCard";
|
|
7
|
-
import { Button } from "@open-mercato/ui/primitives/button";
|
|
8
|
-
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
9
|
-
import { InlineActivityComposer } from "./InlineActivityComposer.js";
|
|
10
|
-
import { formatCurrency } from "./utils.js";
|
|
11
|
-
import { computeHealthScore } from "./healthScoreUtils.js";
|
|
12
|
-
import {
|
|
13
|
-
readJsonFromLocalStorage,
|
|
14
|
-
writeJsonToLocalStorage,
|
|
15
|
-
removeLocalStorageKey
|
|
16
|
-
} from "@open-mercato/shared/lib/browser/safeLocalStorage";
|
|
17
|
-
import {
|
|
18
|
-
sumActiveDeals,
|
|
19
|
-
getActiveDeals,
|
|
20
|
-
getOpenTasks,
|
|
21
|
-
getUpcomingMeetings,
|
|
22
|
-
getRecentActivity,
|
|
23
|
-
computeActivityTrend,
|
|
24
|
-
computeDealTrend
|
|
25
|
-
} from "./dashboard/helpers.js";
|
|
26
|
-
import { UpcomingMeetingsWidget } from "./dashboard/UpcomingMeetingsWidget.js";
|
|
27
|
-
import { RelationshipHealthWidget } from "./dashboard/RelationshipHealthWidget.js";
|
|
28
|
-
import { ActiveDealWidget } from "./dashboard/ActiveDealWidget.js";
|
|
29
|
-
import { OpenTasksWidget } from "./dashboard/OpenTasksWidget.js";
|
|
30
|
-
import { RecentActivityWidget } from "./dashboard/RecentActivityWidget.js";
|
|
31
|
-
function CompanyDashboardTab({ data, companyId, onTabChange, onActivityCreated, onScheduleRequested, runGuardedMutation, useCanonicalInteractions }) {
|
|
32
|
-
const t = useT();
|
|
33
|
-
const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals]);
|
|
34
|
-
const activeDealsValue = React.useMemo(() => sumActiveDeals(data.deals), [data.deals]);
|
|
35
|
-
const openTasks = React.useMemo(() => getOpenTasks(data.todos), [data.todos]);
|
|
36
|
-
const upcomingMeetings = React.useMemo(() => getUpcomingMeetings(data.interactions), [data.interactions]);
|
|
37
|
-
const recentActivity = React.useMemo(() => getRecentActivity(data.interactions), [data.interactions]);
|
|
38
|
-
const dealCurrency = activeDeals[0]?.valueCurrency ?? data.deals[0]?.valueCurrency ?? "PLN";
|
|
39
|
-
const activityTrend = React.useMemo(() => computeActivityTrend(data.interactions), [data.interactions]);
|
|
40
|
-
const dealTrend = React.useMemo(() => computeDealTrend(data.deals), [data.deals]);
|
|
41
|
-
const healthScore = React.useMemo(() => computeHealthScore(data.interactions), [data.interactions]);
|
|
42
|
-
const ltvValue = React.useMemo(() => {
|
|
43
|
-
const wonDeals = data.deals.filter((d) => d.status === "won");
|
|
44
|
-
if (wonDeals.length === 0) return null;
|
|
45
|
-
return wonDeals.reduce((sum, d) => {
|
|
46
|
-
const amt = typeof d.valueAmount === "number" ? d.valueAmount : parseFloat(String(d.valueAmount ?? "0"));
|
|
47
|
-
return sum + (Number.isFinite(amt) ? amt : 0);
|
|
48
|
-
}, 0);
|
|
49
|
-
}, [data.deals]);
|
|
50
|
-
const clientTenureYears = React.useMemo(() => {
|
|
51
|
-
const allDates = data.interactions.map((i) => i.occurredAt ?? i.scheduledAt ?? i.createdAt).filter(Boolean).map((d) => new Date(d).getTime());
|
|
52
|
-
if (allDates.length === 0) return null;
|
|
53
|
-
const earliest = Math.min(...allDates);
|
|
54
|
-
return Math.floor((Date.now() - earliest) / (365.25 * 864e5));
|
|
55
|
-
}, [data.interactions]);
|
|
56
|
-
const [hiddenTiles, setHiddenTiles] = React.useState(
|
|
57
|
-
() => new Set(readJsonFromLocalStorage("om:dashboard-hidden-tiles", []))
|
|
58
|
-
);
|
|
59
|
-
const toggleTile = React.useCallback((tileId) => {
|
|
60
|
-
setHiddenTiles((prev) => {
|
|
61
|
-
const next = new Set(prev);
|
|
62
|
-
if (next.has(tileId)) next.delete(tileId);
|
|
63
|
-
else next.add(tileId);
|
|
64
|
-
writeJsonToLocalStorage("om:dashboard-hidden-tiles", [...next]);
|
|
65
|
-
return next;
|
|
66
|
-
});
|
|
67
|
-
}, []);
|
|
68
|
-
const kpiTiles = [
|
|
69
|
-
{ id: "activeDeals", title: t("customers.companies.dashboard.kpi.activeDeals", "ACTIVE DEALS"), value: activeDealsValue, trend: dealTrend, formatValue: (v) => formatCurrency(v, dealCurrency), comparisonLabel: `${activeDeals.length} ${activeDeals.length === 1 ? "pipeline" : "pipelines"}` },
|
|
70
|
-
{ id: "activities", title: t("customers.companies.dashboard.kpi.activities", "ACTIVITIES"), value: data.interactions.length, trend: activityTrend, comparisonLabel: t("customers.companies.dashboard.kpi.last12months", "last 12 months") },
|
|
71
|
-
{ id: "ltv", title: t("customers.companies.dashboard.kpi.ltv", "CUSTOMER VALUE (LTV)"), value: ltvValue, formatValue: ltvValue !== null ? (v) => formatCurrency(v, dealCurrency) : void 0, comparisonLabel: ltvValue !== null ? t("customers.companies.dashboard.kpi.wonDeals", "won deals total") : t("customers.companies.dashboard.kpi.noWonDeals", "No won deals") },
|
|
72
|
-
{ id: "clientSince", title: t("customers.companies.dashboard.kpi.clientSince", "CLIENT SINCE"), value: clientTenureYears, formatValue: clientTenureYears !== null ? (v) => v < 1 ? `< 1 ${t("customers.companies.dashboard.kpi.year", "year")}` : `${v} ${v === 1 ? t("customers.companies.dashboard.kpi.year", "year") : t("customers.companies.dashboard.kpi.years", "years")}` : void 0, comparisonLabel: clientTenureYears !== null ? `${data.deals.filter((d) => d.status === "won").length} ${t("customers.companies.dashboard.kpi.completedDeals", "completed deals")}` : t("customers.companies.dashboard.kpi.noInteractions", "No interactions yet") }
|
|
73
|
-
];
|
|
74
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
75
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: kpiTiles.filter((tile) => !hiddenTiles.has(tile.id)).map((tile) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
|
|
76
|
-
/* @__PURE__ */ jsx(
|
|
77
|
-
KpiCard,
|
|
78
|
-
{
|
|
79
|
-
title: tile.title,
|
|
80
|
-
value: tile.value,
|
|
81
|
-
trend: tile.trend,
|
|
82
|
-
formatValue: tile.formatValue,
|
|
83
|
-
comparisonLabel: tile.comparisonLabel
|
|
84
|
-
}
|
|
85
|
-
),
|
|
86
|
-
/* @__PURE__ */ jsx(
|
|
87
|
-
IconButton,
|
|
88
|
-
{
|
|
89
|
-
type: "button",
|
|
90
|
-
variant: "ghost",
|
|
91
|
-
size: "sm",
|
|
92
|
-
onClick: () => toggleTile(tile.id),
|
|
93
|
-
className: "h-auto absolute top-2 right-2 opacity-0 group-hover:opacity-60 transition-opacity text-muted-foreground hover:text-foreground p-0",
|
|
94
|
-
"aria-label": t("customers.companies.dashboard.hideTile", "Hide tile"),
|
|
95
|
-
children: /* @__PURE__ */ jsx(EyeOff, { className: "size-3.5" })
|
|
96
|
-
}
|
|
97
|
-
)
|
|
98
|
-
] }, tile.id)) }),
|
|
99
|
-
hiddenTiles.size > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
100
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("customers.companies.dashboard.hiddenTiles", "{{count}} tiles hidden", { count: hiddenTiles.size }) }),
|
|
101
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-auto text-xs px-1.5 py-0.5", onClick: () => {
|
|
102
|
-
setHiddenTiles(/* @__PURE__ */ new Set());
|
|
103
|
-
removeLocalStorageKey("om:dashboard-hidden-tiles");
|
|
104
|
-
}, children: t("customers.companies.dashboard.showAll", "Show all") })
|
|
105
|
-
] }),
|
|
106
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_380px]", children: [
|
|
107
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
108
|
-
/* @__PURE__ */ jsx(UpcomingMeetingsWidget, { meetings: upcomingMeetings, t }),
|
|
109
|
-
/* @__PURE__ */ jsx(OpenTasksWidget, { tasks: openTasks, t, onViewAll: () => onTabChange("activity-log"), currentUserId: null })
|
|
110
|
-
] }),
|
|
111
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
112
|
-
/* @__PURE__ */ jsx(RelationshipHealthWidget, { health: healthScore, t }),
|
|
113
|
-
/* @__PURE__ */ jsx(ActiveDealWidget, { deals: activeDeals, t })
|
|
114
|
-
] })
|
|
115
|
-
] }),
|
|
116
|
-
/* @__PURE__ */ jsx(
|
|
117
|
-
InlineActivityComposer,
|
|
118
|
-
{
|
|
119
|
-
entityType: "company",
|
|
120
|
-
entityId: companyId,
|
|
121
|
-
onActivityCreated,
|
|
122
|
-
runGuardedMutation,
|
|
123
|
-
onScheduleRequested,
|
|
124
|
-
useCanonicalInteractions
|
|
125
|
-
}
|
|
126
|
-
),
|
|
127
|
-
/* @__PURE__ */ jsx(RecentActivityWidget, { interactions: recentActivity, t })
|
|
128
|
-
] });
|
|
129
|
-
}
|
|
130
|
-
export {
|
|
131
|
-
CompanyDashboardTab
|
|
132
|
-
};
|
|
133
|
-
//# sourceMappingURL=CompanyDashboardTab.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../../src/modules/customers/components/detail/CompanyDashboardTab.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { EyeOff } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { InlineActivityComposer } from './InlineActivityComposer'\nimport type { CompanyOverview } from '../formConfig'\nimport { formatCurrency } from './utils'\nimport { computeHealthScore } from './healthScoreUtils'\nimport {\n readJsonFromLocalStorage,\n writeJsonToLocalStorage,\n removeLocalStorageKey,\n} from '@open-mercato/shared/lib/browser/safeLocalStorage'\nimport {\n sumActiveDeals,\n getActiveDeals,\n getOpenTasks,\n getUpcomingMeetings,\n getRecentActivity,\n computeActivityTrend,\n computeDealTrend,\n} from './dashboard/helpers'\nimport { UpcomingMeetingsWidget } from './dashboard/UpcomingMeetingsWidget'\nimport { RelationshipHealthWidget } from './dashboard/RelationshipHealthWidget'\nimport { ActiveDealWidget } from './dashboard/ActiveDealWidget'\nimport { OpenTasksWidget } from './dashboard/OpenTasksWidget'\nimport { RecentActivityWidget } from './dashboard/RecentActivityWidget'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype CompanyDashboardTabProps = {\n data: CompanyOverview\n companyId: string\n onTabChange: (tab: string) => void\n onActivityCreated?: () => void\n onScheduleRequested?: () => void\n runGuardedMutation?: GuardedMutationRunner\n useCanonicalInteractions?: boolean\n}\n\nexport function CompanyDashboardTab({ data, companyId, onTabChange, onActivityCreated, onScheduleRequested, runGuardedMutation, useCanonicalInteractions }: CompanyDashboardTabProps) {\n const t = useT()\n\n const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals])\n const activeDealsValue = React.useMemo(() => sumActiveDeals(data.deals), [data.deals])\n const openTasks = React.useMemo(() => getOpenTasks(data.todos), [data.todos])\n const upcomingMeetings = React.useMemo(() => getUpcomingMeetings(data.interactions), [data.interactions])\n const recentActivity = React.useMemo(() => getRecentActivity(data.interactions), [data.interactions])\n\n const dealCurrency = activeDeals[0]?.valueCurrency ?? data.deals[0]?.valueCurrency ?? 'PLN'\n const activityTrend = React.useMemo(() => computeActivityTrend(data.interactions), [data.interactions])\n const dealTrend = React.useMemo(() => computeDealTrend(data.deals), [data.deals])\n const healthScore = React.useMemo(() => computeHealthScore(data.interactions), [data.interactions])\n\n const ltvValue = React.useMemo(() => {\n const wonDeals = data.deals.filter((d) => d.status === 'won')\n if (wonDeals.length === 0) return null\n return wonDeals.reduce((sum, d) => {\n const amt = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))\n return sum + (Number.isFinite(amt) ? amt : 0)\n }, 0)\n }, [data.deals])\n\n const clientTenureYears = React.useMemo(() => {\n const allDates = data.interactions\n .map((i) => i.occurredAt ?? i.scheduledAt ?? i.createdAt)\n .filter(Boolean)\n .map((d) => new Date(d).getTime())\n if (allDates.length === 0) return null\n const earliest = Math.min(...allDates)\n return Math.floor((Date.now() - earliest) / (365.25 * 86_400_000))\n }, [data.interactions])\n\n const [hiddenTiles, setHiddenTiles] = React.useState<Set<string>>(\n () => new Set(readJsonFromLocalStorage<string[]>('om:dashboard-hidden-tiles', [])),\n )\n\n const toggleTile = React.useCallback((tileId: string) => {\n setHiddenTiles((prev) => {\n const next = new Set(prev)\n if (next.has(tileId)) next.delete(tileId)\n else next.add(tileId)\n writeJsonToLocalStorage('om:dashboard-hidden-tiles', [...next])\n return next\n })\n }, [])\n\n const kpiTiles: Array<{ id: string; title: string; value: number | null; trend?: KpiTrend; formatValue?: (v: number) => string; comparisonLabel: string }> = [\n { id: 'activeDeals', title: t('customers.companies.dashboard.kpi.activeDeals', 'ACTIVE DEALS'), value: activeDealsValue, trend: dealTrend, formatValue: (v: number) => formatCurrency(v, dealCurrency), comparisonLabel: `${activeDeals.length} ${activeDeals.length === 1 ? 'pipeline' : 'pipelines'}` },\n { id: 'activities', title: t('customers.companies.dashboard.kpi.activities', 'ACTIVITIES'), value: data.interactions.length, trend: activityTrend, comparisonLabel: t('customers.companies.dashboard.kpi.last12months', 'last 12 months') },\n { id: 'ltv', title: t('customers.companies.dashboard.kpi.ltv', 'CUSTOMER VALUE (LTV)'), value: ltvValue, formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency) : undefined, comparisonLabel: ltvValue !== null ? t('customers.companies.dashboard.kpi.wonDeals', 'won deals total') : t('customers.companies.dashboard.kpi.noWonDeals', 'No won deals') },\n { id: 'clientSince', title: t('customers.companies.dashboard.kpi.clientSince', 'CLIENT SINCE'), value: clientTenureYears, formatValue: clientTenureYears !== null ? (v: number) => v < 1 ? `< 1 ${t('customers.companies.dashboard.kpi.year', 'year')}` : `${v} ${v === 1 ? t('customers.companies.dashboard.kpi.year', 'year') : t('customers.companies.dashboard.kpi.years', 'years')}` : undefined, comparisonLabel: clientTenureYears !== null ? `${data.deals.filter(d => d.status === 'won').length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}` : t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet') },\n ]\n\n return (\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {kpiTiles.filter((tile) => !hiddenTiles.has(tile.id)).map((tile) => (\n <div key={tile.id} className=\"relative group\">\n <KpiCard\n title={tile.title}\n value={tile.value}\n trend={tile.trend}\n formatValue={tile.formatValue}\n comparisonLabel={tile.comparisonLabel}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => toggleTile(tile.id)}\n className=\"h-auto absolute top-2 right-2 opacity-0 group-hover:opacity-60 transition-opacity text-muted-foreground hover:text-foreground p-0\"\n aria-label={t('customers.companies.dashboard.hideTile', 'Hide tile')}\n >\n <EyeOff className=\"size-3.5\" />\n </IconButton>\n </div>\n ))}\n </div>\n {hiddenTiles.size > 0 && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {t('customers.companies.dashboard.hiddenTiles', '{{count}} tiles hidden', { count: hiddenTiles.size })}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"h-auto text-xs px-1.5 py-0.5\" onClick={() => { setHiddenTiles(new Set()); removeLocalStorageKey('om:dashboard-hidden-tiles') }}>\n {t('customers.companies.dashboard.showAll', 'Show all')}\n </Button>\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[1fr_380px]\">\n <div className=\"space-y-6\">\n <UpcomingMeetingsWidget meetings={upcomingMeetings} t={t} />\n <OpenTasksWidget tasks={openTasks} t={t} onViewAll={() => onTabChange('activity-log')} currentUserId={null} />\n </div>\n <div className=\"space-y-6\">\n <RelationshipHealthWidget health={healthScore} t={t} />\n <ActiveDealWidget deals={activeDeals} t={t} />\n </div>\n </div>\n\n <InlineActivityComposer\n entityType=\"company\"\n entityId={companyId}\n onActivityCreated={onActivityCreated}\n runGuardedMutation={runGuardedMutation}\n onScheduleRequested={onScheduleRequested}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n\n <RecentActivityWidget interactions={recentActivity} t={t} />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { EyeOff } from 'lucide-react'
|
|
5
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
-
import { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'
|
|
7
|
-
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
|
-
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
9
|
-
import { InlineActivityComposer } from './InlineActivityComposer'
|
|
10
|
-
import type { CompanyOverview } from '../formConfig'
|
|
11
|
-
import { formatCurrency } from './utils'
|
|
12
|
-
import { computeHealthScore } from './healthScoreUtils'
|
|
13
|
-
import {
|
|
14
|
-
readJsonFromLocalStorage,
|
|
15
|
-
writeJsonToLocalStorage,
|
|
16
|
-
removeLocalStorageKey,
|
|
17
|
-
} from '@open-mercato/shared/lib/browser/safeLocalStorage'
|
|
18
|
-
import {
|
|
19
|
-
sumActiveDeals,
|
|
20
|
-
getActiveDeals,
|
|
21
|
-
getOpenTasks,
|
|
22
|
-
getUpcomingMeetings,
|
|
23
|
-
getRecentActivity,
|
|
24
|
-
computeActivityTrend,
|
|
25
|
-
computeDealTrend,
|
|
26
|
-
} from './dashboard/helpers'
|
|
27
|
-
import { UpcomingMeetingsWidget } from './dashboard/UpcomingMeetingsWidget'
|
|
28
|
-
import { RelationshipHealthWidget } from './dashboard/RelationshipHealthWidget'
|
|
29
|
-
import { ActiveDealWidget } from './dashboard/ActiveDealWidget'
|
|
30
|
-
import { OpenTasksWidget } from './dashboard/OpenTasksWidget'
|
|
31
|
-
import { RecentActivityWidget } from './dashboard/RecentActivityWidget'
|
|
32
|
-
|
|
33
|
-
type GuardedMutationRunner = <T>(
|
|
34
|
-
operation: () => Promise<T>,
|
|
35
|
-
mutationPayload?: Record<string, unknown>,
|
|
36
|
-
) => Promise<T>
|
|
37
|
-
|
|
38
|
-
type CompanyDashboardTabProps = {
|
|
39
|
-
data: CompanyOverview
|
|
40
|
-
companyId: string
|
|
41
|
-
onTabChange: (tab: string) => void
|
|
42
|
-
onActivityCreated?: () => void
|
|
43
|
-
onScheduleRequested?: () => void
|
|
44
|
-
runGuardedMutation?: GuardedMutationRunner
|
|
45
|
-
useCanonicalInteractions?: boolean
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function CompanyDashboardTab({ data, companyId, onTabChange, onActivityCreated, onScheduleRequested, runGuardedMutation, useCanonicalInteractions }: CompanyDashboardTabProps) {
|
|
49
|
-
const t = useT()
|
|
50
|
-
|
|
51
|
-
const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals])
|
|
52
|
-
const activeDealsValue = React.useMemo(() => sumActiveDeals(data.deals), [data.deals])
|
|
53
|
-
const openTasks = React.useMemo(() => getOpenTasks(data.todos), [data.todos])
|
|
54
|
-
const upcomingMeetings = React.useMemo(() => getUpcomingMeetings(data.interactions), [data.interactions])
|
|
55
|
-
const recentActivity = React.useMemo(() => getRecentActivity(data.interactions), [data.interactions])
|
|
56
|
-
|
|
57
|
-
const dealCurrency = activeDeals[0]?.valueCurrency ?? data.deals[0]?.valueCurrency ?? 'PLN'
|
|
58
|
-
const activityTrend = React.useMemo(() => computeActivityTrend(data.interactions), [data.interactions])
|
|
59
|
-
const dealTrend = React.useMemo(() => computeDealTrend(data.deals), [data.deals])
|
|
60
|
-
const healthScore = React.useMemo(() => computeHealthScore(data.interactions), [data.interactions])
|
|
61
|
-
|
|
62
|
-
const ltvValue = React.useMemo(() => {
|
|
63
|
-
const wonDeals = data.deals.filter((d) => d.status === 'won')
|
|
64
|
-
if (wonDeals.length === 0) return null
|
|
65
|
-
return wonDeals.reduce((sum, d) => {
|
|
66
|
-
const amt = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))
|
|
67
|
-
return sum + (Number.isFinite(amt) ? amt : 0)
|
|
68
|
-
}, 0)
|
|
69
|
-
}, [data.deals])
|
|
70
|
-
|
|
71
|
-
const clientTenureYears = React.useMemo(() => {
|
|
72
|
-
const allDates = data.interactions
|
|
73
|
-
.map((i) => i.occurredAt ?? i.scheduledAt ?? i.createdAt)
|
|
74
|
-
.filter(Boolean)
|
|
75
|
-
.map((d) => new Date(d).getTime())
|
|
76
|
-
if (allDates.length === 0) return null
|
|
77
|
-
const earliest = Math.min(...allDates)
|
|
78
|
-
return Math.floor((Date.now() - earliest) / (365.25 * 86_400_000))
|
|
79
|
-
}, [data.interactions])
|
|
80
|
-
|
|
81
|
-
const [hiddenTiles, setHiddenTiles] = React.useState<Set<string>>(
|
|
82
|
-
() => new Set(readJsonFromLocalStorage<string[]>('om:dashboard-hidden-tiles', [])),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
const toggleTile = React.useCallback((tileId: string) => {
|
|
86
|
-
setHiddenTiles((prev) => {
|
|
87
|
-
const next = new Set(prev)
|
|
88
|
-
if (next.has(tileId)) next.delete(tileId)
|
|
89
|
-
else next.add(tileId)
|
|
90
|
-
writeJsonToLocalStorage('om:dashboard-hidden-tiles', [...next])
|
|
91
|
-
return next
|
|
92
|
-
})
|
|
93
|
-
}, [])
|
|
94
|
-
|
|
95
|
-
const kpiTiles: Array<{ id: string; title: string; value: number | null; trend?: KpiTrend; formatValue?: (v: number) => string; comparisonLabel: string }> = [
|
|
96
|
-
{ id: 'activeDeals', title: t('customers.companies.dashboard.kpi.activeDeals', 'ACTIVE DEALS'), value: activeDealsValue, trend: dealTrend, formatValue: (v: number) => formatCurrency(v, dealCurrency), comparisonLabel: `${activeDeals.length} ${activeDeals.length === 1 ? 'pipeline' : 'pipelines'}` },
|
|
97
|
-
{ id: 'activities', title: t('customers.companies.dashboard.kpi.activities', 'ACTIVITIES'), value: data.interactions.length, trend: activityTrend, comparisonLabel: t('customers.companies.dashboard.kpi.last12months', 'last 12 months') },
|
|
98
|
-
{ id: 'ltv', title: t('customers.companies.dashboard.kpi.ltv', 'CUSTOMER VALUE (LTV)'), value: ltvValue, formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency) : undefined, comparisonLabel: ltvValue !== null ? t('customers.companies.dashboard.kpi.wonDeals', 'won deals total') : t('customers.companies.dashboard.kpi.noWonDeals', 'No won deals') },
|
|
99
|
-
{ id: 'clientSince', title: t('customers.companies.dashboard.kpi.clientSince', 'CLIENT SINCE'), value: clientTenureYears, formatValue: clientTenureYears !== null ? (v: number) => v < 1 ? `< 1 ${t('customers.companies.dashboard.kpi.year', 'year')}` : `${v} ${v === 1 ? t('customers.companies.dashboard.kpi.year', 'year') : t('customers.companies.dashboard.kpi.years', 'years')}` : undefined, comparisonLabel: clientTenureYears !== null ? `${data.deals.filter(d => d.status === 'won').length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}` : t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet') },
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<div className="space-y-6">
|
|
104
|
-
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
|
105
|
-
{kpiTiles.filter((tile) => !hiddenTiles.has(tile.id)).map((tile) => (
|
|
106
|
-
<div key={tile.id} className="relative group">
|
|
107
|
-
<KpiCard
|
|
108
|
-
title={tile.title}
|
|
109
|
-
value={tile.value}
|
|
110
|
-
trend={tile.trend}
|
|
111
|
-
formatValue={tile.formatValue}
|
|
112
|
-
comparisonLabel={tile.comparisonLabel}
|
|
113
|
-
/>
|
|
114
|
-
<IconButton
|
|
115
|
-
type="button"
|
|
116
|
-
variant="ghost"
|
|
117
|
-
size="sm"
|
|
118
|
-
onClick={() => toggleTile(tile.id)}
|
|
119
|
-
className="h-auto absolute top-2 right-2 opacity-0 group-hover:opacity-60 transition-opacity text-muted-foreground hover:text-foreground p-0"
|
|
120
|
-
aria-label={t('customers.companies.dashboard.hideTile', 'Hide tile')}
|
|
121
|
-
>
|
|
122
|
-
<EyeOff className="size-3.5" />
|
|
123
|
-
</IconButton>
|
|
124
|
-
</div>
|
|
125
|
-
))}
|
|
126
|
-
</div>
|
|
127
|
-
{hiddenTiles.size > 0 && (
|
|
128
|
-
<div className="flex items-center gap-2">
|
|
129
|
-
<span className="text-xs text-muted-foreground">
|
|
130
|
-
{t('customers.companies.dashboard.hiddenTiles', '{{count}} tiles hidden', { count: hiddenTiles.size })}
|
|
131
|
-
</span>
|
|
132
|
-
<Button type="button" variant="ghost" size="sm" className="h-auto text-xs px-1.5 py-0.5" onClick={() => { setHiddenTiles(new Set()); removeLocalStorageKey('om:dashboard-hidden-tiles') }}>
|
|
133
|
-
{t('customers.companies.dashboard.showAll', 'Show all')}
|
|
134
|
-
</Button>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
|
|
138
|
-
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[1fr_380px]">
|
|
139
|
-
<div className="space-y-6">
|
|
140
|
-
<UpcomingMeetingsWidget meetings={upcomingMeetings} t={t} />
|
|
141
|
-
<OpenTasksWidget tasks={openTasks} t={t} onViewAll={() => onTabChange('activity-log')} currentUserId={null} />
|
|
142
|
-
</div>
|
|
143
|
-
<div className="space-y-6">
|
|
144
|
-
<RelationshipHealthWidget health={healthScore} t={t} />
|
|
145
|
-
<ActiveDealWidget deals={activeDeals} t={t} />
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<InlineActivityComposer
|
|
150
|
-
entityType="company"
|
|
151
|
-
entityId={companyId}
|
|
152
|
-
onActivityCreated={onActivityCreated}
|
|
153
|
-
runGuardedMutation={runGuardedMutation}
|
|
154
|
-
onScheduleRequested={onScheduleRequested}
|
|
155
|
-
useCanonicalInteractions={useCanonicalInteractions}
|
|
156
|
-
/>
|
|
157
|
-
|
|
158
|
-
<RecentActivityWidget interactions={recentActivity} t={t} />
|
|
159
|
-
</div>
|
|
160
|
-
)
|
|
161
|
-
}
|