@umituz/web-dashboard 6.1.0 → 6.2.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 +47 -73
- package/package.json +5 -9
- package/src/config/DashboardConfig.ts +19 -68
- package/src/domains/analytics/components/index.ts +0 -6
- package/src/domains/analytics/hooks/index.ts +0 -6
- package/src/domains/analytics/hooks/useAnalytics.ts +2 -20
- package/src/domains/analytics/index.ts +0 -10
- package/src/domains/analytics/services/AnalyticsEngineService.ts +7 -74
- package/src/domains/analytics/services/PerformanceService.ts +27 -138
- package/src/domains/analytics/services/index.ts +0 -4
- package/src/domains/analytics/types/analytics.ts +100 -270
- package/src/domains/analytics/types/index.ts +0 -6
- package/src/domains/analytics/utils/analytics.test.ts +2 -9
- package/src/domains/analytics/utils/analytics.ts +1 -22
- package/src/domains/analytics/utils/index.ts +0 -6
- package/src/domains/calendar/config.ts +0 -7
- package/src/domains/calendar/hooks/index.ts +0 -4
- package/src/domains/calendar/hooks/useCalendar.ts +7 -27
- package/src/domains/calendar/index.ts +0 -4
- package/src/domains/calendar/services/CalendarService.ts +0 -21
- package/src/domains/calendar/services/index.ts +0 -4
- package/src/domains/calendar/types/calendar.types.ts +9 -53
- package/src/domains/calendar/utils/contentItemMapper.test.ts +0 -4
- package/src/domains/calendar/utils/contentItemMapper.ts +0 -31
- package/src/domains/calendar/utils/index.ts +0 -26
- package/src/domains/layouts/components/index.ts +0 -4
- package/src/domains/layouts/hooks/dashboard.ts +0 -21
- package/src/domains/layouts/hooks/index.ts +0 -4
- package/src/domains/layouts/index.ts +0 -6
- package/src/domains/layouts/theme/default.ts +0 -12
- package/src/domains/layouts/theme/index.ts +0 -4
- package/src/domains/layouts/theme/presets.ts +0 -9
- package/src/domains/layouts/theme/utils.ts +0 -15
- package/src/domains/layouts/types/index.ts +0 -4
- package/src/domains/layouts/types/layout.ts +18 -56
- package/src/domains/layouts/types/notification.ts +4 -17
- package/src/domains/layouts/types/sidebar.ts +8 -28
- package/src/domains/layouts/types/theme.ts +23 -59
- package/src/domains/layouts/types/user.ts +9 -30
- package/src/domains/layouts/utils/dashboard.ts +4 -48
- package/src/domains/layouts/utils/index.ts +0 -4
- package/src/domains/modules/build.tsx +2 -16
- package/src/domains/modules/types.ts +8 -23
- package/src/domains/onboarding/calc/calc.test.ts +1 -9
- package/src/domains/onboarding/calc/card-stack-motion-curve.ts +0 -6
- package/src/domains/onboarding/calc/count-with-locale.ts +0 -8
- package/src/domains/onboarding/calc/duration-equal-split.ts +0 -12
- package/src/domains/onboarding/calc/elapsed-millis.ts +0 -6
- package/src/domains/onboarding/calc/progress-percentage.ts +1 -11
- package/src/domains/onboarding/calc/slider-value-bounds.ts +1 -11
- package/src/domains/onboarding/calc/step-index-clamp.ts +0 -9
- package/src/domains/onboarding/calc/swipe-gesture-curve.ts +0 -13
- package/src/domains/onboarding/components/{shared/AnimationWrapper.tsx → AnimationWrapper.tsx} +0 -4
- package/src/domains/onboarding/components/{shared/OnboardingErrorBoundary.tsx → OnboardingErrorBoundary.tsx} +7 -27
- package/src/domains/onboarding/components/OnboardingFlow.test.tsx +8 -30
- package/src/domains/onboarding/components/OnboardingFlow.tsx +13 -61
- package/src/domains/onboarding/components/OnboardingIntl.tsx +1 -11
- package/src/domains/onboarding/components/PrimaryButton.tsx +40 -0
- package/src/domains/onboarding/components/ProgressBar.tsx +32 -0
- package/src/domains/onboarding/components/QuestionWrapper.tsx +28 -0
- package/src/domains/onboarding/components/ScreenContainer.tsx +19 -0
- package/src/domains/onboarding/components/{shared/SwipeCard.test.tsx → SwipeCard.test.tsx} +1 -3
- package/src/domains/onboarding/components/{shared/SwipeCard.tsx → SwipeCard.tsx} +10 -28
- package/src/domains/onboarding/components/renderBundledIcon.tsx +7 -0
- package/src/domains/onboarding/components/screens/AccountGateScreen.tsx +7 -17
- package/src/domains/onboarding/components/screens/AppDemoScreen.tsx +4 -10
- package/src/domains/onboarding/components/screens/ComparisonScreen.tsx +12 -22
- package/src/domains/onboarding/components/screens/GoalQuestionScreen.tsx +9 -6
- package/src/domains/onboarding/components/screens/PainPointsScreen.tsx +10 -10
- package/src/domains/onboarding/components/screens/PaywallScreen.tsx +22 -38
- package/src/domains/onboarding/components/screens/PermissionPrimingScreen.tsx +14 -29
- package/src/domains/onboarding/components/screens/PersonalizedSolutionScreen.tsx +10 -23
- package/src/domains/onboarding/components/screens/PreferencesScreen.tsx +13 -27
- package/src/domains/onboarding/components/screens/ProcessingScreen.tsx +11 -27
- package/src/domains/onboarding/components/screens/SocialProofScreen.tsx +22 -51
- package/src/domains/onboarding/components/screens/TinderCardsScreen.tsx +13 -30
- package/src/domains/onboarding/components/screens/ValueDeliveryScreen.tsx +13 -29
- package/src/domains/onboarding/components/screens/WelcomeScreen.tsx +12 -32
- package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingAnalytics.test.tsx +1 -1
- package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingAnalytics.ts +3 -16
- package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingProgress.test.tsx +2 -2
- package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingProgress.ts +10 -21
- package/src/domains/onboarding/{components/hooks → hooks}/usePermissionPriming.ts +3 -10
- package/src/domains/onboarding/{components/hooks → hooks}/useReducedMotion.ts +0 -5
- package/src/domains/onboarding/index.ts +16 -45
- package/src/domains/onboarding/predicates/has-handler.ts +0 -4
- package/src/domains/onboarding/predicates/icon-name-resolves.ts +1 -17
- package/src/domains/onboarding/predicates/permission-granted.ts +0 -5
- package/src/domains/onboarding/predicates/permission-supports.ts +1 -11
- package/src/domains/onboarding/predicates/predicates.test.ts +6 -20
- package/src/domains/onboarding/predicates/screen-emits-data.ts +0 -4
- package/src/domains/onboarding/predicates/storage-available.ts +0 -8
- package/src/domains/onboarding/templates/TemplateDemoFallback.tsx +19 -39
- package/src/domains/onboarding/templates/art-onboarding.ts +1 -11
- package/src/domains/onboarding/templates/health-onboarding.ts +0 -5
- package/src/domains/onboarding/templates/platform-onboarding.ts +0 -9
- package/src/domains/onboarding/templates/video-onboarding.ts +0 -5
- package/src/domains/onboarding/test/setup.ts +1 -8
- package/src/domains/onboarding/types/config.ts +7 -18
- package/src/domains/onboarding/types/labels.ts +25 -59
- package/src/domains/onboarding/types/screens.ts +3 -11
- package/src/domains/onboarding/utils/analytics-dispatch.ts +2 -21
- package/src/domains/onboarding/utils/default-storage-key.ts +0 -1
- package/src/domains/onboarding/utils/index.ts +0 -1
- package/src/domains/onboarding/utils/local-storage-bridge.ts +1 -21
- package/src/domains/onboarding/utils/permission-bridge.ts +0 -8
- package/src/domains/onboarding/validation/flow-structure-check.ts +0 -6
- package/src/domains/onboarding/validation/permission-status-check.ts +0 -5
- package/src/domains/onboarding/validation/screen-config-check.ts +0 -5
- package/src/domains/onboarding/validation/stored-progress-check.ts +1 -10
- package/src/domains/onboarding/validation/validation.test.ts +1 -6
- package/src/domains/permissions/hasPermission.ts +0 -8
- package/src/domains/permissions/types.ts +0 -20
- package/src/domains/settings/components/index.ts +0 -4
- package/src/domains/settings/hooks/index.ts +0 -4
- package/src/domains/settings/hooks/useSettings.ts +1 -26
- package/src/domains/settings/index.ts +0 -6
- package/src/domains/settings/types/index.ts +0 -4
- package/src/domains/settings/types/settings.ts +23 -67
- package/src/domains/settings/utils/index.ts +0 -4
- package/src/domains/settings/utils/settings.ts +0 -45
- package/src/styles/dashboard.css +818 -1
- package/src/domains/onboarding/components/shared/OptionCard.tsx +0 -73
- package/src/domains/onboarding/components/shared/PrimaryButton.tsx +0 -58
- package/src/domains/onboarding/components/shared/ProgressBar.tsx +0 -51
- package/src/domains/onboarding/components/shared/QuestionWrapper.tsx +0 -36
- package/src/domains/onboarding/components/shared/ScreenContainer.tsx +0 -29
- package/src/domains/onboarding/components/shared/index.ts +0 -16
- package/src/domains/onboarding/components/shared/renderBundledIcon.tsx +0 -20
- package/src/domains/onboarding/onboarding.css +0 -1
- package/src/domains/onboarding/theme/cn.ts +0 -10
- package/src/domains/onboarding/theme/index.css +0 -131
- package/src/domains/onboarding/theme/index.ts +0 -12
- package/src/domains/onboarding/theme/tokens.ts +0 -122
- /package/src/domains/onboarding/{components/hooks → hooks}/index.ts +0 -0
|
@@ -1,132 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analytics Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions for analytics system
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { ComponentType } from "react";
|
|
8
2
|
|
|
9
|
-
/**
|
|
10
|
-
* Metric card data
|
|
11
|
-
*/
|
|
12
3
|
export interface Metric {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
previousValue?: number;
|
|
21
|
-
/** Unit (%, $, etc.) */
|
|
22
|
-
unit?: string;
|
|
23
|
-
/** Trend direction */
|
|
24
|
-
trend?: "up" | "down" | "stable";
|
|
25
|
-
/** Icon component */
|
|
26
|
-
icon?: ComponentType<{ className?: string }>;
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
value: number;
|
|
7
|
+
previousValue?: number;
|
|
8
|
+
unit?: string;
|
|
9
|
+
trend?: "up" | "down" | "stable";
|
|
10
|
+
icon?: ComponentType<{ className?: string }>;
|
|
27
11
|
}
|
|
28
12
|
|
|
29
|
-
/**
|
|
30
|
-
* Time series data point
|
|
31
|
-
*/
|
|
32
13
|
export interface TimeSeriesData {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/** Metric values */
|
|
36
|
-
[key: string]: string | number;
|
|
14
|
+
date: string;
|
|
15
|
+
[key: string]: string | number;
|
|
37
16
|
}
|
|
38
17
|
|
|
39
|
-
/**
|
|
40
|
-
* Chart data point
|
|
41
|
-
*/
|
|
42
18
|
export interface ChartData {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
value: number;
|
|
47
|
-
/** Additional data */
|
|
48
|
-
[key: string]: string | number | boolean;
|
|
19
|
+
label: string;
|
|
20
|
+
value: number;
|
|
21
|
+
[key: string]: string | number | boolean;
|
|
49
22
|
}
|
|
50
23
|
|
|
51
|
-
/**
|
|
52
|
-
* Funnel step
|
|
53
|
-
*/
|
|
54
24
|
export interface FunnelStep {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
conversionRate: number;
|
|
61
|
-
/** Drop-off rate from previous step */
|
|
62
|
-
dropOffRate: number;
|
|
63
|
-
/** Average time spent at step */
|
|
64
|
-
averageTime?: number;
|
|
25
|
+
name: string;
|
|
26
|
+
count: number;
|
|
27
|
+
conversionRate: number;
|
|
28
|
+
dropOffRate: number;
|
|
29
|
+
averageTime?: number;
|
|
65
30
|
}
|
|
66
31
|
|
|
67
|
-
/**
|
|
68
|
-
* Funnel data
|
|
69
|
-
*/
|
|
70
32
|
export interface FunnelData {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/** Total starting users */
|
|
76
|
-
totalUsers: number;
|
|
77
|
-
/** Final conversion rate */
|
|
78
|
-
finalConversion: number;
|
|
33
|
+
title: string;
|
|
34
|
+
steps: FunnelStep[];
|
|
35
|
+
totalUsers: number;
|
|
36
|
+
finalConversion: number;
|
|
79
37
|
}
|
|
80
38
|
|
|
81
|
-
/**
|
|
82
|
-
* Cohort analysis data
|
|
83
|
-
*/
|
|
84
39
|
export interface CohortAnalysis {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/** Retention rates over time */
|
|
90
|
-
retention: number[];
|
|
91
|
-
/** Average retention rate */
|
|
92
|
-
averageRetention: number;
|
|
40
|
+
cohort: string;
|
|
41
|
+
size: number;
|
|
42
|
+
retention: number[];
|
|
43
|
+
averageRetention: number;
|
|
93
44
|
}
|
|
94
45
|
|
|
95
|
-
/**
|
|
96
|
-
* User segment
|
|
97
|
-
*/
|
|
98
46
|
export interface UserSegment {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
percentage: number;
|
|
105
|
-
/** Segment characteristics */
|
|
106
|
-
characteristics: string[];
|
|
107
|
-
/** Behavioral metrics */
|
|
108
|
-
behavior: {
|
|
47
|
+
name: string;
|
|
48
|
+
count: number;
|
|
49
|
+
percentage: number;
|
|
50
|
+
characteristics: string[];
|
|
51
|
+
behavior: {
|
|
109
52
|
avgSessionDuration: number;
|
|
110
53
|
pagesPerSession: number;
|
|
111
54
|
bounceRate: number;
|
|
112
55
|
};
|
|
113
56
|
}
|
|
114
57
|
|
|
115
|
-
/**
|
|
116
|
-
* KPI data with comparison
|
|
117
|
-
*/
|
|
118
58
|
export interface KPIData {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
previous: number;
|
|
123
|
-
/** Growth rate percentage */
|
|
124
|
-
growth: number;
|
|
59
|
+
current: number;
|
|
60
|
+
previous: number;
|
|
61
|
+
growth: number;
|
|
125
62
|
}
|
|
126
63
|
|
|
127
|
-
/**
|
|
128
|
-
* KPIs container
|
|
129
|
-
*/
|
|
130
64
|
export interface KPIs {
|
|
131
65
|
downloads: KPIData;
|
|
132
66
|
engagement: KPIData;
|
|
@@ -135,9 +69,6 @@ export interface KPIs {
|
|
|
135
69
|
retention: KPIData;
|
|
136
70
|
}
|
|
137
71
|
|
|
138
|
-
/**
|
|
139
|
-
* Chart types
|
|
140
|
-
*/
|
|
141
72
|
export type ChartType =
|
|
142
73
|
| "line"
|
|
143
74
|
| "bar"
|
|
@@ -149,202 +80,101 @@ export type ChartType =
|
|
|
149
80
|
| "metric"
|
|
150
81
|
| "table";
|
|
151
82
|
|
|
152
|
-
/**
|
|
153
|
-
* Chart configuration
|
|
154
|
-
*/
|
|
155
83
|
export interface ChartConfig {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
yAxisKeys?: string[];
|
|
170
|
-
/** Color scheme (CSS variable names, e.g. "hsl(var(--primary))") */
|
|
171
|
-
colors?: string[];
|
|
172
|
-
/** Show legend */
|
|
173
|
-
showLegend?: boolean;
|
|
174
|
-
/** Show grid */
|
|
175
|
-
showGrid?: boolean;
|
|
176
|
-
/** Show tooltip */
|
|
177
|
-
showTooltip?: boolean;
|
|
178
|
-
/** Chart height */
|
|
179
|
-
height?: number | string;
|
|
180
|
-
/** Chart aspect ratio */
|
|
181
|
-
aspectRatio?: string;
|
|
84
|
+
id: string;
|
|
85
|
+
type: ChartType;
|
|
86
|
+
title?: string;
|
|
87
|
+
description?: string;
|
|
88
|
+
data: TimeSeriesData[] | ChartData[] | Metric[];
|
|
89
|
+
xAxisKey?: string;
|
|
90
|
+
yAxisKeys?: string[];
|
|
91
|
+
colors?: string[];
|
|
92
|
+
showLegend?: boolean;
|
|
93
|
+
showGrid?: boolean;
|
|
94
|
+
showTooltip?: boolean;
|
|
95
|
+
height?: number | string;
|
|
96
|
+
aspectRatio?: string;
|
|
182
97
|
}
|
|
183
98
|
|
|
184
|
-
/**
|
|
185
|
-
* Supported analytics period tokens.
|
|
186
|
-
* Typed union to prevent stringly-typed comparison elsewhere.
|
|
187
|
-
*/
|
|
188
99
|
export type AnalyticsPeriod = '7d' | '30d' | '90d' | '1y' | (string & {});
|
|
189
100
|
|
|
190
|
-
/**
|
|
191
|
-
* Metric card props
|
|
192
|
-
*/
|
|
193
101
|
export interface MetricCardProps {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/** Show icon */
|
|
201
|
-
showIcon?: boolean;
|
|
202
|
-
/** Custom class name */
|
|
203
|
-
className?: string;
|
|
204
|
-
/** On click handler */
|
|
205
|
-
onClick?: () => void;
|
|
102
|
+
metric: Metric;
|
|
103
|
+
size?: "sm" | "md" | "lg";
|
|
104
|
+
showTrend?: boolean;
|
|
105
|
+
showIcon?: boolean;
|
|
106
|
+
className?: string;
|
|
107
|
+
onClick?: () => void;
|
|
206
108
|
}
|
|
207
109
|
|
|
208
|
-
/**
|
|
209
|
-
* Analytics card props
|
|
210
|
-
*/
|
|
211
110
|
export interface AnalyticsCardProps {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
size?: "sm" | "md" | "lg" | "full";
|
|
222
|
-
/** Loading state */
|
|
223
|
-
loading?: boolean;
|
|
224
|
-
/** Error message */
|
|
225
|
-
error?: string;
|
|
226
|
-
/** Custom class name */
|
|
227
|
-
className?: string;
|
|
228
|
-
/** Children content */
|
|
229
|
-
children?: React.ReactNode;
|
|
111
|
+
title?: string;
|
|
112
|
+
description?: string;
|
|
113
|
+
chart?: ChartConfig;
|
|
114
|
+
metrics?: Metric[];
|
|
115
|
+
size?: "sm" | "md" | "lg" | "full";
|
|
116
|
+
loading?: boolean;
|
|
117
|
+
error?: string;
|
|
118
|
+
className?: string;
|
|
119
|
+
children?: React.ReactNode;
|
|
230
120
|
}
|
|
231
121
|
|
|
232
|
-
/**
|
|
233
|
-
* Analytics layout props
|
|
234
|
-
*/
|
|
235
122
|
export interface AnalyticsLayoutProps {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
showDateRange?: boolean;
|
|
252
|
-
/** Refresh button */
|
|
253
|
-
showRefresh?: boolean;
|
|
254
|
-
/** Export button */
|
|
255
|
-
showExport?: boolean;
|
|
256
|
-
/** Charts configuration */
|
|
257
|
-
charts?: ChartConfig[];
|
|
258
|
-
/** Refresh handler — required when showRefresh is true */
|
|
259
|
-
onRefresh?: () => void;
|
|
260
|
-
/** Export handler — required when showExport is true */
|
|
261
|
-
onExport?: () => void;
|
|
262
|
-
/** Custom header content */
|
|
263
|
-
headerContent?: React.ReactNode;
|
|
264
|
-
/** Children content */
|
|
265
|
-
children?: React.ReactNode;
|
|
123
|
+
config?: AnalyticsConfig;
|
|
124
|
+
title?: string;
|
|
125
|
+
description?: string;
|
|
126
|
+
metrics?: Metric[];
|
|
127
|
+
loading?: boolean;
|
|
128
|
+
period?: AnalyticsPeriod;
|
|
129
|
+
onPeriodChange?: (period: AnalyticsPeriod) => void;
|
|
130
|
+
showDateRange?: boolean;
|
|
131
|
+
showRefresh?: boolean;
|
|
132
|
+
showExport?: boolean;
|
|
133
|
+
charts?: ChartConfig[];
|
|
134
|
+
onRefresh?: () => void;
|
|
135
|
+
onExport?: () => void;
|
|
136
|
+
headerContent?: React.ReactNode;
|
|
137
|
+
children?: React.ReactNode;
|
|
266
138
|
}
|
|
267
139
|
|
|
268
|
-
/**
|
|
269
|
-
* Date range preset
|
|
270
|
-
*/
|
|
271
140
|
export interface DateRangePreset {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
days: number;
|
|
278
|
-
/** Range start (ISO yyyy-mm-dd), resolved at creation time */
|
|
279
|
-
from?: string;
|
|
280
|
-
/** Range end (ISO yyyy-mm-dd), resolved at creation time */
|
|
281
|
-
to?: string;
|
|
141
|
+
label: string;
|
|
142
|
+
value: string;
|
|
143
|
+
days: number;
|
|
144
|
+
from?: string;
|
|
145
|
+
to?: string;
|
|
282
146
|
}
|
|
283
147
|
|
|
284
|
-
/**
|
|
285
|
-
* Date range value
|
|
286
|
-
*/
|
|
287
148
|
export interface DateRangeValue {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
to: string;
|
|
292
|
-
/** Preset label */
|
|
293
|
-
preset?: string;
|
|
149
|
+
from: string;
|
|
150
|
+
to: string;
|
|
151
|
+
preset?: string;
|
|
294
152
|
}
|
|
295
153
|
|
|
296
|
-
/**
|
|
297
|
-
* Export format
|
|
298
|
-
*/
|
|
299
154
|
export type ExportFormat = "csv" | "json" | "xlsx" | "pdf";
|
|
300
155
|
|
|
301
|
-
/**
|
|
302
|
-
* Analytics export options
|
|
303
|
-
*/
|
|
304
156
|
export interface AnalyticsExportOptions {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
includeMetrics?: boolean;
|
|
311
|
-
/** Date range */
|
|
312
|
-
dateRange?: DateRangeValue;
|
|
313
|
-
/** Filename */
|
|
314
|
-
filename?: string;
|
|
157
|
+
format: ExportFormat;
|
|
158
|
+
includeCharts?: boolean;
|
|
159
|
+
includeMetrics?: boolean;
|
|
160
|
+
dateRange?: DateRangeValue;
|
|
161
|
+
filename?: string;
|
|
315
162
|
}
|
|
316
163
|
|
|
317
|
-
/**
|
|
318
|
-
* Analytics configuration
|
|
319
|
-
*/
|
|
320
164
|
export interface AnalyticsConfig {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
showExport?: boolean;
|
|
329
|
-
/** Show refresh button */
|
|
330
|
-
showRefresh?: boolean;
|
|
331
|
-
/** Enable real-time updates */
|
|
332
|
-
enableRealTime?: boolean;
|
|
333
|
-
/** Metrics configuration */
|
|
334
|
-
metrics?: MetricConfig[];
|
|
165
|
+
brandName: string;
|
|
166
|
+
defaultPeriod?: string;
|
|
167
|
+
availablePeriods?: string[];
|
|
168
|
+
showExport?: boolean;
|
|
169
|
+
showRefresh?: boolean;
|
|
170
|
+
enableRealTime?: boolean;
|
|
171
|
+
metrics?: MetricConfig[];
|
|
335
172
|
}
|
|
336
173
|
|
|
337
|
-
/**
|
|
338
|
-
* Metric configuration
|
|
339
|
-
*/
|
|
340
174
|
export interface MetricConfig {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
/** Unit type */
|
|
346
|
-
unit?: string;
|
|
347
|
-
/** Icon identifier */
|
|
348
|
-
icon?: string;
|
|
175
|
+
id: string;
|
|
176
|
+
name: string;
|
|
177
|
+
unit?: string;
|
|
178
|
+
icon?: string;
|
|
349
179
|
}
|
|
350
180
|
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for pure analytics utility functions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import { describe, it, expect } from "vitest";
|
|
6
2
|
import {
|
|
7
3
|
formatNumber,
|
|
@@ -53,9 +49,7 @@ describe("formatCurrency", () => {
|
|
|
53
49
|
});
|
|
54
50
|
|
|
55
51
|
it("honors the locale parameter", () => {
|
|
56
|
-
|
|
57
|
-
// expect 2 decimal places, which "en-US"/"de-DE" format as ,50 / ,50.
|
|
58
|
-
expect(formatCurrency(1234.5, "USD", 2, "en-US")).toBe("$1,234.50");
|
|
52
|
+
expect(formatCurrency(1234.5, "USD", 2, "en-US")).toBe("$1,234.50");
|
|
59
53
|
expect(formatCurrency(1234.5, "EUR", 2, "de-DE")).toContain("1.234,50");
|
|
60
54
|
});
|
|
61
55
|
});
|
|
@@ -128,8 +122,7 @@ describe("detectOutliers", () => {
|
|
|
128
122
|
});
|
|
129
123
|
|
|
130
124
|
it("returns indices of values beyond 2 standard deviations", () => {
|
|
131
|
-
|
|
132
|
-
expect(detectOutliers([10, 10, 10, 10, 10, 50])).toEqual([5]);
|
|
125
|
+
expect(detectOutliers([10, 10, 10, 10, 10, 50])).toEqual([5]);
|
|
133
126
|
});
|
|
134
127
|
|
|
135
128
|
it("returns no indices for a uniform series", () => {
|
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analytics Utilities
|
|
3
|
-
*
|
|
4
|
-
* Helper functions for analytics operations
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { TimeSeriesData } from "../types/analytics";
|
|
8
2
|
|
|
9
3
|
import type { Metric, KPIData, DateRangePreset } from "../types/analytics";
|
|
10
4
|
|
|
11
|
-
/**
|
|
12
|
-
* Format number with K/M/B suffixes
|
|
13
|
-
*
|
|
14
|
-
* @param num - Number to format
|
|
15
|
-
* @param decimals - Number of decimal places (default: 1)
|
|
16
|
-
* @returns Formatted string
|
|
17
|
-
*/
|
|
18
5
|
export function formatNumber(num: number, decimals: number = 1): string {
|
|
19
6
|
if (num >= 1_000_000_000) {
|
|
20
7
|
return (num / 1_000_000_000).toFixed(decimals) + "B";
|
|
@@ -25,17 +12,9 @@ export function formatNumber(num: number, decimals: number = 1): string {
|
|
|
25
12
|
if (num >= 1_000) {
|
|
26
13
|
return (num / 1_000).toFixed(decimals) + "K";
|
|
27
14
|
}
|
|
28
|
-
|
|
29
|
-
return Number.isInteger(num) ? num.toString() : num.toFixed(decimals);
|
|
15
|
+
return Number.isInteger(num) ? num.toString() : num.toFixed(decimals);
|
|
30
16
|
}
|
|
31
17
|
|
|
32
|
-
/**
|
|
33
|
-
* Format percentage
|
|
34
|
-
*
|
|
35
|
-
* @param value - Value to format as percentage
|
|
36
|
-
* @param decimals - Number of decimal places (default: 1)
|
|
37
|
-
* @returns Formatted string with % suffix
|
|
38
|
-
*/
|
|
39
18
|
export function formatPercentage(value: number, decimals: number = 1): string {
|
|
40
19
|
return `${value.toFixed(decimals)}%`;
|
|
41
20
|
}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar Configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import type { CalendarConfig } from './types/calendar.types';
|
|
6
2
|
|
|
7
|
-
/**
|
|
8
|
-
* Default calendar configuration
|
|
9
|
-
*/
|
|
10
3
|
export const DEFAULT_CALENDAR_CONFIG: CalendarConfig = {
|
|
11
4
|
defaultView: 'month',
|
|
12
5
|
showWeekends: true,
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useCalendar
|
|
3
|
-
*
|
|
4
|
-
* React hook for the calendar domain. Delegates persistence
|
|
5
|
-
* to the injected `CalendarService`, guards refreshes with a
|
|
6
|
-
* request id so a stale response can never overwrite a newer one,
|
|
7
|
-
* and returns a clean discriminated CRUD contract.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
11
2
|
import type {
|
|
12
3
|
CalendarConfig,
|
|
@@ -20,14 +11,10 @@ import type { ICalendarService } from '../types/calendar.types';
|
|
|
20
11
|
type CalendarView = 'month' | 'week' | 'day' | 'timeline';
|
|
21
12
|
|
|
22
13
|
export interface UseCalendarOptions {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** Error callback — receives the original Error */
|
|
28
|
-
onError?: (error: Error) => void;
|
|
29
|
-
/** Override the default service (useful for tests) */
|
|
30
|
-
service?: ICalendarService;
|
|
14
|
+
config?: Partial<CalendarConfig>;
|
|
15
|
+
userId: string;
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
service?: ICalendarService;
|
|
31
18
|
}
|
|
32
19
|
|
|
33
20
|
export interface UseCalendarReturn {
|
|
@@ -77,17 +64,12 @@ export function useCalendar(options: UseCalendarOptions): UseCalendarReturn {
|
|
|
77
64
|
const [selectedDate, setSelectedDate] = useState(() => new Date());
|
|
78
65
|
const [filter, setFilterState] = useState<CalendarFilter>({});
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
// when consumers pass a fresh closure each render.
|
|
82
|
-
const onErrorRef = useRef(onError);
|
|
67
|
+
const onErrorRef = useRef(onError);
|
|
83
68
|
useEffect(() => {
|
|
84
69
|
onErrorRef.current = onError;
|
|
85
70
|
}, [onError]);
|
|
86
71
|
|
|
87
|
-
|
|
88
|
-
// refresh may commit state (e.g. user changes the filter while a
|
|
89
|
-
// previous fetch is still resolving).
|
|
90
|
-
const requestIdRef = useRef(0);
|
|
72
|
+
const requestIdRef = useRef(0);
|
|
91
73
|
|
|
92
74
|
const refresh = useCallback(async () => {
|
|
93
75
|
if (!userId) {
|
|
@@ -178,9 +160,7 @@ export function useCalendar(options: UseCalendarOptions): UseCalendarReturn {
|
|
|
178
160
|
setFilterState((prev) => ({ ...prev, ...updates }));
|
|
179
161
|
}, []);
|
|
180
162
|
|
|
181
|
-
|
|
182
|
-
// so client-side refiltering can never drift from server-side semantics.
|
|
183
|
-
const filteredItems = useMemo(
|
|
163
|
+
const filteredItems = useMemo(
|
|
184
164
|
() => applyCalendarFilter(items, filter),
|
|
185
165
|
[items, filter],
|
|
186
166
|
);
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar Service
|
|
3
|
-
*
|
|
4
|
-
* Camp-agnostic calendar service for managing content items.
|
|
5
|
-
* Persistence is injected via the `ICalendarDatabase` seam — the
|
|
6
|
-
* package ships no Firebase (or any other) backend. Use
|
|
7
|
-
* `createInMemoryCalendarDatabase()` for demos and tests, or adapt
|
|
8
|
-
* your own backend to the interface.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import type {
|
|
12
2
|
ContentItem,
|
|
13
3
|
CalendarFilter,
|
|
@@ -16,10 +6,6 @@ import type {
|
|
|
16
6
|
ICalendarService,
|
|
17
7
|
} from '../types/calendar.types';
|
|
18
8
|
|
|
19
|
-
/**
|
|
20
|
-
* Database interface for calendar operations
|
|
21
|
-
* Implementations can provide different backends
|
|
22
|
-
*/
|
|
23
9
|
export interface ICalendarDatabase {
|
|
24
10
|
getItems(userId: string): Promise<ContentItem[]>;
|
|
25
11
|
getItemById(id: string): Promise<ContentItem | null>;
|
|
@@ -28,9 +14,6 @@ export interface ICalendarDatabase {
|
|
|
28
14
|
deleteItem(id: string): Promise<void>;
|
|
29
15
|
}
|
|
30
16
|
|
|
31
|
-
/**
|
|
32
|
-
* Resolve scheduled_at to ISO string regardless of source type
|
|
33
|
-
*/
|
|
34
17
|
const resolveScheduledAt = (value: string | Date | undefined): string => {
|
|
35
18
|
if (value === undefined) {
|
|
36
19
|
throw new Error('scheduled_at is required to create or update a content item');
|
|
@@ -39,10 +22,6 @@ const resolveScheduledAt = (value: string | Date | undefined): string => {
|
|
|
39
22
|
return value.toISOString();
|
|
40
23
|
};
|
|
41
24
|
|
|
42
|
-
/**
|
|
43
|
-
* In-memory calendar database — zero-dependency default for demos
|
|
44
|
-
* and tests. Data lives per-instance for the lifetime of the page.
|
|
45
|
-
*/
|
|
46
25
|
export const createInMemoryCalendarDatabase = (): ICalendarDatabase => {
|
|
47
26
|
const items = new Map<string, ContentItem>();
|
|
48
27
|
|