@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.
- package/package.json +21 -2
- package/src/domain/config/CalendarConfig.ts +22 -0
- package/src/domain/config/DashboardConfig.ts +116 -0
- package/src/domain/config/index.ts +6 -0
- package/src/domains/analytics/hooks/useAnalytics.ts +101 -5
- package/src/domains/analytics/index.ts +20 -0
- package/src/domains/analytics/services/AnalyticsEngineService.ts +319 -0
- package/src/domains/analytics/services/PerformanceService.ts +321 -0
- package/src/domains/analytics/services/index.ts +6 -0
- package/src/domains/analytics/utils/analytics.ts +1 -1
- package/src/domains/calendar/components/index.ts +7 -0
- package/src/domains/calendar/hooks/index.ts +5 -0
- package/src/domains/calendar/hooks/useCalendar.ts +232 -0
- package/src/domains/calendar/index.ts +9 -0
- package/src/domains/calendar/services/CalendarService.ts +259 -0
- package/src/domains/calendar/services/index.ts +5 -0
- package/src/domains/calendar/types/calendar.types.ts +129 -0
- package/src/domains/calendar/utils/index.ts +131 -0
- package/src/index.ts +140 -0
|
@@ -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();
|
|
@@ -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,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
|
+
}
|