Calendar/calendar-data-manager.js

385 lines
No EOL
10 KiB
JavaScript

// 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();
}
}