@umituz/web-dashboard 3.2.2 → 4.0.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 +202 -14
- package/package.json +21 -29
- package/src/domains/analytics/components/AnalyticsCard.tsx +23 -21
- 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 +42 -64
- 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 +87 -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/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 +30 -0
- package/src/primitives/Checkbox.tsx +40 -0
- package/src/primitives/Input.tsx +19 -0
- package/src/primitives/Skeleton.tsx +26 -0
- package/src/primitives/cn.ts +15 -0
- package/src/primitives/index.ts +23 -0
- package/src/styles/dashboard.css +3045 -0
- package/src/styles/tokens.contract.test.ts +66 -0
- package/src/styles/tokens.css +124 -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,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* MetricCard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Single KPI tile with a trend indicator. Colors and sizes come
|
|
5
|
+
* from `--u-*` tokens via the `udb-metric-*` classes (no hardcoded
|
|
6
|
+
* green/orange) and a11y role=button when interactive.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { ArrowUp, ArrowDown, Minus } from "lucide-react";
|
|
8
|
-
import {
|
|
10
|
+
import { useT } from "../../../i18n";
|
|
11
|
+
import { cn } from "../../../primitives";
|
|
9
12
|
import type { MetricCardProps } from "../types/analytics";
|
|
10
|
-
import { formatMetricValue, calculateGrowth } from "../utils/analytics";
|
|
13
|
+
import { formatMetricValue, calculateGrowth, getTrend } from "../utils/analytics";
|
|
14
|
+
import { ANALYTICS_KEYS } from "../utils/i18nKeys";
|
|
11
15
|
|
|
12
16
|
export const MetricCard = ({
|
|
13
17
|
metric,
|
|
@@ -17,87 +21,61 @@ export const MetricCard = ({
|
|
|
17
21
|
className,
|
|
18
22
|
onClick,
|
|
19
23
|
}: MetricCardProps) => {
|
|
20
|
-
const
|
|
21
|
-
metric.previousValue !== undefined
|
|
22
|
-
? calculateGrowth(metric.value, metric.previousValue)
|
|
23
|
-
: 0;
|
|
24
|
+
const t = useT();
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
lg: "p-8",
|
|
29
|
-
};
|
|
26
|
+
const hasPrevious = metric.previousValue !== undefined;
|
|
27
|
+
const growth = hasPrevious ? calculateGrowth(metric.value, metric.previousValue as number) : null;
|
|
28
|
+
const trend = growth !== null ? getTrend(growth) : 'stable';
|
|
30
29
|
|
|
31
|
-
const
|
|
32
|
-
sm: "text-sm",
|
|
33
|
-
md: "text-base",
|
|
34
|
-
lg: "text-lg",
|
|
35
|
-
};
|
|
30
|
+
const TrendIcon = trend === 'up' ? ArrowUp : trend === 'down' ? ArrowDown : Minus;
|
|
36
31
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
33
|
+
if (!onClick) return;
|
|
34
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
onClick();
|
|
37
|
+
}
|
|
41
38
|
};
|
|
42
39
|
|
|
43
40
|
return (
|
|
44
41
|
<div
|
|
42
|
+
role={onClick ? 'button' : undefined}
|
|
43
|
+
tabIndex={onClick ? 0 : undefined}
|
|
44
|
+
aria-label={onClick ? `${metric.name} — ${formatMetricValue(metric)}` : undefined}
|
|
45
45
|
onClick={onClick}
|
|
46
|
+
onKeyDown={onClick ? handleKeyDown : undefined}
|
|
46
47
|
className={cn(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
onClick &&
|
|
50
|
-
className
|
|
48
|
+
'udb-metric',
|
|
49
|
+
`udb-metric--${size}`,
|
|
50
|
+
onClick && 'udb-metric--interactive',
|
|
51
|
+
className,
|
|
51
52
|
)}
|
|
52
53
|
>
|
|
53
|
-
<div className="
|
|
54
|
-
<div className="
|
|
55
|
-
{
|
|
56
|
-
<p
|
|
57
|
-
className={cn(
|
|
58
|
-
"text-muted-foreground font-medium mb-1",
|
|
59
|
-
titleSizeClasses[size]
|
|
60
|
-
)}
|
|
61
|
-
>
|
|
62
|
-
{metric.name}
|
|
63
|
-
</p>
|
|
54
|
+
<div className="udb-metric__head">
|
|
55
|
+
<div className="udb-metric__body">
|
|
56
|
+
<p className="udb-metric__name">{metric.name}</p>
|
|
64
57
|
|
|
65
|
-
{
|
|
66
|
-
<div className={cn("font-bold text-foreground", valueSizeClasses[size])}>
|
|
67
|
-
{formatMetricValue(metric)}
|
|
68
|
-
</div>
|
|
58
|
+
<div className="udb-metric__value">{formatMetricValue(metric)}</div>
|
|
69
59
|
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
>
|
|
60
|
+
{showTrend && hasPrevious && growth !== null && (
|
|
61
|
+
<div
|
|
62
|
+
className={cn('udb-metric__trend', `udb-metric__trend--${trend}`)}
|
|
63
|
+
aria-label={t(ANALYTICS_KEYS.metric.change)}
|
|
64
|
+
>
|
|
65
|
+
<TrendIcon className="udb-metric__trend-icon" aria-hidden="true" />
|
|
66
|
+
<span className="udb-metric__trend-value">
|
|
88
67
|
{Math.abs(growth).toFixed(1)}%
|
|
89
68
|
</span>
|
|
90
|
-
<span className="
|
|
91
|
-
|
|
69
|
+
<span className="udb-metric__trend-period">
|
|
70
|
+
{t(ANALYTICS_KEYS.common.lastPeriod)}
|
|
92
71
|
</span>
|
|
93
72
|
</div>
|
|
94
73
|
)}
|
|
95
74
|
</div>
|
|
96
75
|
|
|
97
|
-
{/* Icon */}
|
|
98
76
|
{showIcon && metric.icon && (
|
|
99
|
-
<div className="
|
|
100
|
-
<metric.icon className="
|
|
77
|
+
<div className="udb-metric__icon-box">
|
|
78
|
+
<metric.icon className="udb-metric__icon" aria-hidden="true" />
|
|
101
79
|
</div>
|
|
102
80
|
)}
|
|
103
81
|
</div>
|
|
@@ -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";
|