@umituz/web-dashboard 2.1.0 → 2.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/package.json +13 -1
- package/src/domains/analytics/components/AnalyticsCard.tsx +80 -0
- package/src/domains/analytics/components/AnalyticsChart.tsx +184 -0
- package/src/domains/analytics/components/AnalyticsLayout.tsx +150 -0
- package/src/domains/analytics/components/MetricCard.tsx +108 -0
- package/src/domains/analytics/components/index.ts +10 -0
- package/src/domains/analytics/hooks/index.ts +7 -0
- package/src/domains/analytics/hooks/useAnalytics.ts +178 -0
- package/src/domains/analytics/index.ts +62 -0
- package/src/domains/analytics/types/analytics.ts +291 -0
- package/src/domains/analytics/types/index.ts +26 -0
- package/src/domains/analytics/utils/analytics.ts +333 -0
- package/src/domains/analytics/utils/index.ts +26 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for analytics operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Metric, KPIData, DateRangePreset } from "../types/analytics";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format number with K/M/B suffixes
|
|
11
|
+
*
|
|
12
|
+
* @param num - Number to format
|
|
13
|
+
* @param decimals - Number of decimal places (default: 1)
|
|
14
|
+
* @returns Formatted string
|
|
15
|
+
*/
|
|
16
|
+
export function formatNumber(num: number, decimals: number = 1): string {
|
|
17
|
+
if (num >= 1_000_000_000) {
|
|
18
|
+
return (num / 1_000_000_000).toFixed(decimals) + "B";
|
|
19
|
+
}
|
|
20
|
+
if (num >= 1_000_000) {
|
|
21
|
+
return (num / 1_000_000).toFixed(decimals) + "M";
|
|
22
|
+
}
|
|
23
|
+
if (num >= 1_000) {
|
|
24
|
+
return (num / 1_000).toFixed(decimals) + "K";
|
|
25
|
+
}
|
|
26
|
+
return num.toFixed(decimals);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format percentage
|
|
31
|
+
*
|
|
32
|
+
* @param value - Value to format as percentage
|
|
33
|
+
* @param decimals - Number of decimal places (default: 1)
|
|
34
|
+
* @returns Formatted string with % suffix
|
|
35
|
+
*/
|
|
36
|
+
export function formatPercentage(value: number, decimals: number = 1): string {
|
|
37
|
+
return `${value.toFixed(decimals)}%`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format currency
|
|
42
|
+
*
|
|
43
|
+
* @param value - Value to format
|
|
44
|
+
* @param currency - Currency code (default: USD)
|
|
45
|
+
* @param decimals - Number of decimal places (default: 0)
|
|
46
|
+
* @returns Formatted currency string
|
|
47
|
+
*/
|
|
48
|
+
export function formatCurrency(
|
|
49
|
+
value: number,
|
|
50
|
+
currency: string = "USD",
|
|
51
|
+
decimals: number = 0
|
|
52
|
+
): string {
|
|
53
|
+
return new Intl.NumberFormat("en-US", {
|
|
54
|
+
style: "currency",
|
|
55
|
+
currency,
|
|
56
|
+
minimumFractionDigits: decimals,
|
|
57
|
+
maximumFractionDigits: decimals,
|
|
58
|
+
}).format(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Calculate growth rate
|
|
63
|
+
*
|
|
64
|
+
* @param current - Current value
|
|
65
|
+
* @param previous - Previous value
|
|
66
|
+
* @returns Growth rate percentage
|
|
67
|
+
*/
|
|
68
|
+
export function calculateGrowth(current: number, previous: number): number {
|
|
69
|
+
if (previous === 0) return current > 0 ? 100 : 0;
|
|
70
|
+
return ((current - previous) / previous) * 100;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get trend direction from growth
|
|
75
|
+
*
|
|
76
|
+
* @param growth - Growth rate
|
|
77
|
+
* @returns Trend direction
|
|
78
|
+
*/
|
|
79
|
+
export function getTrend(growth: number): "up" | "down" | "stable" {
|
|
80
|
+
if (growth > 0.1) return "up";
|
|
81
|
+
if (growth < -0.1) return "down";
|
|
82
|
+
return "stable";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create KPI data object
|
|
87
|
+
*
|
|
88
|
+
* @param current - Current value
|
|
89
|
+
* @param previous - Previous value
|
|
90
|
+
* @returns KPI data object
|
|
91
|
+
*/
|
|
92
|
+
export function createKPI(current: number, previous: number): KPIData {
|
|
93
|
+
const growth = calculateGrowth(current, previous);
|
|
94
|
+
return {
|
|
95
|
+
current,
|
|
96
|
+
previous,
|
|
97
|
+
growth,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create metric object
|
|
103
|
+
*
|
|
104
|
+
* @param id - Metric ID
|
|
105
|
+
* @param name - Metric name
|
|
106
|
+
* @param value - Current value
|
|
107
|
+
* @param previousValue - Previous value
|
|
108
|
+
* @param unit - Optional unit
|
|
109
|
+
* @returns Metric object
|
|
110
|
+
*/
|
|
111
|
+
export function createMetric(
|
|
112
|
+
id: string,
|
|
113
|
+
name: string,
|
|
114
|
+
value: number,
|
|
115
|
+
previousValue?: number,
|
|
116
|
+
unit?: string
|
|
117
|
+
): Metric {
|
|
118
|
+
const growth = previousValue !== undefined ? calculateGrowth(value, previousValue) : 0;
|
|
119
|
+
return {
|
|
120
|
+
id,
|
|
121
|
+
name,
|
|
122
|
+
value,
|
|
123
|
+
previousValue,
|
|
124
|
+
unit,
|
|
125
|
+
trend: getTrend(growth),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format metric value based on unit
|
|
131
|
+
*
|
|
132
|
+
* @param metric - Metric object
|
|
133
|
+
* @returns Formatted value string
|
|
134
|
+
*/
|
|
135
|
+
export function formatMetricValue(metric: Metric): string {
|
|
136
|
+
const { value, unit } = metric;
|
|
137
|
+
|
|
138
|
+
switch (unit) {
|
|
139
|
+
case "%":
|
|
140
|
+
return formatPercentage(value);
|
|
141
|
+
case "$":
|
|
142
|
+
case "€":
|
|
143
|
+
case "£":
|
|
144
|
+
return formatCurrency(value, unit === "$" ? "USD" : unit === "€" ? "EUR" : "GBP");
|
|
145
|
+
case "K":
|
|
146
|
+
case "M":
|
|
147
|
+
case "B":
|
|
148
|
+
return formatNumber(value);
|
|
149
|
+
default:
|
|
150
|
+
return value.toLocaleString();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Calculate conversion rate
|
|
156
|
+
*
|
|
157
|
+
* @param converted - Number of conversions
|
|
158
|
+
* @param total - Total number of users
|
|
159
|
+
* @returns Conversion rate percentage
|
|
160
|
+
*/
|
|
161
|
+
export function calculateConversionRate(converted: number, total: number): number {
|
|
162
|
+
if (total === 0) return 0;
|
|
163
|
+
return (converted / total) * 100;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Calculate drop-off rate
|
|
168
|
+
*
|
|
169
|
+
* @param current - Current step count
|
|
170
|
+
* @param previous - Previous step count
|
|
171
|
+
* @returns Drop-off rate percentage
|
|
172
|
+
*/
|
|
173
|
+
export function calculateDropOffRate(current: number, previous: number): number {
|
|
174
|
+
if (previous === 0) return 0;
|
|
175
|
+
return ((previous - current) / previous) * 100;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate date range preset
|
|
180
|
+
*
|
|
181
|
+
* @param label - Preset label
|
|
182
|
+
* @param days - Number of days
|
|
183
|
+
* @returns Date range preset
|
|
184
|
+
*/
|
|
185
|
+
export function createDateRangePreset(label: string, days: number): DateRangePreset {
|
|
186
|
+
const to = new Date();
|
|
187
|
+
const from = new Date();
|
|
188
|
+
from.setDate(from.getDate() - days);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
label,
|
|
192
|
+
value: label.toLowerCase().replace(/\s+/g, "-"),
|
|
193
|
+
days,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get common date range presets
|
|
199
|
+
*
|
|
200
|
+
* @returns Array of date range presets
|
|
201
|
+
*/
|
|
202
|
+
export function getDateRangePresets(): DateRangePreset[] {
|
|
203
|
+
return [
|
|
204
|
+
createDateRangePreset("Last 7 Days", 7),
|
|
205
|
+
createDateRangePreset("Last 30 Days", 30),
|
|
206
|
+
createDateRangePreset("Last 90 Days", 90),
|
|
207
|
+
createDateRangePreset("This Year", 365),
|
|
208
|
+
createDateRangePreset("All Time", 365 * 10),
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Aggregate data by time period
|
|
214
|
+
*
|
|
215
|
+
* @param data - Array of data points with date field
|
|
216
|
+
* @param period - Aggregation period (day, week, month)
|
|
217
|
+
* @returns Aggregated data
|
|
218
|
+
*/
|
|
219
|
+
export function aggregateByPeriod(
|
|
220
|
+
data: Array<{ date: string; [key: string]: number | string }>,
|
|
221
|
+
period: "day" | "week" | "month" = "day"
|
|
222
|
+
): Array<{ date: string; [key: string]: number }> {
|
|
223
|
+
const grouped = new Map<string, Array<typeof data[0]>>();
|
|
224
|
+
|
|
225
|
+
data.forEach((item) => {
|
|
226
|
+
const date = new Date(item.date);
|
|
227
|
+
let key: string;
|
|
228
|
+
|
|
229
|
+
if (period === "day") {
|
|
230
|
+
key = date.toISOString().split("T")[0];
|
|
231
|
+
} else if (period === "week") {
|
|
232
|
+
const weekStart = new Date(date);
|
|
233
|
+
weekStart.setDate(date.getDate() - date.getDay());
|
|
234
|
+
key = weekStart.toISOString().split("T")[0];
|
|
235
|
+
} else {
|
|
236
|
+
// month
|
|
237
|
+
key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!grouped.has(key)) {
|
|
241
|
+
grouped.set(key, []);
|
|
242
|
+
}
|
|
243
|
+
grouped.get(key)!.push(item);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return Array.from(grouped.entries()).map(([date, items]) => {
|
|
247
|
+
const aggregated: any = { date };
|
|
248
|
+
|
|
249
|
+
// Sum all numeric fields
|
|
250
|
+
items.forEach((item) => {
|
|
251
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
252
|
+
if (key !== "date" && typeof value === "number") {
|
|
253
|
+
aggregated[key] = (aggregated[key] || 0) + value;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return aggregated;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Calculate moving average
|
|
264
|
+
*
|
|
265
|
+
* @param data - Array of numbers
|
|
266
|
+
* @param window - Window size
|
|
267
|
+
* @returns Array of moving averages
|
|
268
|
+
*/
|
|
269
|
+
export function calculateMovingAverage(data: number[], window: number): number[] {
|
|
270
|
+
const result: number[] = [];
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < data.length; i++) {
|
|
273
|
+
const start = Math.max(0, i - window + 1);
|
|
274
|
+
const subset = data.slice(start, i + 1);
|
|
275
|
+
const avg = subset.reduce((sum, val) => sum + val, 0) / subset.length;
|
|
276
|
+
result.push(avg);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Detect outliers in data
|
|
284
|
+
*
|
|
285
|
+
* @param data - Array of numbers
|
|
286
|
+
* @param threshold - Standard deviation threshold (default: 2)
|
|
287
|
+
* @returns Array of outlier indices
|
|
288
|
+
*/
|
|
289
|
+
export function detectOutliers(data: number[], threshold: number = 2): number[] {
|
|
290
|
+
const mean = data.reduce((sum, val) => sum + val, 0) / data.length;
|
|
291
|
+
const variance = data.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / data.length;
|
|
292
|
+
const stdDev = Math.sqrt(variance);
|
|
293
|
+
|
|
294
|
+
return data
|
|
295
|
+
.map((value, index) => ({ value, index }))
|
|
296
|
+
.filter(({ value }) => Math.abs(value - mean) > threshold * stdDev)
|
|
297
|
+
.map(({ index }) => index);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Round to decimal places
|
|
302
|
+
*
|
|
303
|
+
* @param num - Number to round
|
|
304
|
+
* @param decimals - Decimal places (default: 2)
|
|
305
|
+
* @returns Rounded number
|
|
306
|
+
*/
|
|
307
|
+
export function roundTo(num: number, decimals: number = 2): number {
|
|
308
|
+
const multiplier = Math.pow(10, decimals);
|
|
309
|
+
return Math.round(num * multiplier) / multiplier;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Generate random color
|
|
314
|
+
*
|
|
315
|
+
* @param index - Color index
|
|
316
|
+
* @param alpha - Alpha value (0-1)
|
|
317
|
+
* @returns HSL color string
|
|
318
|
+
*/
|
|
319
|
+
export function generateColor(index: number, alpha: number = 1): string {
|
|
320
|
+
const hue = (index * 137.508) % 360; // Golden angle approximation
|
|
321
|
+
return `hsla(${hue}, 70%, 50%, ${alpha})`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Generate chart colors
|
|
326
|
+
*
|
|
327
|
+
* @param count - Number of colors
|
|
328
|
+
* @param alpha - Alpha value (0-1)
|
|
329
|
+
* @returns Array of color strings
|
|
330
|
+
*/
|
|
331
|
+
export function generateChartColors(count: number, alpha: number = 1): string[] {
|
|
332
|
+
return Array.from({ length: count }, (_, i) => generateColor(i, alpha));
|
|
333
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Utilities
|
|
3
|
+
*
|
|
4
|
+
* Export all analytics utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
formatNumber,
|
|
9
|
+
formatPercentage,
|
|
10
|
+
formatCurrency,
|
|
11
|
+
calculateGrowth,
|
|
12
|
+
getTrend,
|
|
13
|
+
createKPI,
|
|
14
|
+
createMetric,
|
|
15
|
+
formatMetricValue,
|
|
16
|
+
calculateConversionRate,
|
|
17
|
+
calculateDropOffRate,
|
|
18
|
+
createDateRangePreset,
|
|
19
|
+
getDateRangePresets,
|
|
20
|
+
aggregateByPeriod,
|
|
21
|
+
calculateMovingAverage,
|
|
22
|
+
detectOutliers,
|
|
23
|
+
roundTo,
|
|
24
|
+
generateColor,
|
|
25
|
+
generateChartColors,
|
|
26
|
+
} from "./analytics";
|