// js/managers/DataManager.js import { eventBus } from '../core/EventBus.js'; import { EventTypes } from '../types/EventTypes.js'; /** * Manages data fetching and API communication * Currently uses mock data until backend is implemented */ export class DataManager { constructor() { this.baseUrl = '/api/events'; this.useMockData = true; // Toggle this when backend is ready this.cache = new Map(); this.init(); } init() { this.subscribeToEvents(); } subscribeToEvents() { // Listen for period changes to fetch new data eventBus.on(EventTypes.PERIOD_CHANGE, (e) => { this.fetchEventsForPeriod(e.detail); }); // Listen for event updates eventBus.on(EventTypes.EVENT_UPDATE, (e) => { this.updateEvent(e.detail); }); // Listen for event creation eventBus.on(EventTypes.EVENT_CREATE, (e) => { this.createEvent(e.detail); }); // Listen for event deletion eventBus.on(EventTypes.EVENT_DELETE, (e) => { this.deleteEvent(e.detail.eventId); }); } /** * Fetch events for a specific period * @param {Object} period - Contains start, end, view */ async fetchEventsForPeriod(period) { const cacheKey = `${period.start}-${period.end}-${period.view}`; // Check cache first if (this.cache.has(cacheKey)) { const cachedData = this.cache.get(cacheKey); eventBus.emit(EventTypes.DATA_FETCH_SUCCESS, cachedData); return cachedData; } // Emit loading start eventBus.emit(EventTypes.DATA_FETCH_START, { period }); try { let data; if (this.useMockData) { // Simulate network delay await this.delay(300); data = this.getMockData(period); } else { // Real API call const params = new URLSearchParams({ start: period.start, end: period.end, view: period.view }); const response = await fetch(`${this.baseUrl}?${params}`); if (!response.ok) throw new Error('Failed to fetch events'); data = await response.json(); } // Cache the data this.cache.set(cacheKey, data); // Emit success eventBus.emit(EventTypes.DATA_FETCH_SUCCESS, data); return data; } catch (error) { eventBus.emit(EventTypes.DATA_FETCH_ERROR, { error: error.message }); throw error; } } /** * Create a new event */ async createEvent(eventData) { eventBus.emit(EventTypes.DATA_SYNC_START, { action: 'create' }); try { if (this.useMockData) { await this.delay(200); const newEvent = { id: `evt-${Date.now()}`, ...eventData, syncStatus: 'synced' }; // Clear cache to force refresh this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'create', event: newEvent }); return newEvent; } else { // Real API call const response = await fetch(this.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(eventData) }); if (!response.ok) throw new Error('Failed to create event'); const newEvent = await response.json(); this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'create', event: newEvent }); return newEvent; } } catch (error) { eventBus.emit(EventTypes.DATA_SYNC_ERROR, { action: 'create', error: error.message }); throw error; } } /** * Update an existing event */ async updateEvent(updateData) { eventBus.emit(EventTypes.DATA_SYNC_START, { action: 'update' }); try { if (this.useMockData) { await this.delay(200); // Clear cache to force refresh this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'update', eventId: updateData.eventId, changes: updateData.changes }); return true; } else { // Real API call const response = await fetch(`${this.baseUrl}/${updateData.eventId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updateData.changes) }); if (!response.ok) throw new Error('Failed to update event'); this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'update', eventId: updateData.eventId }); return true; } } catch (error) { eventBus.emit(EventTypes.DATA_SYNC_ERROR, { action: 'update', error: error.message, eventId: updateData.eventId }); throw error; } } /** * Delete an event */ async deleteEvent(eventId) { eventBus.emit(EventTypes.DATA_SYNC_START, { action: 'delete' }); try { if (this.useMockData) { await this.delay(200); // Clear cache to force refresh this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'delete', eventId }); return true; } else { // Real API call const response = await fetch(`${this.baseUrl}/${eventId}`, { method: 'DELETE' }); if (!response.ok) throw new Error('Failed to delete event'); this.cache.clear(); eventBus.emit(EventTypes.DATA_SYNC_SUCCESS, { action: 'delete', eventId }); return true; } } catch (error) { eventBus.emit(EventTypes.DATA_SYNC_ERROR, { action: 'delete', error: error.message, eventId }); throw error; } } /** * Generate mock data for testing */ getMockData(period) { const events = []; const types = ['meeting', 'meal', 'work', 'milestone']; const titles = { meeting: ['Team Standup', 'Client Meeting', 'Project Review', 'Sprint Planning', 'Design Review'], meal: ['Breakfast', 'Lunch', 'Coffee Break', 'Dinner'], work: ['Deep Work Session', 'Code Review', 'Documentation', 'Testing'], milestone: ['Project Deadline', 'Release Day', 'Demo Day'] }; // Parse dates const startDate = new Date(period.start); const endDate = new Date(period.end); // Generate some events for each day for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { // Skip weekends for most events const dayOfWeek = d.getDay(); const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; if (isWeekend) { // Maybe one or two events on weekends if (Math.random() > 0.7) { const type = 'meal'; const title = titles[type][Math.floor(Math.random() * titles[type].length)]; const hour = 12 + Math.floor(Math.random() * 4); events.push({ id: `evt-${events.length + 1}`, title, type, start: `${this.formatDate(d)}T${hour}:00:00`, end: `${this.formatDate(d)}T${hour + 1}:00:00`, allDay: false, syncStatus: 'synced' }); } } else { // Regular workday events // Morning standup if (Math.random() > 0.3) { events.push({ id: `evt-${events.length + 1}`, title: 'Team Standup', type: 'meeting', start: `${this.formatDate(d)}T09:00:00`, end: `${this.formatDate(d)}T09:30:00`, allDay: false, syncStatus: 'synced' }); } // Lunch events.push({ id: `evt-${events.length + 1}`, title: 'Lunch', type: 'meal', start: `${this.formatDate(d)}T12:00:00`, end: `${this.formatDate(d)}T13:00:00`, allDay: false, syncStatus: 'synced' }); // Random afternoon events const numAfternoonEvents = Math.floor(Math.random() * 3) + 1; for (let i = 0; i < numAfternoonEvents; i++) { const type = types[Math.floor(Math.random() * types.length)]; const title = titles[type][Math.floor(Math.random() * titles[type].length)]; const startHour = 13 + Math.floor(Math.random() * 4); const duration = 1 + Math.floor(Math.random() * 2); events.push({ id: `evt-${events.length + 1}`, title, type, start: `${this.formatDate(d)}T${startHour}:${Math.random() > 0.5 ? '00' : '30'}:00`, end: `${this.formatDate(d)}T${startHour + duration}:00:00`, allDay: false, syncStatus: Math.random() > 0.9 ? 'pending' : 'synced' }); } } } // Add a multi-day event if (period.view === 'week') { const midWeek = new Date(startDate); midWeek.setDate(midWeek.getDate() + 2); events.push({ id: `evt-${events.length + 1}`, title: 'Project Sprint', type: 'milestone', start: `${this.formatDate(startDate)}T00:00:00`, end: `${this.formatDate(midWeek)}T23:59:59`, allDay: true, syncStatus: 'synced' }); } return { events, meta: { start: period.start, end: period.end, view: period.view, total: events.length } }; } /** * Utility methods */ formatDate(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Clear all cached data */ clearCache() { this.cache.clear(); } /** * Toggle between mock and real data */ setUseMockData(useMock) { this.useMockData = useMock; this.clearCache(); } }