@umituz/web-dashboard 2.5.2 → 3.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.
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Performance Service
3
+ *
4
+ * Real-time metrics and dashboard performance monitoring
5
+ */
6
+
7
+ export interface PerformanceMetric {
8
+ name: string;
9
+ value: number;
10
+ unit: string;
11
+ timestamp: number;
12
+ threshold?: number;
13
+ status: 'good' | 'warning' | 'critical';
14
+ }
15
+
16
+ export interface DashboardMetrics {
17
+ loadTime: number;
18
+ renderTime: number;
19
+ apiResponseTime: number;
20
+ memoryUsage: number;
21
+ errorRate: number;
22
+ activeUsers: number;
23
+ }
24
+
25
+ export interface KPIData {
26
+ current: number;
27
+ previous: number;
28
+ growth: number;
29
+ }
30
+
31
+ export interface RealtimeMetrics {
32
+ timestamp: number;
33
+ users: number;
34
+ sessions: number;
35
+ pageViews: number;
36
+ conversions: number;
37
+ revenue: number;
38
+ }
39
+
40
+ /**
41
+ * Performance Service
42
+ *
43
+ * Monitors dashboard performance and provides real-time metrics
44
+ */
45
+ export class PerformanceService {
46
+ private metrics: Map<string, PerformanceMetric[]> = new Map();
47
+ private observers: PerformanceObserver[] = [];
48
+
49
+ constructor() {
50
+ if (typeof window !== 'undefined') {
51
+ this.initializeObservers();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Initialize performance observers
57
+ */
58
+ private initializeObservers(): void {
59
+ if ('PerformanceObserver' in window) {
60
+ // Observe layout shifts
61
+ try {
62
+ const observer = new PerformanceObserver((list) => {
63
+ for (const entry of list.getEntries()) {
64
+ if (entry.entryType === 'layout-shift') {
65
+ this.recordMetric('CLS', (entry as any).value, 'score', 0.1);
66
+ }
67
+ }
68
+ });
69
+ observer.observe({ entryTypes: ['layout-shift'] });
70
+ this.observers.push(observer);
71
+ } catch (e) {
72
+ // Layout Shift API not supported
73
+ }
74
+
75
+ // Observe largest contentful paint
76
+ try {
77
+ const observer = new PerformanceObserver((list) => {
78
+ for (const entry of list.getEntries()) {
79
+ if (entry.entryType === 'largest-contentful-paint') {
80
+ this.recordMetric('LCP', entry.startTime, 'ms', 2500);
81
+ }
82
+ }
83
+ });
84
+ observer.observe({ entryTypes: ['largest-contentful-paint'] });
85
+ this.observers.push(observer);
86
+ } catch (e) {
87
+ // LCP API not supported
88
+ }
89
+
90
+ // Observe first input delay
91
+ try {
92
+ const observer = new PerformanceObserver((list) => {
93
+ for (const entry of list.getEntries()) {
94
+ if (entry.entryType === 'first-input') {
95
+ this.recordMetric('FID', (entry as any).processingStart - entry.startTime, 'ms', 100);
96
+ }
97
+ }
98
+ });
99
+ observer.observe({ entryTypes: ['first-input'] });
100
+ this.observers.push(observer);
101
+ } catch (e) {
102
+ // FID API not supported
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Record a performance metric
109
+ *
110
+ * @param name - Metric name
111
+ * @param value - Metric value
112
+ * @param unit - Unit of measurement
113
+ * @param threshold - Warning threshold
114
+ */
115
+ public recordMetric(name: string, value: number, unit: string, threshold?: number): void {
116
+ const status = threshold
117
+ ? value > threshold * 2
118
+ ? 'critical'
119
+ : value > threshold
120
+ ? 'warning'
121
+ : 'good'
122
+ : 'good';
123
+
124
+ const metric: PerformanceMetric = {
125
+ name,
126
+ value,
127
+ unit,
128
+ timestamp: Date.now(),
129
+ threshold,
130
+ status,
131
+ };
132
+
133
+ if (!this.metrics.has(name)) {
134
+ this.metrics.set(name, []);
135
+ }
136
+
137
+ const metrics = this.metrics.get(name)!;
138
+ metrics.push(metric);
139
+
140
+ // Keep only last 100 metrics per name
141
+ if (metrics.length > 100) {
142
+ metrics.shift();
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Get metrics by name
148
+ *
149
+ * @param name - Metric name
150
+ * @returns Array of metrics
151
+ */
152
+ public getMetrics(name: string): PerformanceMetric[] {
153
+ return this.metrics.get(name) || [];
154
+ }
155
+
156
+ /**
157
+ * Get latest metric value
158
+ *
159
+ * @param name - Metric name
160
+ * @returns Latest metric or undefined
161
+ */
162
+ public getLatestMetric(name: string): PerformanceMetric | undefined {
163
+ const metrics = this.getMetrics(name);
164
+ return metrics[metrics.length - 1];
165
+ }
166
+
167
+ /**
168
+ * Get all metrics
169
+ *
170
+ * @returns Map of all metrics
171
+ */
172
+ public getAllMetrics(): Map<string, PerformanceMetric[]> {
173
+ return new Map(this.metrics);
174
+ }
175
+
176
+ /**
177
+ * Clear all metrics
178
+ */
179
+ public clearMetrics(): void {
180
+ this.metrics.clear();
181
+ }
182
+
183
+ /**
184
+ * Calculate dashboard performance metrics
185
+ *
186
+ * @returns Dashboard performance data
187
+ */
188
+ public getDashboardMetrics(): DashboardMetrics {
189
+ const timing = typeof window !== 'undefined' ? window.performance?.timing : null;
190
+ const navigation = typeof window !== 'undefined' ? window.performance?.navigation : null;
191
+
192
+ const loadTime = timing
193
+ ? timing.loadEventEnd - timing.navigationStart
194
+ : this.getLatestMetric('loadTime')?.value || 0;
195
+
196
+ const renderTime = this.getLatestMetric('renderTime')?.value || 0;
197
+ const apiResponseTime = this.getLatestMetric('apiResponseTime')?.value || 0;
198
+ const memoryUsage =
199
+ typeof (performance as any).memory !== 'undefined'
200
+ ? (performance as any).memory.usedJSHeapSize / 1048576
201
+ : 0;
202
+
203
+ const errorRate = this.calculateErrorRate();
204
+
205
+ return {
206
+ loadTime,
207
+ renderTime,
208
+ apiResponseTime,
209
+ memoryUsage,
210
+ errorRate,
211
+ activeUsers: 0, // To be implemented with real data
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Calculate error rate from metrics
217
+ *
218
+ * @returns Error rate percentage
219
+ */
220
+ private calculateErrorRate(): number {
221
+ const errors = this.getMetrics('error');
222
+ if (errors.length === 0) return 0;
223
+
224
+ const recentErrors = errors.filter((e) => e.timestamp > Date.now() - 60000); // Last minute
225
+ return recentErrors.length;
226
+ }
227
+
228
+ /**
229
+ * Simulate real-time metrics
230
+ *
231
+ * @param previous - Previous metrics
232
+ * @returns Simulated real-time metrics
233
+ */
234
+ public simulateRealtimeMetrics(previous?: RealtimeMetrics): RealtimeMetrics {
235
+ const now = Date.now();
236
+
237
+ if (previous) {
238
+ // Simulate changes
239
+ const userChange = Math.floor(Math.random() * 20) - 10;
240
+ const sessionChange = Math.floor(Math.random() * 30) - 15;
241
+ const pageViewChange = Math.floor(Math.random() * 50) - 20;
242
+ const conversionCount = Math.random() > 0.8 ? Math.floor(Math.random() * 3) : 0;
243
+ const revenueAdd = conversionCount * (Math.random() * 100 + 50);
244
+
245
+ return {
246
+ timestamp: now,
247
+ users: Math.max(0, previous.users + userChange),
248
+ sessions: Math.max(0, previous.sessions + sessionChange),
249
+ pageViews: Math.max(0, previous.pageViews + pageViewChange),
250
+ conversions: previous.conversions + conversionCount,
251
+ revenue: previous.revenue + revenueAdd,
252
+ };
253
+ }
254
+
255
+ // Initial values
256
+ return {
257
+ timestamp: now,
258
+ users: Math.floor(Math.random() * 1000) + 500,
259
+ sessions: Math.floor(Math.random() * 1500) + 800,
260
+ pageViews: Math.floor(Math.random() * 5000) + 2000,
261
+ conversions: Math.floor(Math.random() * 50),
262
+ revenue: Math.floor(Math.random() * 5000) + 2000,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Calculate growth KPI
268
+ *
269
+ * @param current - Current value
270
+ * @param previous - Previous value
271
+ * @returns KPI data
272
+ */
273
+ public calculateKPI(current: number, previous: number): KPIData {
274
+ const growth = previous > 0 ? ((current - previous) / previous) * 100 : 0;
275
+ return {
276
+ current,
277
+ previous,
278
+ growth,
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Measure page load time
284
+ */
285
+ public measurePageLoad(): void {
286
+ if (typeof window === 'undefined' || !window.performance) return;
287
+
288
+ window.addEventListener('load', () => {
289
+ setTimeout(() => {
290
+ const timing = window.performance?.timing;
291
+ if (timing) {
292
+ const loadTime = timing.loadEventEnd - timing.navigationStart;
293
+ this.recordMetric('loadTime', loadTime, 'ms', 3000);
294
+ }
295
+ }, 0);
296
+ });
297
+ }
298
+
299
+ /**
300
+ * Measure API response time
301
+ *
302
+ * @param startTime - Request start time
303
+ */
304
+ public measureAPIResponse(startTime: number): void {
305
+ const duration = Date.now() - startTime;
306
+ this.recordMetric('apiResponseTime', duration, 'ms', 1000);
307
+ }
308
+
309
+ /**
310
+ * Disconnect all observers
311
+ */
312
+ public disconnect(): void {
313
+ this.observers.forEach((observer) => observer.disconnect());
314
+ this.observers = [];
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Create singleton instance
320
+ */
321
+ export const performanceService = new PerformanceService();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Analytics Services Index
3
+ */
4
+
5
+ export * from './AnalyticsEngineService';
6
+ export * from './PerformanceService';
@@ -219,7 +219,7 @@ export function getDateRangePresets(): DateRangePreset[] {
219
219
  export function aggregateByPeriod(
220
220
  data: Array<{ date: string; [key: string]: number | string }>,
221
221
  period: "day" | "week" | "month" = "day"
222
- ): Array<{ date: string; [key: string]: number }> {
222
+ ): Array<{ date: string; [key: string]: string | number }> {
223
223
  const grouped = new Map<string, Array<typeof data[0]>>();
224
224
 
225
225
  data.forEach((item) => {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Calendar Components Index
3
+ */
4
+
5
+ // Placeholder - components will be added in next iteration
6
+ // For now, types, services, hooks, and utils are sufficient
7
+ export {} from './index';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Calendar Hooks Index
3
+ */
4
+
5
+ export * from './useCalendar';
@@ -0,0 +1,232 @@
1
+ /**
2
+ * useCalendar Hook
3
+ *
4
+ * React hook for calendar functionality with config support
5
+ */
6
+
7
+ import { useState, useCallback, useEffect, useMemo } from 'react';
8
+ import type { CalendarConfig, ContentItem, CalendarFilter } from '../types/calendar.types';
9
+ import { calendarService } from '../services';
10
+ import { DEFAULT_CALENDAR_CONFIG } from '../utils';
11
+
12
+ interface UseCalendarOptions {
13
+ /** Calendar configuration */
14
+ config?: Partial<CalendarConfig>;
15
+ /** User ID */
16
+ userId: string;
17
+ /** Error callback */
18
+ onError?: (error: Error) => void;
19
+ }
20
+
21
+ interface UseCalendarReturn {
22
+ // Data
23
+ items: ContentItem[];
24
+ loading: boolean;
25
+ error: string | null;
26
+ currentView: CalendarView;
27
+ currentDate: Date;
28
+ selectedDate: Date;
29
+ filter: CalendarFilter;
30
+
31
+ // Actions
32
+ setCurrentView: (view: CalendarView) => void;
33
+ setCurrentDate: (date: Date) => void;
34
+ setSelectedDate: (date: Date) => void;
35
+ setFilter: (filter: Partial<CalendarFilter>) => void;
36
+ refresh: () => Promise<void>;
37
+
38
+ // CRUD
39
+ createItem: (item: Omit<ContentItem, 'id' | 'created_at' | 'updated_at'>) => Promise<ContentItem>;
40
+ updateItem: (id: string, updates: Partial<ContentItem>) => Promise<void>;
41
+ deleteItem: (id: string) => Promise<void>;
42
+ moveItem: (id: string, newDate: Date) => Promise<void>;
43
+
44
+ // Computed
45
+ filteredItems: ContentItem[];
46
+ itemsForDate: (date: Date) => ContentItem[];
47
+ }
48
+
49
+ type CalendarView = 'month' | 'week' | 'day' | 'timeline';
50
+
51
+ /**
52
+ * useCalendar hook
53
+ *
54
+ * Manages calendar state and operations with config support
55
+ */
56
+ export function useCalendar(options: UseCalendarOptions): UseCalendarReturn {
57
+ const { config: userConfig, userId, onError } = options;
58
+
59
+ // Merge config with defaults
60
+ const config = useMemo(() => ({
61
+ ...DEFAULT_CALENDAR_CONFIG,
62
+ ...userConfig,
63
+ }), [userConfig]);
64
+
65
+ // State
66
+ const [items, setItems] = useState<ContentItem[]>([]);
67
+ const [loading, setLoading] = useState(false);
68
+ const [error, setError] = useState<string | null>(null);
69
+ const [currentView, setCurrentView] = useState<CalendarView>(config.defaultView || 'month');
70
+ const [currentDate, setCurrentDate] = useState(new Date());
71
+ const [selectedDate, setSelectedDate] = useState(new Date());
72
+ const [filter, setFilter] = useState<CalendarFilter>({});
73
+
74
+ // Fetch items
75
+ const refresh = useCallback(async () => {
76
+ if (!userId) return;
77
+
78
+ setLoading(true);
79
+ setError(null);
80
+
81
+ try {
82
+ const data = await calendarService.getContentItems(userId, filter);
83
+ setItems(data);
84
+ } catch (err) {
85
+ const errorMessage = err instanceof Error ? err.message : 'Failed to fetch calendar items';
86
+ setError(errorMessage);
87
+ onError?.(err as Error);
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ }, [userId, filter, onError]);
92
+
93
+ // Create item
94
+ const createItem = useCallback(async (item: Omit<ContentItem, 'id' | 'created_at' | 'updated_at'>): Promise<ContentItem> => {
95
+ if (!userId) {
96
+ throw new Error('User ID is required');
97
+ }
98
+
99
+ try {
100
+ const created = await calendarService.createContentItem(userId, item);
101
+ await refresh();
102
+ return created;
103
+ } catch (err) {
104
+ onError?.(err as Error);
105
+ throw err;
106
+ }
107
+ }, [userId, refresh, onError]);
108
+
109
+ // Update item
110
+ const updateItem = useCallback(async (id: string, updates: Partial<ContentItem>): Promise<void> => {
111
+ try {
112
+ await calendarService.updateContentItem(id, updates);
113
+ await refresh();
114
+ } catch (err) {
115
+ onError?.(err as Error);
116
+ throw err;
117
+ }
118
+ }, [refresh, onError]);
119
+
120
+ // Delete item
121
+ const deleteItem = useCallback(async (id: string): Promise<void> => {
122
+ try {
123
+ await calendarService.deleteContentItem(id);
124
+ await refresh();
125
+ } catch (err) {
126
+ onError?.(err as Error);
127
+ throw err;
128
+ }
129
+ }, [refresh, onError]);
130
+
131
+ // Move item
132
+ const moveItem = useCallback(async (id: string, newDate: Date): Promise<void> => {
133
+ try {
134
+ await calendarService.moveContentItem(id, newDate);
135
+ await refresh();
136
+ } catch (err) {
137
+ onError?.(err as Error);
138
+ throw err;
139
+ }
140
+ }, [refresh, onError]);
141
+
142
+ // Filter update
143
+ const updateFilter = useCallback((updates: Partial<CalendarFilter>) => {
144
+ setFilter(prev => ({ ...prev, ...updates }));
145
+ }, []);
146
+
147
+ // Get filtered items
148
+ const filteredItems = useMemo(() => {
149
+ return items.filter(item => {
150
+ // Search filter
151
+ if (filter.search && !item.title.toLowerCase().includes(filter.search.toLowerCase())) {
152
+ return false;
153
+ }
154
+
155
+ // Platform filter
156
+ if (filter.platforms && filter.platforms.length > 0) {
157
+ if (!item.platforms.some(p => filter.platforms?.includes(p))) {
158
+ return false;
159
+ }
160
+ }
161
+
162
+ // Type filter
163
+ if (filter.types && filter.types.length > 0) {
164
+ if (!item.type || !filter.types.includes(item.type)) {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ // Status filter
170
+ if (filter.status && item.status !== filter.status) {
171
+ return false;
172
+ }
173
+
174
+ // Date range filter
175
+ if (filter.dateRange) {
176
+ const itemDate = new Date(item.scheduled_at);
177
+ if (itemDate < filter.dateRange.start || itemDate > filter.dateRange.end) {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ return true;
183
+ });
184
+ }, [items, filter]);
185
+
186
+ // Get items for specific date
187
+ const itemsForDate = useCallback((date: Date): ContentItem[] => {
188
+ const dateStart = new Date(date);
189
+ dateStart.setHours(0, 0, 0, 0);
190
+
191
+ const dateEnd = new Date(date);
192
+ dateEnd.setHours(23, 59, 59, 999);
193
+
194
+ return filteredItems.filter(item => {
195
+ const itemDate = new Date(item.scheduled_at);
196
+ return itemDate >= dateStart && itemDate <= dateEnd;
197
+ });
198
+ }, [filteredItems]);
199
+
200
+ // Initial fetch
201
+ useEffect(() => {
202
+ refresh();
203
+ }, [refresh]);
204
+
205
+ return {
206
+ // Data
207
+ items,
208
+ loading,
209
+ error,
210
+ currentView,
211
+ currentDate,
212
+ selectedDate,
213
+ filter,
214
+
215
+ // Actions
216
+ setCurrentView,
217
+ setCurrentDate,
218
+ setSelectedDate,
219
+ setFilter: updateFilter,
220
+ refresh,
221
+
222
+ // CRUD
223
+ createItem,
224
+ updateItem,
225
+ deleteItem,
226
+ moveItem,
227
+
228
+ // Computed
229
+ filteredItems,
230
+ itemsForDate,
231
+ };
232
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Calendar Domain Index
3
+ */
4
+
5
+ export * from './types/calendar.types';
6
+ export * from './services';
7
+ export * from './hooks';
8
+ export * from './utils';
9
+ export * from './components';