Implements offline-first calendar sync infrastructure
Adds IndexedDB and operation queue for robust offline synchronization Introduces SyncManager to handle background data synchronization Supports local event operations with automatic remote sync queuing Enhances application reliability and user experience in low/no connectivity scenarios
This commit is contained in:
parent
9c765b35ab
commit
e7011526e3
20 changed files with 3822 additions and 57 deletions
|
|
@ -6,11 +6,11 @@ import { IEventRepository } from '../repositories/IEventRepository';
|
|||
|
||||
/**
|
||||
* EventManager - Event lifecycle and CRUD operations
|
||||
* Handles event management and CRUD operations
|
||||
* Delegates all data operations to IEventRepository
|
||||
* No longer maintains in-memory cache - repository is single source of truth
|
||||
*/
|
||||
export class EventManager {
|
||||
|
||||
private events: ICalendarEvent[] = [];
|
||||
private dateService: DateService;
|
||||
private config: Configuration;
|
||||
private repository: IEventRepository;
|
||||
|
|
@ -28,30 +28,32 @@ export class EventManager {
|
|||
|
||||
/**
|
||||
* Load event data from repository
|
||||
* No longer caches - delegates to repository
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
try {
|
||||
this.events = await this.repository.loadEvents();
|
||||
// Just ensure repository is ready - no caching
|
||||
await this.repository.loadEvents();
|
||||
} catch (error) {
|
||||
console.error('Failed to load event data:', error);
|
||||
this.events = [];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events with optional copying for performance
|
||||
* Get all events from repository
|
||||
*/
|
||||
public getEvents(copy: boolean = false): ICalendarEvent[] {
|
||||
return copy ? [...this.events] : this.events;
|
||||
public async getEvents(copy: boolean = false): Promise<ICalendarEvent[]> {
|
||||
const events = await this.repository.loadEvents();
|
||||
return copy ? [...events] : events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized event lookup with early return
|
||||
* Get event by ID from repository
|
||||
*/
|
||||
public getEventById(id: string): ICalendarEvent | undefined {
|
||||
// Use find for better performance than filter + first
|
||||
return this.events.find(event => event.id === id);
|
||||
public async getEventById(id: string): Promise<ICalendarEvent | undefined> {
|
||||
const events = await this.repository.loadEvents();
|
||||
return events.find(event => event.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,8 +61,8 @@ export class EventManager {
|
|||
* @param id Event ID to find
|
||||
* @returns Event with navigation info or null if not found
|
||||
*/
|
||||
public getEventForNavigation(id: string): { event: ICalendarEvent; eventDate: Date } | null {
|
||||
const event = this.getEventById(id);
|
||||
public async getEventForNavigation(id: string): Promise<{ event: ICalendarEvent; eventDate: Date } | null> {
|
||||
const event = await this.getEventById(id);
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -90,8 +92,8 @@ export class EventManager {
|
|||
* @param eventId Event ID to navigate to
|
||||
* @returns true if event found and navigation initiated, false otherwise
|
||||
*/
|
||||
public navigateToEvent(eventId: string): boolean {
|
||||
const eventInfo = this.getEventForNavigation(eventId);
|
||||
public async navigateToEvent(eventId: string): Promise<boolean> {
|
||||
const eventInfo = await this.getEventForNavigation(eventId);
|
||||
if (!eventInfo) {
|
||||
console.warn(`EventManager: Event with ID ${eventId} not found`);
|
||||
return false;
|
||||
|
|
@ -113,23 +115,20 @@ export class EventManager {
|
|||
/**
|
||||
* Get events that overlap with a given time period
|
||||
*/
|
||||
public getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[] {
|
||||
public async getEventsForPeriod(startDate: Date, endDate: Date): Promise<ICalendarEvent[]> {
|
||||
const events = await this.repository.loadEvents();
|
||||
// Event overlaps period if it starts before period ends AND ends after period starts
|
||||
return this.events.filter(event => {
|
||||
return events.filter(event => {
|
||||
return event.start <= endDate && event.end >= startDate;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new event and add it to the calendar
|
||||
* Delegates to repository with source='local'
|
||||
*/
|
||||
public addEvent(event: Omit<ICalendarEvent, 'id'>): ICalendarEvent {
|
||||
const newEvent: ICalendarEvent = {
|
||||
...event,
|
||||
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
};
|
||||
|
||||
this.events.push(newEvent);
|
||||
public async addEvent(event: Omit<ICalendarEvent, 'id'>): Promise<ICalendarEvent> {
|
||||
const newEvent = await this.repository.createEvent(event, 'local');
|
||||
|
||||
this.eventBus.emit(CoreEvents.EVENT_CREATED, {
|
||||
event: newEvent
|
||||
|
|
@ -140,18 +139,59 @@ export class EventManager {
|
|||
|
||||
/**
|
||||
* Update an existing event
|
||||
* Delegates to repository with source='local'
|
||||
*/
|
||||
public updateEvent(id: string, updates: Partial<ICalendarEvent>): ICalendarEvent | null {
|
||||
const eventIndex = this.events.findIndex(event => event.id === id);
|
||||
if (eventIndex === -1) return null;
|
||||
public async updateEvent(id: string, updates: Partial<ICalendarEvent>): Promise<ICalendarEvent | null> {
|
||||
try {
|
||||
const updatedEvent = await this.repository.updateEvent(id, updates, 'local');
|
||||
|
||||
const updatedEvent = { ...this.events[eventIndex], ...updates };
|
||||
this.events[eventIndex] = updatedEvent;
|
||||
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
||||
event: updatedEvent
|
||||
});
|
||||
|
||||
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
||||
event: updatedEvent
|
||||
});
|
||||
return updatedEvent;
|
||||
} catch (error) {
|
||||
console.error(`Failed to update event ${id}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return updatedEvent;
|
||||
/**
|
||||
* Delete an event
|
||||
* Delegates to repository with source='local'
|
||||
*/
|
||||
public async deleteEvent(id: string): Promise<boolean> {
|
||||
try {
|
||||
await this.repository.deleteEvent(id, 'local');
|
||||
|
||||
this.eventBus.emit(CoreEvents.EVENT_DELETED, {
|
||||
eventId: id
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete event ${id}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle remote update from SignalR
|
||||
* Delegates to repository with source='remote'
|
||||
*/
|
||||
public async handleRemoteUpdate(event: ICalendarEvent): Promise<void> {
|
||||
try {
|
||||
await this.repository.updateEvent(event.id, event, 'remote');
|
||||
|
||||
this.eventBus.emit(CoreEvents.REMOTE_UPDATE_RECEIVED, {
|
||||
event
|
||||
});
|
||||
|
||||
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
||||
event
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to handle remote update for event ${event.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue