@umituz/web-dashboard 3.0.0 → 3.1.1
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 -3
- package/src/domain/config/CalendarConfig.ts +22 -0
- package/src/domain/config/index.ts +1 -0
- 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 +33 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-dashboard",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Dashboard Layout System - Comprehensive analytics
|
|
3
|
+
"version": "3.1.1",
|
|
4
|
+
"description": "Dashboard Layout System - Comprehensive analytics, calendar, customizable layouts, and config-based architecture",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"./analytics/utils": "./src/domains/analytics/utils/index.ts",
|
|
35
35
|
"./analytics/types": "./src/domains/analytics/types/index.ts",
|
|
36
36
|
"./analytics/services": "./src/domains/analytics/services/index.ts",
|
|
37
|
+
"./calendar": "./src/domains/calendar/index.ts",
|
|
38
|
+
"./calendar/types": "./src/domains/calendar/types/calendar.types",
|
|
39
|
+
"./calendar/services": "./src/domains/calendar/services/index.ts",
|
|
40
|
+
"./calendar/hooks": "./src/domains/calendar/hooks/index.ts",
|
|
41
|
+
"./calendar/utils": "./src/domains/calendar/utils/index.ts",
|
|
37
42
|
"./config": "./src/domain/config/index.ts",
|
|
38
43
|
"./billing": "./src/domains/billing/index.ts",
|
|
39
44
|
"./billing/components": "./src/domains/billing/components/index.ts",
|
|
@@ -70,7 +75,7 @@
|
|
|
70
75
|
"@types/react-dom": "^19.2.3",
|
|
71
76
|
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
72
77
|
"@typescript-eslint/parser": "^8.57.2",
|
|
73
|
-
"@umituz/web-design-system": "^
|
|
78
|
+
"@umituz/web-design-system": "^3.1.11",
|
|
74
79
|
"eslint": "^10.0.2",
|
|
75
80
|
"react-i18next": "^16.5.4",
|
|
76
81
|
"react-router-dom": "^7.13.1",
|
|
@@ -94,6 +99,11 @@
|
|
|
94
99
|
"behavior-prediction",
|
|
95
100
|
"performance-monitoring",
|
|
96
101
|
"realtime-metrics",
|
|
102
|
+
"calendar",
|
|
103
|
+
"content-calendar",
|
|
104
|
+
"scheduler",
|
|
105
|
+
"content-planning",
|
|
106
|
+
"drag-drop",
|
|
97
107
|
"charts",
|
|
98
108
|
"metrics",
|
|
99
109
|
"kpi",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CalendarConfig } from '../../domains/calendar/types/calendar.types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default calendar configuration
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_CALENDAR_CONFIG: CalendarConfig = {
|
|
11
|
+
defaultView: 'month',
|
|
12
|
+
showWeekends: true,
|
|
13
|
+
startOfWeek: 0,
|
|
14
|
+
hourRange: {
|
|
15
|
+
start: 0,
|
|
16
|
+
end: 23,
|
|
17
|
+
},
|
|
18
|
+
enableDragDrop: true,
|
|
19
|
+
showPlatformFilters: true,
|
|
20
|
+
platforms: ['instagram', 'facebook', 'twitter', 'linkedin', 'tiktok', 'youtube'],
|
|
21
|
+
slotDuration: 30,
|
|
22
|
+
};
|
|
@@ -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,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Service
|
|
3
|
+
*
|
|
4
|
+
* Firebase-based calendar service for managing content items
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ContentItem,
|
|
9
|
+
CalendarFilter,
|
|
10
|
+
CreateContentItemParams,
|
|
11
|
+
UpdateContentItemParams,
|
|
12
|
+
ICalendarService,
|
|
13
|
+
} from '../types/calendar.types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Database interface for calendar operations
|
|
17
|
+
* Implementations can provide different backends
|
|
18
|
+
*/
|
|
19
|
+
interface ICalendarDatabase {
|
|
20
|
+
getItems(userId: string): Promise<ContentItem[]>;
|
|
21
|
+
getItemById(id: string): Promise<ContentItem | null>;
|
|
22
|
+
createItem(userId: string, item: CreateContentItemParams): Promise<ContentItem>;
|
|
23
|
+
updateItem(id: string, updates: UpdateContentItemParams): Promise<void>;
|
|
24
|
+
deleteItem(id: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Firebase implementation of calendar database
|
|
29
|
+
*/
|
|
30
|
+
class FirebaseCalendarDatabase implements ICalendarDatabase {
|
|
31
|
+
async getItems(userId: string): Promise<ContentItem[]> {
|
|
32
|
+
// Lazy load Firebase
|
|
33
|
+
const { collection, query, where, getDocs } = await import('firebase/firestore');
|
|
34
|
+
const { db } = await import('@umituz/web-firebase');
|
|
35
|
+
|
|
36
|
+
const calendarQuery = query(
|
|
37
|
+
collection(db as any, 'calendar_items'),
|
|
38
|
+
where("user_id", "==", userId)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const postsQuery = query(
|
|
42
|
+
collection(db as any, "posts"),
|
|
43
|
+
where("userId", "==", userId)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const [calendarSnap, postsSnap] = await Promise.all([
|
|
47
|
+
getDocs(calendarQuery),
|
|
48
|
+
getDocs(postsQuery)
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const calendarItems = calendarSnap.docs.map((doc: any) => ({
|
|
52
|
+
id: doc.id,
|
|
53
|
+
...doc.data()
|
|
54
|
+
} as ContentItem));
|
|
55
|
+
|
|
56
|
+
const postItems = postsSnap.docs.map((doc: any) => {
|
|
57
|
+
const data = doc.data();
|
|
58
|
+
return {
|
|
59
|
+
id: doc.id,
|
|
60
|
+
title: data.title || 'Untitled Post',
|
|
61
|
+
description: data.content || '',
|
|
62
|
+
scheduled_at: data.scheduledAt
|
|
63
|
+
? (typeof data.scheduledAt === 'string' ? data.scheduledAt : (data.scheduledAt as any)?.toDate?.().toISOString())
|
|
64
|
+
: new Date().toISOString(),
|
|
65
|
+
platforms: data.platform ? [data.platform] : [],
|
|
66
|
+
app_name: data.appName || 'My App',
|
|
67
|
+
status: data.status || 'draft',
|
|
68
|
+
type: 'post'
|
|
69
|
+
} as ContentItem;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return [...calendarItems, ...postItems].sort((a, b) =>
|
|
73
|
+
new Date(b.scheduled_at).getTime() - new Date(a.scheduled_at).getTime()
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getItemById(id: string): Promise<ContentItem | null> {
|
|
78
|
+
const { doc, getDoc } = await import('firebase/firestore');
|
|
79
|
+
const { db } = await import('@umituz/web-firebase');
|
|
80
|
+
|
|
81
|
+
const docRef = doc(db as any, 'calendar_items', id);
|
|
82
|
+
const snap = await getDoc(docRef);
|
|
83
|
+
|
|
84
|
+
if (!snap.exists()) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
id: snap.id,
|
|
90
|
+
...snap.data()
|
|
91
|
+
} as ContentItem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async createItem(userId: string, item: CreateContentItemParams): Promise<ContentItem> {
|
|
95
|
+
const { collection, addDoc, serverTimestamp } = await import('firebase/firestore');
|
|
96
|
+
const { db } = await import('@umituz/web-firebase');
|
|
97
|
+
|
|
98
|
+
const docRef = await addDoc(collection(db as any, 'calendar_items'), {
|
|
99
|
+
...item,
|
|
100
|
+
scheduled_at: typeof item.scheduled_at === 'string' ? item.scheduled_at : item.scheduled_at.toISOString(),
|
|
101
|
+
user_id: userId,
|
|
102
|
+
created_at: serverTimestamp(),
|
|
103
|
+
updated_at: serverTimestamp()
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id: docRef.id,
|
|
108
|
+
...item,
|
|
109
|
+
scheduled_at: typeof item.scheduled_at === 'string' ? item.scheduled_at : item.scheduled_at.toISOString(),
|
|
110
|
+
user_id: userId,
|
|
111
|
+
created_at: new Date().toISOString()
|
|
112
|
+
} as ContentItem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async updateItem(id: string, updates: UpdateContentItemParams): Promise<void> {
|
|
116
|
+
const { doc, updateDoc, serverTimestamp } = await import('firebase/firestore');
|
|
117
|
+
const { db } = await import('@umituz/web-firebase');
|
|
118
|
+
|
|
119
|
+
const docRef = doc(db as any, 'calendar_items', id);
|
|
120
|
+
|
|
121
|
+
const updateData: any = { ...updates };
|
|
122
|
+
if (updates.scheduled_at) {
|
|
123
|
+
updateData.scheduled_at = typeof updates.scheduled_at === 'string'
|
|
124
|
+
? updates.scheduled_at
|
|
125
|
+
: updates.scheduled_at.toISOString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await updateDoc(docRef, {
|
|
129
|
+
...updateData,
|
|
130
|
+
updated_at: serverTimestamp()
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async deleteItem(id: string): Promise<void> {
|
|
135
|
+
const { doc, deleteDoc } = await import('firebase/firestore');
|
|
136
|
+
const { db } = await import('@umituz/web-firebase');
|
|
137
|
+
|
|
138
|
+
const docRef = doc(db as any, 'calendar_items', id);
|
|
139
|
+
await deleteDoc(docRef);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Calendar Service Implementation
|
|
145
|
+
*
|
|
146
|
+
* Provides CRUD operations for calendar content items
|
|
147
|
+
* Uses database interface for backend abstraction
|
|
148
|
+
*/
|
|
149
|
+
export class CalendarService implements ICalendarService {
|
|
150
|
+
private static instance: CalendarService;
|
|
151
|
+
private database: ICalendarDatabase;
|
|
152
|
+
|
|
153
|
+
private constructor() {
|
|
154
|
+
// Use Firebase implementation by default
|
|
155
|
+
this.database = new FirebaseCalendarDatabase();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public static getInstance(): CalendarService {
|
|
159
|
+
if (!CalendarService.instance) {
|
|
160
|
+
CalendarService.instance = new CalendarService();
|
|
161
|
+
}
|
|
162
|
+
return CalendarService.instance;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Set database implementation (for testing or custom backends)
|
|
167
|
+
*/
|
|
168
|
+
setDatabase(database: ICalendarDatabase): void {
|
|
169
|
+
this.database = database;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all content items for a user
|
|
174
|
+
*/
|
|
175
|
+
async getContentItems(userId: string, filter?: CalendarFilter): Promise<ContentItem[]> {
|
|
176
|
+
const items = await this.database.getItems(userId);
|
|
177
|
+
|
|
178
|
+
if (!filter) return items;
|
|
179
|
+
|
|
180
|
+
// Apply filters
|
|
181
|
+
return items.filter(item => {
|
|
182
|
+
// Search filter
|
|
183
|
+
if (filter.search && !item.title.toLowerCase().includes(filter.search.toLowerCase())) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Platform filter
|
|
188
|
+
if (filter.platforms && filter.platforms.length > 0) {
|
|
189
|
+
if (!item.platforms.some(p => filter.platforms?.includes(p))) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Type filter
|
|
195
|
+
if (filter.types && filter.types.length > 0) {
|
|
196
|
+
if (!item.type || !filter.types.includes(item.type)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Status filter
|
|
202
|
+
if (filter.status && item.status !== filter.status) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Date range filter
|
|
207
|
+
if (filter.dateRange) {
|
|
208
|
+
const itemDate = new Date(item.scheduled_at);
|
|
209
|
+
if (itemDate < filter.dateRange.start || itemDate > filter.dateRange.end) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get a single content item by ID
|
|
220
|
+
*/
|
|
221
|
+
async getContentItemById(id: string): Promise<ContentItem | null> {
|
|
222
|
+
return this.database.getItemById(id);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create a new content item
|
|
227
|
+
*/
|
|
228
|
+
async createContentItem(userId: string, item: CreateContentItemParams): Promise<ContentItem> {
|
|
229
|
+
return this.database.createItem(userId, item);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Update an existing content item
|
|
234
|
+
*/
|
|
235
|
+
async updateContentItem(id: string, updates: UpdateContentItemParams): Promise<void> {
|
|
236
|
+
await this.database.updateItem(id, updates);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Delete a content item
|
|
241
|
+
*/
|
|
242
|
+
async deleteContentItem(id: string): Promise<void> {
|
|
243
|
+
await this.database.deleteItem(id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Move content item to a new date
|
|
248
|
+
*/
|
|
249
|
+
async moveContentItem(id: string, newDate: Date): Promise<void> {
|
|
250
|
+
await this.updateContentItem(id, {
|
|
251
|
+
scheduled_at: newDate
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Singleton instance
|
|
258
|
+
*/
|
|
259
|
+
export const calendarService = CalendarService.getInstance();
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Domain Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Content item status
|
|
7
|
+
*/
|
|
8
|
+
export type ContentStatus = 'draft' | 'scheduled' | 'published' | 'failed';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Content item type
|
|
12
|
+
*/
|
|
13
|
+
export type ContentType = 'post' | 'story' | 'reel' | 'tweet' | 'article';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Calendar view type
|
|
17
|
+
*/
|
|
18
|
+
export type CalendarView = 'month' | 'week' | 'day' | 'timeline';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Content item
|
|
22
|
+
*/
|
|
23
|
+
export interface ContentItem {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
description: string;
|
|
27
|
+
type?: ContentType;
|
|
28
|
+
status: ContentStatus;
|
|
29
|
+
scheduled_at: string;
|
|
30
|
+
platforms: string[];
|
|
31
|
+
app_name: string;
|
|
32
|
+
user_id?: string;
|
|
33
|
+
media_url?: string;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
created_at?: string;
|
|
36
|
+
updated_at?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calendar event
|
|
41
|
+
*/
|
|
42
|
+
export interface CalendarEvent {
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
date: Date;
|
|
46
|
+
type: 'content' | 'campaign' | 'reminder';
|
|
47
|
+
status: string;
|
|
48
|
+
color?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Calendar filter
|
|
53
|
+
*/
|
|
54
|
+
export interface CalendarFilter {
|
|
55
|
+
search?: string;
|
|
56
|
+
platforms?: string[];
|
|
57
|
+
types?: ContentType[];
|
|
58
|
+
status?: ContentStatus;
|
|
59
|
+
dateRange?: {
|
|
60
|
+
start: Date;
|
|
61
|
+
end: Date;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calendar configuration
|
|
67
|
+
*/
|
|
68
|
+
export interface CalendarConfig {
|
|
69
|
+
/** Default view */
|
|
70
|
+
defaultView?: CalendarView;
|
|
71
|
+
/** Show weekends */
|
|
72
|
+
showWeekends?: boolean;
|
|
73
|
+
/** Start of week (0-6, 0 = Sunday) */
|
|
74
|
+
startOfWeek?: number;
|
|
75
|
+
/** Hour range for day/week view */
|
|
76
|
+
hourRange?: {
|
|
77
|
+
start: number; // 0-23
|
|
78
|
+
end: number; // 0-23
|
|
79
|
+
};
|
|
80
|
+
/** Enable drag and drop */
|
|
81
|
+
enableDragDrop?: boolean;
|
|
82
|
+
/** Show platform filters */
|
|
83
|
+
showPlatformFilters?: boolean;
|
|
84
|
+
/** Available platforms */
|
|
85
|
+
platforms?: string[];
|
|
86
|
+
/** Time slot duration in minutes */
|
|
87
|
+
slotDuration?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Content item creation params
|
|
92
|
+
*/
|
|
93
|
+
export interface CreateContentItemParams {
|
|
94
|
+
title: string;
|
|
95
|
+
description: string;
|
|
96
|
+
scheduled_at: string | Date;
|
|
97
|
+
platforms: string[];
|
|
98
|
+
app_name: string;
|
|
99
|
+
type?: ContentType;
|
|
100
|
+
status?: ContentStatus;
|
|
101
|
+
media_url?: string;
|
|
102
|
+
tags?: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Content item update params
|
|
107
|
+
*/
|
|
108
|
+
export interface UpdateContentItemParams {
|
|
109
|
+
title?: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
scheduled_at?: string | Date;
|
|
112
|
+
platforms?: string[];
|
|
113
|
+
type?: ContentType;
|
|
114
|
+
status?: ContentStatus;
|
|
115
|
+
media_url?: string;
|
|
116
|
+
tags?: string[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Calendar service interface
|
|
121
|
+
*/
|
|
122
|
+
export interface ICalendarService {
|
|
123
|
+
getContentItems(userId: string, filter?: CalendarFilter): Promise<ContentItem[]>;
|
|
124
|
+
getContentItemById(id: string): Promise<ContentItem | null>;
|
|
125
|
+
createContentItem(userId: string, item: CreateContentItemParams): Promise<ContentItem>;
|
|
126
|
+
updateContentItem(id: string, updates: UpdateContentItemParams): Promise<void>;
|
|
127
|
+
deleteContentItem(id: string): Promise<void>;
|
|
128
|
+
moveContentItem(id: string, newDate: Date): Promise<void>;
|
|
129
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Domain Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CalendarConfig } from '../types/calendar.types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default calendar configuration
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_CALENDAR_CONFIG: CalendarConfig = {
|
|
11
|
+
defaultView: 'month',
|
|
12
|
+
showWeekends: true,
|
|
13
|
+
startOfWeek: 0, // Sunday
|
|
14
|
+
hourRange: {
|
|
15
|
+
start: 0,
|
|
16
|
+
end: 23,
|
|
17
|
+
},
|
|
18
|
+
enableDragDrop: true,
|
|
19
|
+
showPlatformFilters: true,
|
|
20
|
+
platforms: ['instagram', 'facebook', 'twitter', 'linkedin', 'tiktok'],
|
|
21
|
+
slotDuration: 30, // 30 minutes
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get week dates for a given date
|
|
26
|
+
*/
|
|
27
|
+
export function getWeekDates(date: Date, startOfWeek: number = 0): Date[] {
|
|
28
|
+
const week = [];
|
|
29
|
+
const current = new Date(date);
|
|
30
|
+
|
|
31
|
+
// Find the first day of the week
|
|
32
|
+
const day = current.getDay();
|
|
33
|
+
const diff = (day < startOfWeek ? 7 : 0) + day - startOfWeek;
|
|
34
|
+
current.setDate(current.getDate() - diff);
|
|
35
|
+
|
|
36
|
+
// Get 7 days
|
|
37
|
+
for (let i = 0; i < 7; i++) {
|
|
38
|
+
week.push(new Date(current));
|
|
39
|
+
current.setDate(current.getDate() + 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return week;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get month days
|
|
47
|
+
*/
|
|
48
|
+
export function getMonthDays(date: Date): Date[] {
|
|
49
|
+
const year = date.getFullYear();
|
|
50
|
+
const month = date.getMonth();
|
|
51
|
+
const days: Date[] = [];
|
|
52
|
+
|
|
53
|
+
const firstDay = new Date(year, month, 1);
|
|
54
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
55
|
+
|
|
56
|
+
for (let d = new Date(firstDay); d <= lastDay; d.setDate(d.getDate() + 1)) {
|
|
57
|
+
days.push(new Date(d));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return days;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if date is in range
|
|
65
|
+
*/
|
|
66
|
+
export function isDateInRange(date: Date, range: { start: Date; end: Date }): boolean {
|
|
67
|
+
return date >= range.start && date <= range.end;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format date for display
|
|
72
|
+
*/
|
|
73
|
+
export function formatDate(date: Date, format: 'short' | 'long' | 'time' = 'short'): string {
|
|
74
|
+
if (format === 'time') {
|
|
75
|
+
return date.toLocaleTimeString('en-US', {
|
|
76
|
+
hour: '2-digit',
|
|
77
|
+
minute: '2-digit',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return date.toLocaleDateString('en-US', {
|
|
82
|
+
month: format === 'long' ? 'long' : 'short',
|
|
83
|
+
day: 'numeric',
|
|
84
|
+
year: 'numeric',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if dates are same day
|
|
90
|
+
*/
|
|
91
|
+
export function isSameDay(date1: Date, date2: Date): boolean {
|
|
92
|
+
return (
|
|
93
|
+
date1.getFullYear() === date2.getFullYear() &&
|
|
94
|
+
date1.getMonth() === date2.getMonth() &&
|
|
95
|
+
date1.getDate() === date2.getDate()
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get date range for a view
|
|
101
|
+
*/
|
|
102
|
+
export function getViewRange(view: 'month' | 'week', date: Date): { start: Date; end: Date } {
|
|
103
|
+
if (view === 'month') {
|
|
104
|
+
const start = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
105
|
+
const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
106
|
+
return { start, end };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Week view
|
|
110
|
+
const week = getWeekDates(date);
|
|
111
|
+
return {
|
|
112
|
+
start: new Date(week[0].setHours(0, 0, 0, 0)),
|
|
113
|
+
end: new Date(week[6].setHours(23, 59, 59, 999)),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate time slots
|
|
119
|
+
*/
|
|
120
|
+
export function generateTimeSlots(startHour: number, endHour: number, slotDuration: number): string[] {
|
|
121
|
+
const slots: string[] = [];
|
|
122
|
+
|
|
123
|
+
for (let hour = startHour; hour < endHour; hour++) {
|
|
124
|
+
for (let minute = 0; minute < 60; minute += slotDuration) {
|
|
125
|
+
const time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
126
|
+
slots.push(time);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return slots;
|
|
131
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,11 @@ export * from './domains/layouts';
|
|
|
12
12
|
export * from './domains/settings';
|
|
13
13
|
export * from './domains/billing';
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Domains - using selective exports to avoid conflicts
|
|
16
|
+
export * from './domains/layouts';
|
|
17
|
+
export * from './domains/settings';
|
|
18
|
+
export * from './domains/billing';
|
|
19
|
+
export * from './domains/calendar';
|
|
16
20
|
export {
|
|
17
21
|
OnboardingWizard,
|
|
18
22
|
useOnboarding,
|
|
@@ -95,9 +99,37 @@ export {
|
|
|
95
99
|
type RealtimeMetrics,
|
|
96
100
|
} from './domains/analytics';
|
|
97
101
|
|
|
102
|
+
// Calendar
|
|
103
|
+
export {
|
|
104
|
+
useCalendar,
|
|
105
|
+
calendarService,
|
|
106
|
+
// Services
|
|
107
|
+
CalendarService,
|
|
108
|
+
// Utils
|
|
109
|
+
getWeekDates,
|
|
110
|
+
getMonthDays,
|
|
111
|
+
isDateInRange,
|
|
112
|
+
formatDate,
|
|
113
|
+
isSameDay,
|
|
114
|
+
getViewRange,
|
|
115
|
+
generateTimeSlots,
|
|
116
|
+
// Types
|
|
117
|
+
type ContentItem,
|
|
118
|
+
type CalendarEvent,
|
|
119
|
+
type CalendarFilter,
|
|
120
|
+
type CalendarConfig,
|
|
121
|
+
type ContentStatus,
|
|
122
|
+
type ContentType,
|
|
123
|
+
type CalendarView,
|
|
124
|
+
type CreateContentItemParams,
|
|
125
|
+
type UpdateContentItemParams,
|
|
126
|
+
type ICalendarService,
|
|
127
|
+
} from './domains/calendar';
|
|
128
|
+
|
|
98
129
|
// Config with renamed exports to avoid conflicts
|
|
99
130
|
export {
|
|
100
131
|
DEFAULT_DASHBOARD_CONFIG,
|
|
132
|
+
DEFAULT_CALENDAR_CONFIG,
|
|
101
133
|
type AnalyticsServiceType,
|
|
102
134
|
type ExportFormat as DashboardExportFormat,
|
|
103
135
|
type AnalyticsConfig as DashboardAnalyticsConfig,
|