@umituz/web-dashboard 3.2.4 → 4.1.0
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/README.md +228 -14
- package/package.json +23 -30
- package/src/domains/analytics/components/AnalyticsCard.tsx +43 -42
- package/src/domains/analytics/components/AnalyticsChart.tsx +345 -149
- package/src/domains/analytics/components/AnalyticsLayout.tsx +93 -112
- package/src/domains/analytics/components/MetricCard.tsx +29 -87
- package/src/domains/analytics/hooks/index.ts +7 -1
- package/src/domains/analytics/hooks/useAnalytics.ts +144 -139
- package/src/domains/analytics/index.ts +11 -0
- package/src/domains/analytics/services/AnalyticsEngineService.ts +103 -55
- package/src/domains/analytics/services/PerformanceService.ts +117 -69
- package/src/domains/analytics/types/analytics.ts +20 -6
- package/src/domains/analytics/types/index.ts +3 -0
- package/src/domains/analytics/utils/analytics.test.ts +183 -0
- package/src/domains/analytics/utils/analytics.ts +26 -9
- package/src/domains/analytics/utils/i18nKeys.ts +40 -0
- package/src/domains/analytics/utils/index.ts +2 -0
- package/src/domains/auth/components/AuthLayout.tsx +63 -54
- package/src/domains/auth/components/ForgotPasswordForm.tsx +96 -98
- package/src/domains/auth/components/LoginForm.tsx +167 -226
- package/src/domains/auth/components/RegisterForm.tsx +202 -272
- package/src/domains/auth/components/ResetPasswordForm.tsx +105 -151
- package/src/domains/auth/components/shared/AuthErrorBanner.tsx +35 -0
- package/src/domains/auth/components/shared/EmailInput.tsx +91 -0
- package/src/domains/auth/components/shared/PasswordInput.tsx +119 -0
- package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +75 -0
- package/src/domains/auth/components/shared/SocialLoginButtons.tsx +58 -0
- package/src/domains/auth/components/shared/SocialProviderIcons.tsx +48 -0
- package/src/domains/auth/components/shared/index.ts +20 -0
- package/src/domains/auth/hooks/index.ts +1 -0
- package/src/domains/auth/hooks/useAuth.ts +151 -232
- package/src/domains/auth/index.ts +8 -4
- package/src/domains/auth/types/auth.ts +26 -5
- package/src/domains/auth/types/index.ts +1 -0
- package/src/domains/auth/utils/auth.test.ts +182 -0
- package/src/domains/auth/utils/auth.ts +71 -130
- package/src/domains/auth/utils/i18nKeys.ts +127 -0
- package/src/domains/auth/utils/index.ts +7 -1
- package/src/domains/auth/utils/secureToken.ts +33 -0
- package/src/domains/auth/utils/validationKeys.ts +23 -0
- package/src/domains/calendar/hooks/useCalendar.ts +145 -151
- package/src/domains/calendar/index.ts +0 -1
- package/src/domains/calendar/services/CalendarService.ts +122 -179
- package/src/domains/calendar/utils/contentItemMapper.test.ts +108 -0
- package/src/domains/calendar/utils/contentItemMapper.ts +99 -0
- package/src/domains/calendar/utils/index.ts +33 -30
- package/src/domains/layouts/components/BrandLogo.tsx +12 -9
- package/src/domains/layouts/components/DashboardHeader.tsx +78 -80
- package/src/domains/layouts/components/DashboardLayout.tsx +45 -21
- package/src/domains/layouts/components/DashboardSidebar.tsx +48 -51
- package/src/domains/layouts/types/layout.ts +7 -1
- package/src/domains/layouts/types/user.ts +3 -1
- package/src/domains/modules/build.test.tsx +97 -0
- package/src/domains/modules/build.tsx +71 -0
- package/src/domains/modules/index.ts +2 -0
- package/src/domains/modules/types.ts +29 -0
- package/src/domains/onboarding/components/AppFocusStep.tsx +99 -68
- package/src/domains/onboarding/components/OnboardingWizard.tsx +109 -183
- package/src/domains/onboarding/components/PlanStep.tsx +156 -123
- package/src/domains/onboarding/components/PlatformsStep.tsx +74 -54
- package/src/domains/onboarding/components/StepNavigation.tsx +108 -0
- package/src/domains/onboarding/components/StepProgress.tsx +62 -0
- package/src/domains/onboarding/components/UserTypeStep.tsx +59 -80
- package/src/domains/onboarding/components/index.ts +4 -0
- package/src/domains/onboarding/hooks/useOnboarding.ts +76 -66
- package/src/domains/onboarding/index.ts +1 -0
- package/src/domains/onboarding/types/onboarding.ts +57 -17
- package/src/domains/onboarding/utils/i18nKeys.ts +72 -0
- package/src/domains/onboarding/utils/index.ts +2 -0
- package/src/domains/permissions/PermissionsContext.tsx +103 -0
- package/src/domains/permissions/hasPermission.test.ts +50 -0
- package/src/domains/permissions/hasPermission.ts +38 -0
- package/src/domains/permissions/index.ts +19 -0
- package/src/domains/permissions/types.ts +35 -0
- package/src/domains/permissions/utils/i18nKeys.ts +11 -0
- package/src/domains/session/DemoLogin.tsx +50 -0
- package/src/domains/session/OrgSwitcher.tsx +110 -0
- package/src/domains/session/SessionContext.tsx +150 -0
- package/src/domains/session/inMemoryAuthAdapter.ts +53 -0
- package/src/domains/session/index.ts +18 -0
- package/src/domains/session/types.ts +47 -0
- package/src/domains/session/utils/i18nKeys.ts +12 -0
- package/src/domains/settings/components/AuditLogList.tsx +59 -0
- package/src/domains/settings/components/PermissionMatrixScreen.tsx +80 -0
- package/src/domains/settings/components/SettingsLayout.tsx +75 -60
- package/src/domains/settings/components/SettingsSection.tsx +35 -22
- package/src/domains/settings/components/UserManagementTable.tsx +69 -0
- package/src/domains/settings/components/index.ts +10 -0
- package/src/domains/settings/hooks/useSettings.ts +77 -52
- package/src/domains/settings/types/settings.ts +2 -0
- package/src/domains/settings/utils/i18nKeys.ts +18 -0
- package/src/domains/settings/utils/index.ts +1 -0
- package/src/domains/settings/utils/settings.ts +11 -4
- package/src/i18n/I18nContext.tsx +33 -0
- package/src/i18n/defaultT.test.ts +43 -0
- package/src/i18n/defaultT.ts +34 -0
- package/src/i18n/index.ts +7 -0
- package/src/i18n/types.ts +13 -0
- package/src/index.ts +1 -7
- package/src/primitives/Button.tsx +24 -0
- package/src/primitives/Checkbox.tsx +17 -0
- package/src/primitives/Input.tsx +23 -0
- package/src/primitives/Skeleton.tsx +6 -0
- package/src/primitives/cn.ts +10 -0
- package/src/primitives/index.ts +23 -0
- package/src/styles/dashboard.css +2731 -0
- package/src/styles/tokens.contract.test.ts +91 -0
- package/src/styles/tokens.css +116 -0
- package/src/domains/billing/components/BillingLayout.tsx +0 -63
- package/src/domains/billing/components/BillingPage.tsx +0 -49
- package/src/domains/billing/components/BillingPortal.tsx +0 -198
- package/src/domains/billing/components/InvoiceCard.tsx +0 -143
- package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
- package/src/domains/billing/components/PlanComparison.tsx +0 -199
- package/src/domains/billing/components/UsageCard.tsx +0 -103
- package/src/domains/billing/components/index.ts +0 -13
- package/src/domains/billing/constants/billing.ts +0 -69
- package/src/domains/billing/hooks/index.ts +0 -7
- package/src/domains/billing/hooks/useBilling.ts +0 -385
- package/src/domains/billing/index.ts +0 -73
- package/src/domains/billing/types/billing.ts +0 -342
- package/src/domains/billing/types/index.ts +0 -30
- package/src/domains/billing/utils/billing.ts +0 -360
- package/src/domains/billing/utils/index.ts +0 -31
- package/src/domains/billing/utils/polar.ts +0 -63
- package/src/domains/calendar/components/index.ts +0 -7
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* AnalyticsLayout
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Top-level analytics page shell. All actions delegate to the
|
|
5
|
+
* consumer via callbacks — no console.log, no no-op handlers.
|
|
6
|
+
* The legacy "kpis" prop has been removed in favor of the typed
|
|
7
|
+
* `metrics` array (breaking change called out in changelog).
|
|
5
8
|
*/
|
|
6
9
|
|
|
10
|
+
import { useMemo } from "react";
|
|
7
11
|
import { RefreshCw, Download, Calendar, Loader2 } from "lucide-react";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
12
|
+
import { useT } from "../../../i18n";
|
|
13
|
+
import { cn } from "../../../primitives";
|
|
14
|
+
import { Button } from "../../../primitives";
|
|
15
|
+
import type { AnalyticsLayoutProps, AnalyticsPeriod } from "../types/analytics";
|
|
11
16
|
import { AnalyticsCard } from "./AnalyticsCard";
|
|
12
17
|
import { MetricCard } from "./MetricCard";
|
|
13
|
-
import {
|
|
18
|
+
import { ANALYTICS_KEYS } from "../utils/i18nKeys";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Map analytics period tokens to localized display labels.
|
|
22
|
+
* Periods not in this map fall back to the raw token — better
|
|
23
|
+
* to show the key than an empty select option.
|
|
24
|
+
*/
|
|
25
|
+
const PERIOD_LABEL: Record<AnalyticsPeriod, string> = {
|
|
26
|
+
'7d': ANALYTICS_KEYS.common.last7Days,
|
|
27
|
+
'30d': ANALYTICS_KEYS.common.last30Days,
|
|
28
|
+
'90d': ANALYTICS_KEYS.common.last90Days,
|
|
29
|
+
'1y': ANALYTICS_KEYS.common.lastYear,
|
|
30
|
+
};
|
|
14
31
|
|
|
15
32
|
export const AnalyticsLayout = ({
|
|
16
33
|
config,
|
|
@@ -21,71 +38,83 @@ export const AnalyticsLayout = ({
|
|
|
21
38
|
period,
|
|
22
39
|
onPeriodChange,
|
|
23
40
|
showDateRange = true,
|
|
24
|
-
showRefresh,
|
|
25
|
-
showExport,
|
|
26
|
-
kpis,
|
|
41
|
+
showRefresh = true,
|
|
42
|
+
showExport = true,
|
|
27
43
|
charts,
|
|
28
44
|
headerContent,
|
|
45
|
+
onRefresh,
|
|
46
|
+
onExport,
|
|
29
47
|
children,
|
|
30
48
|
}: AnalyticsLayoutProps) => {
|
|
31
|
-
const
|
|
49
|
+
const t = useT();
|
|
32
50
|
|
|
33
|
-
const
|
|
34
|
-
// In production, implement export functionality
|
|
35
|
-
console.log("Exporting analytics data...");
|
|
36
|
-
};
|
|
51
|
+
const visibleMetrics = useMemo(() => metrics ?? [], [metrics]);
|
|
37
52
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
console.log("Refreshing analytics data...");
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Use config settings if props not provided
|
|
44
|
-
const _showRefresh = showRefresh ?? config?.showRefresh ?? true;
|
|
45
|
-
const _showExport = showExport ?? config?.showExport ?? true;
|
|
53
|
+
const showRefreshAction = showRefresh && Boolean(onRefresh);
|
|
54
|
+
const showExportAction = showExport && Boolean(onExport);
|
|
46
55
|
|
|
47
56
|
return (
|
|
48
|
-
<div className="
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<div>
|
|
57
|
+
<div className="udb-analytics">
|
|
58
|
+
<div className="udb-analytics__header">
|
|
59
|
+
<div className="udb-analytics__heading">
|
|
52
60
|
{(title || config?.brandName) && (
|
|
53
|
-
<h1 className="
|
|
54
|
-
{title
|
|
61
|
+
<h1 className="udb-analytics__title">
|
|
62
|
+
{title ??
|
|
63
|
+
(config?.brandName
|
|
64
|
+
? `${config.brandName} ${t(ANALYTICS_KEYS.layout.title)}`
|
|
65
|
+
: t(ANALYTICS_KEYS.layout.title))}
|
|
55
66
|
</h1>
|
|
56
67
|
)}
|
|
57
|
-
{description &&
|
|
68
|
+
{description && (
|
|
69
|
+
<p className="udb-analytics__description">{description}</p>
|
|
70
|
+
)}
|
|
58
71
|
</div>
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
73
|
+
<div className="udb-analytics__actions">
|
|
74
|
+
{showDateRange && onPeriodChange && period && (
|
|
75
|
+
<label className="udb-period-select">
|
|
76
|
+
<Calendar className="udb-period-select__icon" aria-hidden="true" />
|
|
77
|
+
<span className="udb-sr-only">{t(ANALYTICS_KEYS.common.periodSelector)}</span>
|
|
65
78
|
<select
|
|
66
|
-
className="
|
|
79
|
+
className="udb-period-select__select"
|
|
67
80
|
value={period}
|
|
68
|
-
onChange={(e) => onPeriodChange(e.target.value)}
|
|
81
|
+
onChange={(e) => onPeriodChange(e.target.value as AnalyticsPeriod)}
|
|
82
|
+
aria-label={t(ANALYTICS_KEYS.common.periodSelector)}
|
|
69
83
|
>
|
|
70
|
-
{config?.availablePeriods
|
|
84
|
+
{(config?.availablePeriods ?? Object.keys(PERIOD_LABEL) as AnalyticsPeriod[]).map((p) => (
|
|
71
85
|
<option key={p} value={p}>
|
|
72
|
-
{p
|
|
86
|
+
{t(PERIOD_LABEL[p] ?? p)}
|
|
73
87
|
</option>
|
|
74
88
|
))}
|
|
75
89
|
</select>
|
|
76
|
-
</
|
|
90
|
+
</label>
|
|
77
91
|
)}
|
|
78
92
|
|
|
79
|
-
{
|
|
80
|
-
<Button
|
|
81
|
-
|
|
93
|
+
{showRefreshAction && (
|
|
94
|
+
<Button
|
|
95
|
+
variant="ghost"
|
|
96
|
+
size="sm"
|
|
97
|
+
onClick={onRefresh}
|
|
98
|
+
disabled={loading}
|
|
99
|
+
aria-label={t(ANALYTICS_KEYS.common.refresh)}
|
|
100
|
+
>
|
|
101
|
+
<RefreshCw
|
|
102
|
+
className={cn("udb-analytics__action-icon", loading && "udb-spin")}
|
|
103
|
+
aria-hidden="true"
|
|
104
|
+
/>
|
|
82
105
|
</Button>
|
|
83
106
|
)}
|
|
84
107
|
|
|
85
|
-
{
|
|
86
|
-
<Button
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
{showExportAction && (
|
|
109
|
+
<Button
|
|
110
|
+
variant="ghost"
|
|
111
|
+
size="sm"
|
|
112
|
+
onClick={onExport}
|
|
113
|
+
disabled={loading}
|
|
114
|
+
aria-label={t(ANALYTICS_KEYS.common.export)}
|
|
115
|
+
>
|
|
116
|
+
<Download className="udb-analytics__action-icon" aria-hidden="true" />
|
|
117
|
+
<span>{t(ANALYTICS_KEYS.common.export)}</span>
|
|
89
118
|
</Button>
|
|
90
119
|
)}
|
|
91
120
|
|
|
@@ -93,82 +122,35 @@ export const AnalyticsLayout = ({
|
|
|
93
122
|
</div>
|
|
94
123
|
</div>
|
|
95
124
|
|
|
96
|
-
{/* Loading State */}
|
|
97
125
|
{loading && (
|
|
98
|
-
<div
|
|
99
|
-
|
|
126
|
+
<div
|
|
127
|
+
className="udb-analytics__loading"
|
|
128
|
+
role="status"
|
|
129
|
+
aria-live="polite"
|
|
130
|
+
>
|
|
131
|
+
<Loader2 className="udb-spin udb-analytics__loading-spinner" aria-hidden="true" />
|
|
132
|
+
<span className="udb-sr-only">{t(ANALYTICS_KEYS.common.loading)}</span>
|
|
100
133
|
</div>
|
|
101
134
|
)}
|
|
102
135
|
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
{!loading && visibleMetrics.length > 0 && (
|
|
137
|
+
<div
|
|
138
|
+
className={cn(
|
|
139
|
+
"udb-analytics__metrics",
|
|
140
|
+
visibleMetrics.length >= 4 && "udb-analytics__metrics--wide",
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{visibleMetrics.map((metric) => (
|
|
110
144
|
<MetricCard key={metric.id} metric={metric} />
|
|
111
145
|
))}
|
|
112
|
-
|
|
113
|
-
{/* Legacy kpis support */}
|
|
114
|
-
{kpis && !metrics && (
|
|
115
|
-
<>
|
|
116
|
-
<MetricCard
|
|
117
|
-
metric={{
|
|
118
|
-
id: "downloads",
|
|
119
|
-
name: "Downloads",
|
|
120
|
-
value: kpis.downloads.current,
|
|
121
|
-
previousValue: kpis.downloads.previous,
|
|
122
|
-
unit: "K",
|
|
123
|
-
}}
|
|
124
|
-
/>
|
|
125
|
-
<MetricCard
|
|
126
|
-
metric={{
|
|
127
|
-
id: "engagement",
|
|
128
|
-
name: "Engagement",
|
|
129
|
-
value: kpis.engagement.current,
|
|
130
|
-
previousValue: kpis.engagement.previous,
|
|
131
|
-
unit: "%",
|
|
132
|
-
}}
|
|
133
|
-
/>
|
|
134
|
-
<MetricCard
|
|
135
|
-
metric={{
|
|
136
|
-
id: "users",
|
|
137
|
-
name: "Users",
|
|
138
|
-
value: kpis.users.current,
|
|
139
|
-
previousValue: kpis.users.previous,
|
|
140
|
-
unit: "K",
|
|
141
|
-
}}
|
|
142
|
-
/>
|
|
143
|
-
<MetricCard
|
|
144
|
-
metric={{
|
|
145
|
-
id: "revenue",
|
|
146
|
-
name: "Revenue",
|
|
147
|
-
value: kpis.revenue.current,
|
|
148
|
-
previousValue: kpis.revenue.previous,
|
|
149
|
-
unit: "$",
|
|
150
|
-
}}
|
|
151
|
-
/>
|
|
152
|
-
<MetricCard
|
|
153
|
-
metric={{
|
|
154
|
-
id: "retention",
|
|
155
|
-
name: "Retention",
|
|
156
|
-
value: kpis.retention.current,
|
|
157
|
-
previousValue: kpis.retention.previous,
|
|
158
|
-
unit: "%",
|
|
159
|
-
}}
|
|
160
|
-
/>
|
|
161
|
-
</>
|
|
162
|
-
)}
|
|
163
146
|
</div>
|
|
164
147
|
)}
|
|
165
148
|
|
|
166
|
-
{/* Charts */}
|
|
167
149
|
{!loading && charts && charts.length > 0 && (
|
|
168
|
-
<div className="
|
|
169
|
-
{charts.map((chartConfig
|
|
150
|
+
<div className="udb-analytics__charts">
|
|
151
|
+
{charts.map((chartConfig) => (
|
|
170
152
|
<AnalyticsCard
|
|
171
|
-
key={
|
|
153
|
+
key={chartConfig.id}
|
|
172
154
|
title={chartConfig.title}
|
|
173
155
|
description={chartConfig.description}
|
|
174
156
|
chart={chartConfig}
|
|
@@ -177,7 +159,6 @@ export const AnalyticsLayout = ({
|
|
|
177
159
|
</div>
|
|
178
160
|
)}
|
|
179
161
|
|
|
180
|
-
{/* Custom Content */}
|
|
181
162
|
{!loading && children}
|
|
182
163
|
</div>
|
|
183
164
|
);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Displays a single metric with trend indicator
|
|
2
|
+
* MetricCard — dashboard logic (growth, i18n, a11y) on top of the
|
|
3
|
+
* design-system MetricCard presentation.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
MetricCard as DSMetricCard,
|
|
8
|
+
} from "@umituz/web-design-system/organisms";
|
|
9
|
+
import { useT } from "../../../i18n";
|
|
9
10
|
import type { MetricCardProps } from "../types/analytics";
|
|
10
|
-
import { formatMetricValue, calculateGrowth } from "../utils/analytics";
|
|
11
|
+
import { formatMetricValue, calculateGrowth, getTrend } from "../utils/analytics";
|
|
12
|
+
import { ANALYTICS_KEYS } from "../utils/i18nKeys";
|
|
11
13
|
|
|
12
14
|
export const MetricCard = ({
|
|
13
15
|
metric,
|
|
@@ -17,91 +19,31 @@ export const MetricCard = ({
|
|
|
17
19
|
className,
|
|
18
20
|
onClick,
|
|
19
21
|
}: MetricCardProps) => {
|
|
20
|
-
const
|
|
21
|
-
metric.previousValue !== undefined
|
|
22
|
-
? calculateGrowth(metric.value, metric.previousValue)
|
|
23
|
-
: 0;
|
|
22
|
+
const t = useT();
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
lg: "p-8",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const titleSizeClasses = {
|
|
32
|
-
sm: "text-sm",
|
|
33
|
-
md: "text-base",
|
|
34
|
-
lg: "text-lg",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const valueSizeClasses = {
|
|
38
|
-
sm: "text-xl",
|
|
39
|
-
md: "text-3xl",
|
|
40
|
-
lg: "text-4xl",
|
|
41
|
-
};
|
|
24
|
+
const hasPrevious = metric.previousValue !== undefined;
|
|
25
|
+
const growth = hasPrevious ? calculateGrowth(metric.value, metric.previousValue as number) : null;
|
|
26
|
+
const trend = growth !== null ? getTrend(growth) : "stable";
|
|
42
27
|
|
|
43
28
|
return (
|
|
44
|
-
<
|
|
29
|
+
<DSMetricCard
|
|
30
|
+
className={className}
|
|
31
|
+
title={metric.name}
|
|
32
|
+
value={formatMetricValue(metric)}
|
|
33
|
+
icon={showIcon ? metric.icon : undefined}
|
|
34
|
+
iconColor="text-primary"
|
|
35
|
+
size={size}
|
|
36
|
+
trend={
|
|
37
|
+
showTrend && growth !== null && trend !== "stable"
|
|
38
|
+
? {
|
|
39
|
+
value: growth,
|
|
40
|
+
label: t(ANALYTICS_KEYS.common.lastPeriod),
|
|
41
|
+
}
|
|
42
|
+
: undefined
|
|
43
|
+
}
|
|
44
|
+
trendValueFormatter={(v) => `${Math.abs(v).toFixed(1)}%`}
|
|
45
45
|
onClick={onClick}
|
|
46
|
-
|
|
47
|
-
"bg-background border border-border rounded-xl",
|
|
48
|
-
sizeClasses[size],
|
|
49
|
-
onClick && "cursor-pointer hover:border-primary/50 transition-colors",
|
|
50
|
-
className
|
|
51
|
-
)}
|
|
52
|
-
>
|
|
53
|
-
<div className="flex items-start justify-between">
|
|
54
|
-
<div className="flex-1">
|
|
55
|
-
{/* Title */}
|
|
56
|
-
<p
|
|
57
|
-
className={cn(
|
|
58
|
-
"text-muted-foreground font-medium mb-1",
|
|
59
|
-
titleSizeClasses[size]
|
|
60
|
-
)}
|
|
61
|
-
>
|
|
62
|
-
{metric.name}
|
|
63
|
-
</p>
|
|
64
|
-
|
|
65
|
-
{/* Value */}
|
|
66
|
-
<div className={cn("font-bold text-foreground", valueSizeClasses[size])}>
|
|
67
|
-
{formatMetricValue(metric)}
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
{/* Trend */}
|
|
71
|
-
{showTrend && metric.previousValue !== undefined && (
|
|
72
|
-
<div className="flex items-center gap-1 mt-2">
|
|
73
|
-
{growth > 0 ? (
|
|
74
|
-
<ArrowUp className="h-4 w-4 text-green-600 dark:text-green-500" />
|
|
75
|
-
) : growth < 0 ? (
|
|
76
|
-
<ArrowDown className="h-4 w-4 text-destructive" />
|
|
77
|
-
) : (
|
|
78
|
-
<Minus className="h-4 w-4 text-muted-foreground" />
|
|
79
|
-
)}
|
|
80
|
-
<span
|
|
81
|
-
className={cn(
|
|
82
|
-
"text-sm font-medium",
|
|
83
|
-
growth > 0 && "text-green-600 dark:text-green-500",
|
|
84
|
-
growth < 0 && "text-destructive",
|
|
85
|
-
growth === 0 && "text-muted-foreground"
|
|
86
|
-
)}
|
|
87
|
-
>
|
|
88
|
-
{Math.abs(growth).toFixed(1)}%
|
|
89
|
-
</span>
|
|
90
|
-
<span className="text-sm text-muted-foreground">
|
|
91
|
-
vs last period
|
|
92
|
-
</span>
|
|
93
|
-
</div>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{/* Icon */}
|
|
98
|
-
{showIcon && metric.icon && (
|
|
99
|
-
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
|
100
|
-
<metric.icon className="h-6 w-6 text-primary" />
|
|
101
|
-
</div>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
46
|
+
/>
|
|
105
47
|
);
|
|
106
48
|
};
|
|
107
49
|
|
|
@@ -4,4 +4,10 @@
|
|
|
4
4
|
* Export all analytics hooks
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export { useAnalytics } from "./useAnalytics";
|
|
7
|
+
export { useAnalytics, createStubAnalyticsApiClient } from "./useAnalytics";
|
|
8
|
+
export type {
|
|
9
|
+
AnalyticsApiClient,
|
|
10
|
+
AnalyticsDataPayload,
|
|
11
|
+
UseAnalyticsOptions,
|
|
12
|
+
UseAnalyticsReturn,
|
|
13
|
+
} from "./useAnalytics";
|