diff --git a/src/core/CalendarConfig.ts b/src/core/CalendarConfig.ts index fd07177..030c5e1 100644 --- a/src/core/CalendarConfig.ts +++ b/src/core/CalendarConfig.ts @@ -1,8 +1,7 @@ // Calendar configuration management +// Pure static configuration class - no dependencies, no events -import { eventBus } from './EventBus'; -import { CoreEvents } from '../constants/CoreEvents'; -import { CalendarConfig as ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes'; +import { ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes'; import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter'; /** @@ -86,376 +85,281 @@ interface TimeFormatConfig { } /** - * Calendar configuration management + * Calendar configuration management - Pure static config */ export class CalendarConfig { - private config: ICalendarConfig; - private calendarMode: CalendarMode = 'date'; - private selectedDate: Date | null = null; - private gridSettings: GridSettings; - private dateViewSettings: DateViewSettings; - private resourceViewSettings: ResourceViewSettings; - private currentWorkWeek: string = 'standard'; - private timeFormatConfig: TimeFormatConfig; + private static config: ICalendarConfig = { + // Scrollbar styling + scrollbarWidth: 16, // Width of scrollbar in pixels + scrollbarColor: '#666', // Scrollbar thumb color + scrollbarTrackColor: '#f0f0f0', // Scrollbar track color + scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color + scrollbarBorderRadius: 6, // Border radius for scrollbar thumb - constructor() { - this.config = { - // Scrollbar styling - scrollbarWidth: 16, // Width of scrollbar in pixels - scrollbarColor: '#666', // Scrollbar thumb color - scrollbarTrackColor: '#f0f0f0', // Scrollbar track color - scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color - scrollbarBorderRadius: 6, // Border radius for scrollbar thumb - - // Interaction settings - allowDrag: true, - allowResize: true, - allowCreate: true, - - // API settings - apiEndpoint: '/api/events', - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm', - - // Feature flags - enableSearch: true, - enableTouch: true, - - // Event defaults - defaultEventDuration: 60, // Minutes - minEventDuration: 15, // Will be same as snapInterval - maxEventDuration: 480 // 8 hours - }; + // Interaction settings + allowDrag: true, + allowResize: true, + allowCreate: true, - // Grid display settings - this.gridSettings = { - hourHeight: 60, - dayStartHour: 0, - dayEndHour: 24, - workStartHour: 8, - workEndHour: 17, - snapInterval: 15, - gridStartThresholdMinutes: 30, // Events starting within ±15 min share grid columns - showCurrentTime: true, - showWorkHours: true, - fitToWidth: false, - scrollToHour: 8 - }; + // API settings + apiEndpoint: '/api/events', + dateFormat: 'YYYY-MM-DD', + timeFormat: 'HH:mm', - // Date view settings - this.dateViewSettings = { - period: 'week', - weekDays: 7, - firstDayOfWeek: 1, - showAllDay: true - }; + // Feature flags + enableSearch: true, + enableTouch: true, - // Resource view settings - this.resourceViewSettings = { - maxResources: 10, - showAvatars: true, - avatarSize: 32, - resourceNameFormat: 'full', - showResourceDetails: true, - showAllDay: true - }; + // Event defaults + defaultEventDuration: 60, // Minutes + minEventDuration: 15, // Will be same as snapInterval + maxEventDuration: 480 // 8 hours + }; - // Time format settings - default to Denmark with technical format - this.timeFormatConfig = { - timezone: 'Europe/Copenhagen', - use24HourFormat: true, - locale: 'da-DK', - dateFormat: 'technical', - showSeconds: false - }; + private static calendarMode: CalendarMode = 'date'; + private static selectedDate: Date | null = new Date(); + private static currentWorkWeek: string = 'standard'; - // Set computed values - this.config.minEventDuration = this.gridSettings.snapInterval; - - // Initialize TimeFormatter with default settings - TimeFormatter.configure(this.timeFormatConfig); - - // Load calendar type from URL parameter - this.loadCalendarType(); - - // Load from data attributes - this.loadFromDOM(); - } + // Grid display settings + private static gridSettings: GridSettings = { + hourHeight: 60, + dayStartHour: 0, + dayEndHour: 24, + workStartHour: 8, + workEndHour: 17, + snapInterval: 15, + gridStartThresholdMinutes: 30, // Events starting within ±15 min share grid columns + showCurrentTime: true, + showWorkHours: true, + fitToWidth: false, + scrollToHour: 8 + }; + + // Date view settings + private static dateViewSettings: DateViewSettings = { + period: 'week', + weekDays: 7, + firstDayOfWeek: 1, + showAllDay: true + }; + + // Resource view settings + private static resourceViewSettings: ResourceViewSettings = { + maxResources: 10, + showAvatars: true, + avatarSize: 32, + resourceNameFormat: 'full', + showResourceDetails: true, + showAllDay: true + }; + + // Time format settings - default to Denmark with technical format + private static timeFormatConfig: TimeFormatConfig = { + timezone: 'Europe/Copenhagen', + use24HourFormat: true, + locale: 'da-DK', + dateFormat: 'technical', + showSeconds: false + }; /** - * Load calendar type and date from URL parameters + * Initialize configuration - called once at startup */ - private loadCalendarType(): void { - const urlParams = new URLSearchParams(window.location.search); - const typeParam = urlParams.get('type'); - const dateParam = urlParams.get('date'); - - // Set calendar mode - if (typeParam === 'resource' || typeParam === 'date') { - this.calendarMode = typeParam; - } else { - this.calendarMode = 'date'; // Default - } - - // Set selected date - if (dateParam) { - const parsedDate = new Date(dateParam); - if (!isNaN(parsedDate.getTime())) { - this.selectedDate = parsedDate; - } else { - this.selectedDate = new Date(); - } - } else { - this.selectedDate = new Date(); // Default to today - } + static initialize(): void { + // Set computed values + CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval; + + // Initialize TimeFormatter with default settings + TimeFormatter.configure(CalendarConfig.timeFormatConfig); + + // Load from data attributes + CalendarConfig.loadFromDOM(); } + /** * Load configuration from DOM data attributes */ - private loadFromDOM(): void { + private static loadFromDOM(): void { const calendar = document.querySelector('swp-calendar') as HTMLElement; if (!calendar) return; // Read data attributes const attrs = calendar.dataset; - + // Update date view settings - if (attrs.view) this.dateViewSettings.period = attrs.view as ViewPeriod; - if (attrs.weekDays) this.dateViewSettings.weekDays = parseInt(attrs.weekDays); - + if (attrs.view) CalendarConfig.dateViewSettings.period = attrs.view as ViewPeriod; + if (attrs.weekDays) CalendarConfig.dateViewSettings.weekDays = parseInt(attrs.weekDays); + // Update grid settings - if (attrs.snapInterval) this.gridSettings.snapInterval = parseInt(attrs.snapInterval); - if (attrs.dayStartHour) this.gridSettings.dayStartHour = parseInt(attrs.dayStartHour); - if (attrs.dayEndHour) this.gridSettings.dayEndHour = parseInt(attrs.dayEndHour); - if (attrs.hourHeight) this.gridSettings.hourHeight = parseInt(attrs.hourHeight); - if (attrs.fitToWidth !== undefined) this.gridSettings.fitToWidth = attrs.fitToWidth === 'true'; - + if (attrs.snapInterval) CalendarConfig.gridSettings.snapInterval = parseInt(attrs.snapInterval); + if (attrs.dayStartHour) CalendarConfig.gridSettings.dayStartHour = parseInt(attrs.dayStartHour); + if (attrs.dayEndHour) CalendarConfig.gridSettings.dayEndHour = parseInt(attrs.dayEndHour); + if (attrs.hourHeight) CalendarConfig.gridSettings.hourHeight = parseInt(attrs.hourHeight); + if (attrs.fitToWidth !== undefined) CalendarConfig.gridSettings.fitToWidth = attrs.fitToWidth === 'true'; + // Update computed values - this.config.minEventDuration = this.gridSettings.snapInterval; + CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval; } /** * Get a config value */ - get(key: K): ICalendarConfig[K] { - return this.config[key]; + static get(key: K): ICalendarConfig[K] { + return CalendarConfig.config[key]; } /** - * Set a config value + * Set a config value (no events - use ConfigManager for updates with events) */ - set(key: K, value: ICalendarConfig[K]): void { - const oldValue = this.config[key]; - this.config[key] = value; - - // Update computed values handled in specific update methods - - // Emit config update event - eventBus.emit(CoreEvents.REFRESH_REQUESTED, { - key, - value, - oldValue - }); + static set(key: K, value: ICalendarConfig[K]): void { + CalendarConfig.config[key] = value; } /** - * Update multiple config values + * Update multiple config values (no events - use ConfigManager for updates with events) */ - update(updates: Partial): void { + static update(updates: Partial): void { Object.entries(updates).forEach(([key, value]) => { - this.set(key as keyof ICalendarConfig, value); + CalendarConfig.set(key as keyof ICalendarConfig, value); }); } /** * Get all config */ - getAll(): ICalendarConfig { - return { ...this.config }; + static getAll(): ICalendarConfig { + return { ...CalendarConfig.config }; } /** * Calculate derived values */ - - get minuteHeight(): number { - return this.gridSettings.hourHeight / 60; + + static get minuteHeight(): number { + return CalendarConfig.gridSettings.hourHeight / 60; } - get totalHours(): number { - return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour; + static get totalHours(): number { + return CalendarConfig.gridSettings.dayEndHour - CalendarConfig.gridSettings.dayStartHour; } - get totalMinutes(): number { - return this.totalHours * 60; + static get totalMinutes(): number { + return CalendarConfig.totalHours * 60; } - get slotsPerHour(): number { - return 60 / this.gridSettings.snapInterval; + static get slotsPerHour(): number { + return 60 / CalendarConfig.gridSettings.snapInterval; } - get totalSlots(): number { - return this.totalHours * this.slotsPerHour; + static get totalSlots(): number { + return CalendarConfig.totalHours * CalendarConfig.slotsPerHour; } - get slotHeight(): number { - return this.gridSettings.hourHeight / this.slotsPerHour; + static get slotHeight(): number { + return CalendarConfig.gridSettings.hourHeight / CalendarConfig.slotsPerHour; } /** * Validate snap interval */ - isValidSnapInterval(interval: number): boolean { + static isValidSnapInterval(interval: number): boolean { return [5, 10, 15, 30, 60].includes(interval); } /** * Get grid display settings */ - getGridSettings(): GridSettings { - return { ...this.gridSettings }; + static getGridSettings(): GridSettings { + return { ...CalendarConfig.gridSettings }; } /** - * Update grid display settings + * Update grid display settings (no events - use ConfigManager for updates with events) */ - updateGridSettings(updates: Partial): void { - this.gridSettings = { ...this.gridSettings, ...updates }; - + static updateGridSettings(updates: Partial): void { + CalendarConfig.gridSettings = { ...CalendarConfig.gridSettings, ...updates }; + // Update computed values if (updates.snapInterval) { - this.config.minEventDuration = updates.snapInterval; + CalendarConfig.config.minEventDuration = updates.snapInterval; } - - // Grid settings changes trigger general refresh - avoid specific event - eventBus.emit(CoreEvents.REFRESH_REQUESTED, { - key: 'gridSettings', - value: this.gridSettings - }); } /** * Get date view settings */ - getDateViewSettings(): DateViewSettings { - return { ...this.dateViewSettings }; - } - - /** - * Update date view settings - */ - updateDateViewSettings(updates: Partial): void { - this.dateViewSettings = { ...this.dateViewSettings, ...updates }; - - // Date view settings changes trigger general refresh - avoid specific event - eventBus.emit(CoreEvents.REFRESH_REQUESTED, { - key: 'dateViewSettings', - value: this.dateViewSettings - }); + static getDateViewSettings(): DateViewSettings { + return { ...CalendarConfig.dateViewSettings }; } /** * Get resource view settings */ - getResourceViewSettings(): ResourceViewSettings { - return { ...this.resourceViewSettings }; + static getResourceViewSettings(): ResourceViewSettings { + return { ...CalendarConfig.resourceViewSettings }; } - /** - * Update resource view settings - */ - updateResourceViewSettings(updates: Partial): void { - this.resourceViewSettings = { ...this.resourceViewSettings, ...updates }; - - // Resource view settings changes trigger general refresh - avoid specific event - eventBus.emit(CoreEvents.REFRESH_REQUESTED, { - key: 'resourceViewSettings', - value: this.resourceViewSettings - }); - } - - /** - * Check if current mode is resource-based - */ - isResourceMode(): boolean { - return this.calendarMode === 'resource'; - } - - /** - * Check if current mode is date-based - */ - isDateMode(): boolean { - return this.calendarMode === 'date'; - } - /** * Get calendar mode */ - getCalendarMode(): CalendarMode { - return this.calendarMode; + static getCalendarMode(): CalendarMode { + return CalendarConfig.calendarMode; } /** * Set calendar mode */ - setCalendarMode(mode: CalendarMode): void { - const oldMode = this.calendarMode; - this.calendarMode = mode; - - // Emit calendar mode change event - eventBus.emit(CoreEvents.VIEW_CHANGED, { - oldType: oldMode, - newType: mode - }); + static setCalendarMode(mode: CalendarMode): void { + CalendarConfig.calendarMode = mode; } /** * Get selected date */ - getSelectedDate(): Date | null { - return this.selectedDate; + static getSelectedDate(): Date | null { + return CalendarConfig.selectedDate; } /** * Set selected date * Note: Does not emit events - caller is responsible for event emission */ - setSelectedDate(date: Date): void { - this.selectedDate = date; + static setSelectedDate(date: Date): void { + CalendarConfig.selectedDate = date; } /** * Get work week presets */ - private getWorkWeekPresets(): { [key: string]: WorkWeekSettings } { + private static getWorkWeekPresets(): { [key: string]: WorkWeekSettings } { return { - 'standard': { + 'standard': { id: 'standard', workDays: [1,2,3,4,5], // Monday-Friday (ISO) totalDays: 5, firstWorkDay: 1 }, - 'compressed': { + 'compressed': { id: 'compressed', workDays: [1,2,3,4], // Monday-Thursday (ISO) totalDays: 4, firstWorkDay: 1 }, - 'midweek': { + 'midweek': { id: 'midweek', workDays: [3,4,5], // Wednesday-Friday (ISO) totalDays: 3, firstWorkDay: 3 }, - 'weekend': { + 'weekend': { id: 'weekend', workDays: [6,7], // Saturday-Sunday (ISO) totalDays: 2, firstWorkDay: 6 }, - 'fullweek': { + 'fullweek': { id: 'fullweek', workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO) totalDays: 7, @@ -467,109 +371,115 @@ export class CalendarConfig { /** * Get current work week settings */ - getWorkWeekSettings(): WorkWeekSettings { - const presets = this.getWorkWeekPresets(); - return presets[this.currentWorkWeek] || presets['standard']; + static getWorkWeekSettings(): WorkWeekSettings { + const presets = CalendarConfig.getWorkWeekPresets(); + return presets[CalendarConfig.currentWorkWeek] || presets['standard']; } /** * Set work week preset * Note: Does not emit events - caller is responsible for event emission */ - setWorkWeek(workWeekId: string): void { - const presets = this.getWorkWeekPresets(); + static setWorkWeek(workWeekId: string): void { + const presets = CalendarConfig.getWorkWeekPresets(); if (presets[workWeekId]) { - this.currentWorkWeek = workWeekId; + CalendarConfig.currentWorkWeek = workWeekId; // Update dateViewSettings to match work week - this.dateViewSettings.weekDays = presets[workWeekId].totalDays; + CalendarConfig.dateViewSettings.weekDays = presets[workWeekId].totalDays; } } /** * Get current work week ID */ - getCurrentWorkWeek(): string { - return this.currentWorkWeek; + static getCurrentWorkWeek(): string { + return CalendarConfig.currentWorkWeek; } /** * Get time format settings */ - getTimeFormatSettings(): TimeFormatConfig { - return { ...this.timeFormatConfig }; - } - - /** - * Update time format settings - */ - updateTimeFormatSettings(updates: Partial): void { - this.timeFormatConfig = { ...this.timeFormatConfig, ...updates }; - - // Update TimeFormatter with new settings - TimeFormatter.configure(this.timeFormatConfig); - - // Emit time format change event - eventBus.emit(CoreEvents.REFRESH_REQUESTED, { - key: 'timeFormatSettings', - value: this.timeFormatConfig - }); - } - - /** - * Set timezone (convenience method) - */ - setTimezone(timezone: string): void { - this.updateTimeFormatSettings({ timezone }); - } - - /** - * Set 12/24 hour format (convenience method) - */ - set24HourFormat(use24Hour: boolean): void { - this.updateTimeFormatSettings({ use24HourFormat: use24Hour }); + static getTimeFormatSettings(): TimeFormatConfig { + return { ...CalendarConfig.timeFormatConfig }; } /** * Get configured timezone */ - getTimezone(): string { - return this.timeFormatConfig.timezone; + static getTimezone(): string { + return CalendarConfig.timeFormatConfig.timezone; } /** * Get configured locale */ - getLocale(): string { - return this.timeFormatConfig.locale; + static getLocale(): string { + return CalendarConfig.timeFormatConfig.locale; } /** * Check if using 24-hour format */ - is24HourFormat(): boolean { - return this.timeFormatConfig.use24HourFormat; - } - - /** - * Set date format (convenience method) - */ - setDateFormat(format: 'locale' | 'technical'): void { - this.updateTimeFormatSettings({ dateFormat: format }); - } - - /** - * Set whether to show seconds (convenience method) - */ - setShowSeconds(show: boolean): void { - this.updateTimeFormatSettings({ showSeconds: show }); + static is24HourFormat(): boolean { + return CalendarConfig.timeFormatConfig.use24HourFormat; } /** * Get current date format */ - getDateFormat(): 'locale' | 'technical' { - return this.timeFormatConfig.dateFormat; + static getDateFormat(): 'locale' | 'technical' { + return CalendarConfig.timeFormatConfig.dateFormat; } + /** + * Load configuration from JSON + */ + static loadFromJSON(json: string): void { + try { + const data = JSON.parse(json); + if (data.gridSettings) CalendarConfig.updateGridSettings(data.gridSettings); + if (data.dateViewSettings) CalendarConfig.dateViewSettings = { ...CalendarConfig.dateViewSettings, ...data.dateViewSettings }; + if (data.resourceViewSettings) CalendarConfig.resourceViewSettings = { ...CalendarConfig.resourceViewSettings, ...data.resourceViewSettings }; + if (data.timeFormatConfig) { + CalendarConfig.timeFormatConfig = { ...CalendarConfig.timeFormatConfig, ...data.timeFormatConfig }; + TimeFormatter.configure(CalendarConfig.timeFormatConfig); + } + } catch (error) { + console.error('Failed to load config from JSON:', error); + } + } + + // ======================================================================== + // Instance method wrappers for backward compatibility + // These allow injected CalendarConfig to work with existing code + // ======================================================================== + + get(key: keyof ICalendarConfig) { return CalendarConfig.get(key); } + set(key: keyof ICalendarConfig, value: any) { return CalendarConfig.set(key, value); } + update(updates: Partial) { return CalendarConfig.update(updates); } + getAll() { return CalendarConfig.getAll(); } + get minuteHeight() { return CalendarConfig.minuteHeight; } + get totalHours() { return CalendarConfig.totalHours; } + get totalMinutes() { return CalendarConfig.totalMinutes; } + get slotsPerHour() { return CalendarConfig.slotsPerHour; } + get totalSlots() { return CalendarConfig.totalSlots; } + get slotHeight() { return CalendarConfig.slotHeight; } + isValidSnapInterval(interval: number) { return CalendarConfig.isValidSnapInterval(interval); } + getGridSettings() { return CalendarConfig.getGridSettings(); } + updateGridSettings(updates: Partial) { return CalendarConfig.updateGridSettings(updates); } + getDateViewSettings() { return CalendarConfig.getDateViewSettings(); } + getResourceViewSettings() { return CalendarConfig.getResourceViewSettings(); } + getCalendarMode() { return CalendarConfig.getCalendarMode(); } + setCalendarMode(mode: CalendarMode) { return CalendarConfig.setCalendarMode(mode); } + getSelectedDate() { return CalendarConfig.getSelectedDate(); } + setSelectedDate(date: Date) { return CalendarConfig.setSelectedDate(date); } + getWorkWeekSettings() { return CalendarConfig.getWorkWeekSettings(); } + setWorkWeek(workWeekId: string) { return CalendarConfig.setWorkWeek(workWeekId); } + getCurrentWorkWeek() { return CalendarConfig.getCurrentWorkWeek(); } + getTimeFormatSettings() { return CalendarConfig.getTimeFormatSettings(); } + getTimezone() { return CalendarConfig.getTimezone(); } + getLocale() { return CalendarConfig.getLocale(); } + is24HourFormat() { return CalendarConfig.is24HourFormat(); } + getDateFormat() { return CalendarConfig.getDateFormat(); } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a02be2d..69b1868 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,10 +19,11 @@ import { ResizeHandleManager } from './managers/ResizeHandleManager'; import { EdgeScrollManager } from './managers/EdgeScrollManager'; import { DragHoverManager } from './managers/DragHoverManager'; import { HeaderManager } from './managers/HeaderManager'; +import { ConfigManager } from './managers/ConfigManager'; // Import renderers -import { DateHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer'; -import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer'; +import { DateHeaderRenderer, ResourceHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer'; +import { DateColumnRenderer, ResourceColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer'; import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer'; import { AllDayEventRenderer } from './renderers/AllDayEventRenderer'; import { GridRenderer } from './renderers/GridRenderer'; @@ -66,7 +67,8 @@ async function handleDeepLinking(eventManager: EventManager, urlManager: URLMana */ async function initializeCalendar(): Promise { try { - // Use the singleton calendar configuration + // Initialize static calendar configuration + CalendarConfig.initialize(); // Create NovaDI container const container = new Container(); @@ -75,20 +77,25 @@ async function initializeCalendar(): Promise { // Enable debug mode for development eventBus.setDebug(true); - builder.registerType(CalendarConfig).as().singleInstance(); + // Register CalendarConfig as singleton instance (static class, not instantiated) + builder.registerInstance(CalendarConfig).as(); + + // Register ConfigManager for event-driven config updates + builder.registerType(ConfigManager).as().singleInstance(); + // Bind core services as instances builder.registerInstance(eventBus).as(); - // Register renderers with keyed registration based on calendar mode - // Date mode renderers - builder.registerType(DateHeaderRenderer).as().keyed('date'); - builder.registerType(DateColumnRenderer).as().keyed('date'); - builder.registerType(DateEventRenderer).as().keyed('date'); - - // Resource mode renderers (using same renderers for now) - builder.registerType(DateHeaderRenderer).as().keyed('resource'); - builder.registerType(DateColumnRenderer).as().keyed('resource'); - builder.registerType(DateEventRenderer).as().keyed('resource'); + // Register renderers based on calendar mode + const calendarMode = CalendarConfig.getCalendarMode(); + if (calendarMode === 'resource') { + builder.registerType(ResourceHeaderRenderer).as().singleInstance(); + builder.registerType(ResourceColumnRenderer).as().singleInstance(); + } else { + builder.registerType(DateHeaderRenderer).as().singleInstance(); + builder.registerType(DateColumnRenderer).as().singleInstance(); + } + builder.registerType(DateEventRenderer).as().singleInstance(); // Register core services and utilities builder.registerType(DateService).as().singleInstance(); @@ -167,9 +174,11 @@ async function initializeCalendar(): Promise { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { initializeCalendar().catch(error => { + console.error('Calendar initialization failed:', error); }); }); } else { initializeCalendar().catch(error => { + console.error('Calendar initialization failed:', error); }); } \ No newline at end of file diff --git a/src/managers/ConfigManager.ts b/src/managers/ConfigManager.ts new file mode 100644 index 0000000..f9920d2 --- /dev/null +++ b/src/managers/ConfigManager.ts @@ -0,0 +1,118 @@ +// Configuration manager - handles config updates with event emission +// Uses static CalendarConfig internally but adds event-driven updates + +import { IEventBus, ICalendarConfig } from '../types/CalendarTypes'; +import { CoreEvents } from '../constants/CoreEvents'; +import { CalendarConfig } from '../core/CalendarConfig'; + +/** + * Grid display settings interface (re-export from CalendarConfig) + */ +interface GridSettings { + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; + fitToWidth: boolean; + scrollToHour: number | null; + gridStartThresholdMinutes: number; + showCurrentTime: boolean; + showWorkHours: boolean; +} + +/** + * ConfigManager - Handles configuration updates with event emission + * Wraps static CalendarConfig with event-driven functionality for DI system + */ +export class ConfigManager { + constructor(private eventBus: IEventBus) {} + + /** + * Set a config value and emit event + */ + set(key: K, value: ICalendarConfig[K]): void { + const oldValue = CalendarConfig.get(key); + CalendarConfig.set(key, value); + + // Emit config update event + this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key, + value, + oldValue + }); + } + + /** + * Update multiple config values and emit event + */ + update(updates: Partial): void { + Object.entries(updates).forEach(([key, value]) => { + this.set(key as keyof ICalendarConfig, value); + }); + } + + /** + * Update grid display settings and emit event + */ + updateGridSettings(updates: Partial): void { + CalendarConfig.updateGridSettings(updates); + + // Emit event after update + this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'gridSettings', + value: CalendarConfig.getGridSettings() + }); + } + + /** + * Set selected date and emit event + */ + setSelectedDate(date: Date): void { + const oldDate = CalendarConfig.getSelectedDate(); + CalendarConfig.setSelectedDate(date); + + // Emit date change event if it actually changed + if (!oldDate || oldDate.getTime() !== date.getTime()) { + this.eventBus.emit(CoreEvents.DATE_CHANGED, { + date, + oldDate + }); + } + } + + /** + * Set work week and emit event + */ + setWorkWeek(workWeekId: string): void { + const oldWorkWeek = CalendarConfig.getCurrentWorkWeek(); + CalendarConfig.setWorkWeek(workWeekId); + + // Emit event if changed + if (oldWorkWeek !== workWeekId) { + this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'workWeek', + value: workWeekId, + oldValue: oldWorkWeek + }); + } + } + + /** + * Set calendar mode and emit event + */ + setCalendarMode(mode: 'date' | 'resource'): void { + const oldMode = CalendarConfig.getCalendarMode(); + CalendarConfig.setCalendarMode(mode); + + // Emit event if changed + if (oldMode !== mode) { + this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'calendarMode', + value: mode, + oldValue: oldMode + }); + } + } +} diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 66647ee..640cb43 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -57,8 +57,8 @@ export class EventManager { private async loadMockData(): Promise { const calendarType = this.config.getCalendarMode(); const jsonFile = calendarType === 'resource' - ? '/src/data/mock-resource-events.json' - : '/src/data/mock-events.json'; + ? '/data/mock-resource-events.json' + : '/data/mock-events.json'; const response = await fetch(jsonFile); if (!response.ok) { diff --git a/src/types/CalendarTypes.ts b/src/types/CalendarTypes.ts index b6166ea..2aeb070 100644 --- a/src/types/CalendarTypes.ts +++ b/src/types/CalendarTypes.ts @@ -50,7 +50,7 @@ export interface CalendarEvent { metadata?: Record; } -export interface CalendarConfig { +export interface ICalendarConfig { // Scrollbar styling scrollbarWidth: number; // Width of scrollbar in pixels scrollbarColor: string; // Scrollbar thumb color diff --git a/wwwroot/data/mock-events.json b/wwwroot/data/mock-events.json new file mode 100644 index 0000000..a04b946 --- /dev/null +++ b/wwwroot/data/mock-events.json @@ -0,0 +1,2809 @@ +[ + { + "id": "1", + "title": "Team Standup", + "start": "2025-07-07T05:00:00Z", + "end": "2025-07-07T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "2", + "title": "Sprint Planning", + "start": "2025-07-07T06:00:00Z", + "end": "2025-07-07T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "3", + "title": "Development Session", + "start": "2025-07-07T10:00:00Z", + "end": "2025-07-07T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "4", + "title": "Team Standup", + "start": "2025-07-08T05:00:00Z", + "end": "2025-07-08T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "5", + "title": "Client Review", + "start": "2025-07-08T11:00:00Z", + "end": "2025-07-08T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "6", + "title": "Team Standup", + "start": "2025-07-09T05:00:00Z", + "end": "2025-07-09T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "7", + "title": "Deep Work Session", + "start": "2025-07-09T06:00:00Z", + "end": "2025-07-09T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#3f51b5" + } + }, + { + "id": "8", + "title": "Architecture Review", + "start": "2025-07-09T10:00:00Z", + "end": "2025-07-09T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#009688" + } + }, + { + "id": "9", + "title": "Team Standup", + "start": "2025-07-10T05:00:00Z", + "end": "2025-07-10T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "10", + "title": "Lunch & Learn", + "start": "2025-07-10T08:00:00Z", + "end": "2025-07-10T09:00:00Z", + "type": "meal", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ff9800" + } + }, + { + "id": "11", + "title": "Team Standup", + "start": "2025-07-11T05:00:00Z", + "end": "2025-07-11T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "12", + "title": "Sprint Review", + "start": "2025-07-11T10:00:00Z", + "end": "2025-07-11T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "13", + "title": "Weekend Project", + "start": "2025-07-12T06:00:00Z", + "end": "2025-07-12T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#f44336" + } + }, + { + "id": "14", + "title": "Team Standup", + "start": "2025-07-14T05:00:00Z", + "end": "2025-07-14T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "15", + "title": "Code Reviews", + "start": "2025-07-14T14:00:00Z", + "end": "2025-07-14T23:59:59Z", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#009688" + } + }, + { + "id": "16", + "title": "Team Standup", + "start": "2025-07-15T05:00:00Z", + "end": "2025-07-15T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "17", + "title": "Product Demo", + "start": "2025-07-15T11:00:00Z", + "end": "2025-07-15T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#e91e63" + } + }, + { + "id": "18", + "title": "Team Standup", + "start": "2025-07-16T05:00:00Z", + "end": "2025-07-16T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "19", + "title": "Workshop: New Technologies", + "start": "2025-07-16T10:00:00Z", + "end": "2025-07-16T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#9c27b0" + } + }, + { + "id": "20", + "title": "Team Standup", + "start": "2025-07-17T05:00:00Z", + "end": "2025-07-17T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "21", + "title": "Deadline: Feature Release", + "start": "2025-07-17T13:00:00Z", + "end": "2025-07-17T13:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 0, + "color": "#f44336" + } + }, + { + "id": "22", + "title": "Team Standup", + "start": "2025-07-18T05:00:00Z", + "end": "2025-07-18T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "23", + "title": "Summer Team Event", + "start": "2025-07-18T00:00:00Z", + "end": "2025-07-17T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 1440, + "color": "#4caf50" + } + }, + { + "id": "24", + "title": "Team Standup", + "start": "2025-07-21T05:00:00Z", + "end": "2025-07-21T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "25", + "title": "Sprint Planning", + "start": "2025-07-21T06:00:00Z", + "end": "2025-07-21T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "26", + "title": "Team Standup", + "start": "2025-07-22T05:00:00Z", + "end": "2025-07-22T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "27", + "title": "Client Meeting", + "start": "2025-07-22T10:00:00Z", + "end": "2025-07-22T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#cddc39" + } + }, + { + "id": "28", + "title": "Team Standup", + "start": "2025-07-23T05:00:00Z", + "end": "2025-07-23T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "29", + "title": "Performance Review", + "start": "2025-07-23T07:00:00Z", + "end": "2025-07-23T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "30", + "title": "Team Standup", + "start": "2025-07-24T05:00:00Z", + "end": "2025-07-24T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "31", + "title": "Technical Discussion", + "start": "2025-07-24T11:00:00Z", + "end": "2025-07-24T12:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#3f51b5" + } + }, + { + "id": "32", + "title": "Team Standup", + "start": "2025-07-25T05:00:00Z", + "end": "2025-07-25T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "33", + "title": "Sprint Review", + "start": "2025-07-25T10:00:00Z", + "end": "2025-07-25T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "34", + "title": "Team Standup", + "start": "2025-07-28T05:00:00Z", + "end": "2025-07-28T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "35", + "title": "Monthly Planning", + "start": "2025-07-28T06:00:00Z", + "end": "2025-07-28T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#9c27b0" + } + }, + { + "id": "36", + "title": "Team Standup", + "start": "2025-07-29T05:00:00Z", + "end": "2025-07-29T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "37", + "title": "Development Work", + "start": "2025-07-29T10:00:00Z", + "end": "2025-07-29T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "38", + "title": "Team Standup", + "start": "2025-07-30T05:00:00Z", + "end": "2025-07-30T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "39", + "title": "Security Review", + "start": "2025-07-30T11:00:00Z", + "end": "2025-07-30T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#f44336" + } + }, + { + "id": "40", + "title": "Team Standup", + "start": "2025-07-31T05:00:00Z", + "end": "2025-07-31T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "41", + "title": "Month End Review", + "start": "2025-07-31T10:00:00Z", + "end": "2025-07-31T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#795548" + } + }, + { + "id": "42", + "title": "Team Standup", + "start": "2025-08-01T05:00:00Z", + "end": "2025-08-01T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "43", + "title": "August Kickoff", + "start": "2025-08-01T06:00:00Z", + "end": "2025-08-01T07:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#4caf50" + } + }, + { + "id": "44", + "title": "Weekend Planning", + "start": "2025-08-03T06:00:00Z", + "end": "2025-08-03T07:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#9c27b0" + } + }, + { + "id": "45", + "title": "Team Standup", + "start": "2025-08-04T05:00:00Z", + "end": "2025-08-04T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "46", + "title": "Project Kickoff", + "start": "2025-08-04T10:00:00Z", + "end": "2025-08-04T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#e91e63" + } + }, + { + "id": "47", + "title": "Company Holiday", + "start": "2025-08-04T00:00:00Z", + "end": "2025-08-04T23:59:59Z", + "type": "milestone", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 1440, + "color": "#4caf50" + } + }, + { + "id": "48", + "title": "Deep Work Session", + "start": "2025-08-05T06:00:00Z", + "end": "2025-08-05T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#3f51b5" + } + }, + { + "id": "49", + "title": "Lunch Meeting", + "start": "2025-08-05T08:30:00Z", + "end": "2025-08-05T09:30:00Z", + "type": "meal", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ff9800" + } + }, + { + "id": "50", + "title": "Early Morning Workout", + "start": "2025-08-05T02:00:00Z", + "end": "2025-08-05T03:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#00bcd4" + } + }, + { + "id": "51", + "title": "Client Review", + "start": "2025-08-06T11:00:00Z", + "end": "2025-08-06T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "52", + "title": "Late Evening Call", + "start": "2025-08-06T17:00:00Z", + "end": "2025-08-06T18:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#673ab7" + } + }, + { + "id": "53", + "title": "Team Building Event", + "start": "2025-08-06T00:00:00Z", + "end": "2025-08-05T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 1440, + "color": "#2196f3" + } + }, + { + "id": "54", + "title": "Sprint Planning", + "start": "2025-08-07T05:00:00Z", + "end": "2025-08-07T06:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#607d8b" + } + }, + { + "id": "55", + "title": "Code Review", + "start": "2025-08-07T10:00:00Z", + "end": "2025-08-07T11:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#009688" + } + }, + { + "id": "56", + "title": "Midnight Deployment", + "start": "2025-08-07T19:00:00Z", + "end": "2025-08-07T21:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#ffc107" + } + }, + { + "id": "57", + "title": "Team Standup", + "start": "2025-08-08T05:00:00Z", + "end": "2025-08-08T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#8bc34a" + } + }, + { + "id": "58", + "title": "Client Meeting", + "start": "2025-08-08T10:00:00Z", + "end": "2025-08-08T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#cddc39" + } + }, + { + "id": "59", + "title": "Weekend Project", + "start": "2025-08-09T06:00:00Z", + "end": "2025-08-09T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#f44336" + } + }, + { + "id": "60", + "title": "Team Standup", + "start": "2025-08-11T05:00:00Z", + "end": "2025-08-11T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "61", + "title": "Sprint Planning", + "start": "2025-08-11T06:00:00Z", + "end": "2025-08-11T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "62", + "title": "Team Standup", + "start": "2025-08-12T05:00:00Z", + "end": "2025-08-12T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "63", + "title": "Technical Workshop", + "start": "2025-08-12T10:00:00Z", + "end": "2025-08-12T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#9c27b0" + } + }, + { + "id": "64", + "title": "Team Standup", + "start": "2025-08-13T05:00:00Z", + "end": "2025-08-13T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "65", + "title": "Development Session", + "start": "2025-08-13T06:00:00Z", + "end": "2025-08-13T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#2196f3" + } + }, + { + "id": "66", + "title": "Team Standup", + "start": "2025-08-14T05:00:00Z", + "end": "2025-08-14T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "67", + "title": "Client Presentation", + "start": "2025-08-14T11:00:00Z", + "end": "2025-08-14T12:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#e91e63" + } + }, + { + "id": "68", + "title": "Team Standup", + "start": "2025-08-15T05:00:00Z", + "end": "2025-08-15T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "69", + "title": "Sprint Review", + "start": "2025-08-15T10:00:00Z", + "end": "2025-08-15T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "70", + "title": "Summer Festival", + "start": "2025-08-14T00:00:00Z", + "end": "2025-08-15T23:59:59Z", + "type": "milestone", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 2880, + "color": "#4caf50" + } + }, + { + "id": "71", + "title": "Team Standup", + "start": "2025-08-18T05:00:00Z", + "end": "2025-08-18T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "72", + "title": "Strategy Meeting", + "start": "2025-08-18T06:00:00Z", + "end": "2025-08-18T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#9c27b0" + } + }, + { + "id": "73", + "title": "Team Standup", + "start": "2025-08-19T05:00:00Z", + "end": "2025-08-19T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "74", + "title": "Development Work", + "start": "2025-08-19T10:00:00Z", + "end": "2025-08-19T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#3f51b5" + } + }, + { + "id": "75", + "title": "Team Standup", + "start": "2025-08-20T05:00:00Z", + "end": "2025-08-20T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "76", + "title": "Architecture Planning", + "start": "2025-08-20T11:00:00Z", + "end": "2025-08-20T12:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#009688" + } + }, + { + "id": "77", + "title": "Team Standup", + "start": "2025-08-21T05:00:00Z", + "end": "2025-08-21T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "78", + "title": "Product Review", + "start": "2025-08-21T10:00:00Z", + "end": "2025-08-21T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "79", + "title": "Team Standup", + "start": "2025-08-22T05:00:00Z", + "end": "2025-08-22T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "80", + "title": "End of Sprint", + "start": "2025-08-22T12:00:00Z", + "end": "2025-08-22T13:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#f44336" + } + }, + { + "id": "81", + "title": "Team Standup", + "start": "2025-08-25T05:00:00Z", + "end": "2025-08-25T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "82", + "title": "Sprint Planning", + "start": "2025-08-25T06:00:00Z", + "end": "2025-08-25T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "83", + "title": "Team Standup", + "start": "2025-08-26T05:00:00Z", + "end": "2025-08-26T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "84", + "title": "Design Review", + "start": "2025-08-26T10:00:00Z", + "end": "2025-08-26T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#e91e63" + } + }, + { + "id": "85", + "title": "Team Standup", + "start": "2025-08-27T05:00:00Z", + "end": "2025-08-27T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "86", + "title": "Development Session", + "start": "2025-08-27T06:00:00Z", + "end": "2025-08-27T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#2196f3" + } + }, + { + "id": "87", + "title": "Team Standup", + "start": "2025-08-28T05:00:00Z", + "end": "2025-08-28T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "88", + "title": "Customer Call", + "start": "2025-08-28T11:00:00Z", + "end": "2025-08-28T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#cddc39" + } + }, + { + "id": "89", + "title": "Team Standup", + "start": "2025-08-29T05:00:00Z", + "end": "2025-08-29T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "90", + "title": "Monthly Review", + "start": "2025-08-29T10:00:00Z", + "end": "2025-08-29T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#795548" + } + }, + { + "id": "91", + "title": "Team Standup", + "start": "2025-09-01T05:00:00Z", + "end": "2025-09-01T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "92", + "title": "September Kickoff", + "start": "2025-09-01T06:00:00Z", + "end": "2025-09-01T07:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#4caf50" + } + }, + { + "id": "93", + "title": "Team Standup", + "start": "2025-09-02T05:00:00Z", + "end": "2025-09-02T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "94", + "title": "Product Planning", + "start": "2025-09-02T10:00:00Z", + "end": "2025-09-02T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#9c27b0" + } + }, + { + "id": "95", + "title": "Team Standup", + "start": "2025-09-03T05:00:00Z", + "end": "2025-09-03T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "96", + "title": "Deep Work", + "start": "2025-09-02T11:00:00Z", + "end": "2025-09-02T11:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#3f51b5" + } + }, + { + "id": "97", + "title": "Team Standup", + "start": "2025-09-04T05:00:00Z", + "end": "2025-09-04T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "98", + "title": "Technical Review", + "start": "2025-09-04T11:00:00Z", + "end": "2025-09-04T12:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#009688" + } + }, + { + "id": "99", + "title": "Team Standup", + "start": "2025-09-05T05:00:00Z", + "end": "2025-09-05T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "100", + "title": "Sprint Review", + "start": "2025-09-04T11:00:00Z", + "end": "2025-09-04T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "101", + "title": "Weekend Workshop", + "start": "2025-09-06T06:00:00Z", + "end": "2025-09-06T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#f44336" + } + }, + { + "id": "102", + "title": "Team Standup", + "start": "2025-09-08T05:00:00Z", + "end": "2025-09-08T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "103", + "title": "Sprint Planning", + "start": "2025-09-08T06:00:00Z", + "end": "2025-09-08T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "104", + "title": "Team Standup", + "start": "2025-09-09T05:00:00Z", + "end": "2025-09-09T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "105", + "title": "Client Workshop", + "start": "2025-09-09T10:00:00Z", + "end": "2025-09-09T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#e91e63" + } + }, + { + "id": "106", + "title": "Team Standup", + "start": "2025-09-10T05:00:00Z", + "end": "2025-09-10T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "107", + "title": "Development Work", + "start": "2025-09-10T06:00:00Z", + "end": "2025-09-10T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#2196f3" + } + }, + { + "id": "108", + "title": "Team Standup", + "start": "2025-09-11T05:00:00Z", + "end": "2025-09-11T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "109", + "title": "Performance Review", + "start": "2025-09-11T11:00:00Z", + "end": "2025-09-11T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "110", + "title": "Team Standup", + "start": "2025-09-12T05:00:00Z", + "end": "2025-09-12T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "111", + "title": "Q3 Review", + "start": "2025-09-12T10:00:00Z", + "end": "2025-09-12T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#9c27b0" + } + }, + { + "id": "112", + "title": "Autumn Equinox", + "start": "2025-09-23T00:00:00Z", + "end": "2025-09-22T23:59:59Z", + "type": "milestone", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 1440, + "color": "#ff6f00" + } + }, + { + "id": "113", + "title": "Team Standup", + "start": "2025-09-15T05:00:00Z", + "end": "2025-09-15T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "114", + "title": "Weekly Planning", + "start": "2025-09-15T06:00:00Z", + "end": "2025-09-15T07:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#3f51b5" + } + }, + { + "id": "115", + "title": "Team Standup", + "start": "2025-09-16T05:00:00Z", + "end": "2025-09-16T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "116", + "title": "Feature Demo", + "start": "2025-09-16T11:00:00Z", + "end": "2025-09-16T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#cddc39" + } + }, + { + "id": "117", + "title": "Team Standup", + "start": "2025-09-17T05:00:00Z", + "end": "2025-09-17T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "118", + "title": "Code Refactoring", + "start": "2025-09-17T06:00:00Z", + "end": "2025-09-17T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#009688" + } + }, + { + "id": "119", + "title": "Team Standup", + "start": "2025-09-18T05:00:00Z", + "end": "2025-09-18T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "120", + "title": "End of Sprint", + "start": "2025-09-19T12:00:00Z", + "end": "2025-09-19T13:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#f44336" + } + }, + { + "id": "121", + "title": "Azure Setup", + "start": "2025-09-10T06:30:00Z", + "end": "2025-09-10T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#2196f3" + } + }, + { + "id": "122", + "title": "Multi-Day Conference", + "start": "2025-09-22T00:00:00Z", + "end": "2025-09-23T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#4caf50" + } + }, + { + "id": "123", + "title": "Project Sprint", + "start": "2025-09-23T00:00:00Z", + "end": "2025-09-24T23:59:59Z", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#2196f3" + } + }, + { + "id": "124", + "title": "Training Week", + "start": "2025-09-29T00:00:00Z", + "end": "2025-10-02T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 7200, + "color": "#9c27b0" + } + }, + { + "id": "125", + "title": "Holiday Weekend", + "start": "2025-10-04T00:00:00Z", + "end": "2025-10-05T23:59:59Z", + "type": "milestone", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#ff6f00" + } + }, + { + "id": "126", + "title": "Client Visit", + "start": "2025-10-07T00:00:00Z", + "end": "2025-10-08T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#e91e63" + } + }, + { + "id": "127", + "title": "Development Marathon", + "start": "2025-10-13T00:00:00Z", + "end": "2025-10-14T23:59:59Z", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#3f51b5" + } + }, + { + "id": "128", + "title": "Morgen Standup", + "start": "2025-09-22T05:00:00Z", + "end": "2025-09-22T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "129", + "title": "Klient Præsentation", + "start": "2025-09-22T10:00:00Z", + "end": "2025-09-22T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#e91e63" + } + }, + { + "id": "130", + "title": "Eftermiddags Kodning", + "start": "2025-09-22T12:00:00Z", + "end": "2025-09-22T14:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#2196f3" + } + }, + { + "id": "131", + "title": "Team Standup", + "start": "2025-09-23T05:00:00Z", + "end": "2025-09-23T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "132", + "title": "Arkitektur Review", + "start": "2025-09-23T07:00:00Z", + "end": "2025-09-23T08:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#009688" + } + }, + { + "id": "133", + "title": "Frokost & Læring", + "start": "2025-09-23T08:30:00Z", + "end": "2025-09-23T09:30:00Z", + "type": "meal", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ff9800" + } + }, + { + "id": "134", + "title": "Team Standup", + "start": "2025-09-24T05:00:00Z", + "end": "2025-09-24T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "135", + "title": "Database Optimering", + "start": "2025-09-24T06:00:00Z", + "end": "2025-09-24T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#3f51b5" + } + }, + { + "id": "136", + "title": "Klient Opkald", + "start": "2025-09-24T11:00:00Z", + "end": "2025-09-24T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "137", + "title": "Team Standup", + "start": "2025-09-25T05:00:00Z", + "end": "2025-09-25T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "138", + "title": "Sprint Review", + "start": "2025-09-25T10:00:00Z", + "end": "2025-09-25T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "139", + "title": "Retrospektiv", + "start": "2025-09-25T11:30:00Z", + "end": "2025-09-25T12:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#9c27b0" + } + }, + { + "id": "140", + "title": "Team Standup", + "start": "2025-09-26T05:00:00Z", + "end": "2025-09-26T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "141", + "title": "Ny Feature Udvikling", + "start": "2025-09-26T06:00:00Z", + "end": "2025-09-26T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#4caf50" + } + }, + { + "id": "142", + "title": "Sikkerhedsgennemgang", + "start": "2025-09-26T10:00:00Z", + "end": "2025-09-26T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#f44336" + } + }, + { + "id": "143", + "title": "Weekend Hackathon", + "start": "2025-09-27T00:00:00Z", + "end": "2025-09-27T23:59:59Z", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 2880, + "color": "#673ab7" + } + }, + { + "id": "144", + "title": "Team Standup", + "start": "2025-09-29T07:30:00Z", + "end": "2025-09-29T08:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "145", + "title": "Månedlig Planlægning", + "start": "2025-09-29T07:00:00Z", + "end": "2025-09-29T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#9c27b0" + } + }, + { + "id": "146", + "title": "Performance Test", + "start": "2025-09-29T08:15:00Z", + "end": "2025-09-29T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#00bcd4" + } + }, + { + "id": "147", + "title": "Team Standup", + "start": "2025-09-30T05:00:00Z", + "end": "2025-09-30T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "148", + "title": "Kvartal Afslutning", + "start": "2025-09-30T11:00:00Z", + "end": "2025-09-30T13:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#f44336" + } + },{ + "id": "1481", + "title": "Kvartal Afslutning 2", + "start": "2025-09-30T11:20:00Z", + "end": "2025-09-30T13:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#f44336" + } + }, + { + "id": "149", + "title": "Oktober Kickoff", + "start": "2025-10-01T05:00:00Z", + "end": "2025-10-01T06:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#4caf50" + } + }, + { + "id": "150", + "title": "Sprint Planlægning", + "start": "2025-10-01T06:30:00Z", + "end": "2025-10-01T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#673ab7" + } + }, + { + "id": "151", + "title": "Eftermiddags Kodning", + "start": "2025-10-01T10:00:00Z", + "end": "2025-10-01T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "1511", + "title": "Eftermiddags Kodning", + "start": "2025-10-01T10:30:00Z", + "end": "2025-10-01T11:00:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "1512", + "title": "Eftermiddags Kodning", + "start": "2025-10-01T11:30:00Z", + "end": "2025-10-01T12:30:00Z", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "1513", + "title": "Eftermiddags Kodning", + "start": "2025-10-01T12:00:00Z", + "end": "2025-10-01T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "1514", + "title": "Eftermiddags Kodning 2", + "start": "2025-10-01T12:00:00Z", + "end": "2025-10-01T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "152", + "title": "Team Standup", + "start": "2025-10-02T05:00:00Z", + "end": "2025-10-02T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "153", + "title": "API Design Workshop", + "start": "2025-10-02T07:00:00Z", + "end": "2025-10-02T08:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#009688" + } + }, + { + "id": "154", + "title": "Bug Fixing Session", + "start": "2025-10-02T07:00:00Z", + "end": "2025-10-02T09:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#ff5722" + } + }, + { + "id": "155", + "title": "Team Standup", + "start": "2025-10-03T05:00:00Z", + "end": "2025-10-03T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "156", + "title": "Klient Demo", + "start": "2025-10-03T10:00:00Z", + "end": "2025-10-03T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#e91e63" + } + }, + { + "id": "157", + "title": "Code Review Session", + "start": "2025-10-03T12:00:00Z", + "end": "2025-10-03T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#009688" + } + }, + { + "id": "158", + "title": "Fredag Standup", + "start": "2025-10-04T05:00:00Z", + "end": "2025-10-04T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "159", + "title": "Uge Retrospektiv", + "start": "2025-10-04T11:00:00Z", + "end": "2025-10-04T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#9c27b0" + } + }, + { + "id": "160", + "title": "Weekend Projekt", + "start": "2025-10-05T06:00:00Z", + "end": "2025-10-05T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 240, + "color": "#3f51b5" + } + }, + { + "id": "161", + "title": "Teknisk Workshop", + "start": "2025-09-24T00:00:00Z", + "end": "2025-09-25T23:59:59Z", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#795548" + } + }, + { + "id": "162", + "title": "Produktudvikling Sprint", + "start": "2025-10-01T08:00:00Z", + "end": "2025-10-02T21:00:00Z", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { + "duration": 4320, + "color": "#cddc39" + } + }, + { + "id": "163", + "title": "Tidlig Morgen Træning", + "start": "2025-09-23T02:30:00Z", + "end": "2025-09-23T03:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#00bcd4" + } + }, + { + "id": "164", + "title": "Sen Aften Deploy", + "start": "2025-09-25T18:00:00Z", + "end": "2025-09-25T20:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 150, + "color": "#ffc107" + } + }, + { + "id": "165", + "title": "Overlappende Møde A", + "start": "2025-09-30T06:00:00Z", + "end": "2025-09-30T07:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#8bc34a" + } + }, + { + "id": "166", + "title": "Overlappende Møde B", + "start": "2025-09-30T06:30:00Z", + "end": "2025-09-30T08:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#ff6f00" + } + }, + { + "id": "167", + "title": "Kort Check-in", + "start": "2025-10-02T05:45:00Z", + "end": "2025-10-02T06:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 15, + "color": "#607d8b" + } + }, + { + "id": "168", + "title": "Lang Udviklingssession", + "start": "2025-10-04T05:00:00Z", + "end": "2025-10-04T09:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 240, + "color": "#2196f3" + } + }, + { + "id": "S1A", + "title": "Scenario 1: Event A", + "start": "2025-10-06T05:00:00Z", + "end": "2025-10-06T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 300, + "color": "#ff6b6b" + } + }, + { + "id": "S1B", + "title": "Scenario 1: Event B", + "start": "2025-10-06T06:00:00Z", + "end": "2025-10-06T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#4ecdc4" + } + }, + { + "id": "S1C", + "title": "Scenario 1: Event C", + "start": "2025-10-06T08:30:00Z", + "end": "2025-10-06T09:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ffe66d" + } + }, + { + "id": "S2A", + "title": "Scenario 2: Event A", + "start": "2025-10-06T11:00:00Z", + "end": "2025-10-06T17:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 360, + "color": "#ff6b6b" + } + }, + { + "id": "S2B", + "title": "Scenario 2: Event B", + "start": "2025-10-06T12:00:00Z", + "end": "2025-10-06T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#4ecdc4" + } + }, + { + "id": "S2C", + "title": "Scenario 2: Event C", + "start": "2025-10-06T13:30:00Z", + "end": "2025-10-06T14:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ffe66d" + } + }, + { + "id": "S2D", + "title": "Scenario 2: Event D", + "start": "2025-10-06T15:00:00Z", + "end": "2025-10-06T16:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#a8e6cf" + } + }, + { + "id": "S3A", + "title": "Scenario 3: Event A", + "start": "2025-10-07T07:00:00Z", + "end": "2025-10-07T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 360, + "color": "#ff6b6b" + } + }, + { + "id": "S3B", + "title": "Scenario 3: Event B", + "start": "2025-10-07T08:00:00Z", + "end": "2025-10-07T11:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#4ecdc4" + } + }, + { + "id": "S3C", + "title": "Scenario 3: Event C", + "start": "2025-10-07T09:00:00Z", + "end": "2025-10-07T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ffe66d" + } + }, + { + "id": "S3D", + "title": "Scenario 3: Event D", + "start": "2025-10-07T10:30:00Z", + "end": "2025-10-07T11:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#a8e6cf" + } + }, + { + "id": "S4A", + "title": "Scenario 4: Event A", + "start": "2025-10-07T14:00:00Z", + "end": "2025-10-07T20:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 360, + "color": "#ff6b6b" + } + }, + { + "id": "S4B", + "title": "Scenario 4: Event B", + "start": "2025-10-07T15:00:00Z", + "end": "2025-10-07T19:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 240, + "color": "#4ecdc4" + } + }, + { + "id": "S4C", + "title": "Scenario 4: Event C", + "start": "2025-10-07T16:00:00Z", + "end": "2025-10-07T18:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#ffe66d" + } + }, + { + "id": "S5A", + "title": "Scenario 5: Event A", + "start": "2025-10-08T05:00:00Z", + "end": "2025-10-08T08:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#ff6b6b" + } + }, + { + "id": "S5B", + "title": "Scenario 5: Event B", + "start": "2025-10-08T06:00:00Z", + "end": "2025-10-08T07:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#4ecdc4" + } + }, + { + "id": "S5C", + "title": "Scenario 5: Event C", + "start": "2025-10-08T06:00:00Z", + "end": "2025-10-08T07:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ffe66d" + } + }, + { + "id": "S6A", + "title": "Scenario 6: Event A", + "start": "2025-10-08T09:00:00Z", + "end": "2025-10-08T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#ff6b6b" + } + }, + { + "id": "S6B", + "title": "Scenario 6: Event B", + "start": "2025-10-08T10:00:00Z", + "end": "2025-10-08T11:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#4ecdc4" + } + }, + { + "id": "S6C", + "title": "Scenario 6: Event C", + "start": "2025-10-08T10:00:00Z", + "end": "2025-10-08T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ffe66d" + } + }, + { + "id": "S6D", + "title": "Scenario 6: Event D", + "start": "2025-10-08T10:30:00Z", + "end": "2025-10-08T10:45:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 15, + "color": "#a8e6cf" + } + }, + { + "id": "S7A", + "title": "Scenario 7: Event A", + "start": "2025-10-09T05:00:00Z", + "end": "2025-10-09T07:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 150, + "color": "#009688" + } + }, + { + "id": "S7B", + "title": "Scenario 7: Event B", + "start": "2025-10-09T05:00:00Z", + "end": "2025-10-09T07:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 120, + "color": "#ff5722" + } + }, + { + "id": "S8A", + "title": "Scenario 8: Event A", + "start": "2025-10-09T08:00:00Z", + "end": "2025-10-09T09:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ff6b6b" + } + }, + { + "id": "S8B", + "title": "Scenario 8: Event B", + "start": "2025-10-09T08:15:00Z", + "end": "2025-10-09T09:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 75, + "color": "#4ecdc4" + } + }, + { + "id": "S9A", + "title": "Scenario 9: Event A", + "start": "2025-10-09T10:00:00Z", + "end": "2025-10-09T11:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ff6b6b" + } + }, + { + "id": "S9B", + "title": "Scenario 9: Event B", + "start": "2025-10-09T10:30:00Z", + "end": "2025-10-09T11:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#4ecdc4" + } + }, + { + "id": "S9C", + "title": "Scenario 9: Event C", + "start": "2025-10-09T11:15:00Z", + "end": "2025-10-09T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 105, + "color": "#ffe66d" + } + }, + { + "id": "S10A", + "title": "Scenario 10: Event A", + "start": "2025-10-10T10:00:00Z", + "end": "2025-10-10T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#ff6b6b" + } + }, + { + "id": "S10B", + "title": "Scenario 10: Event B", + "start": "2025-10-10T10:30:00Z", + "end": "2025-10-10T11:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#4ecdc4" + } + }, + { + "id": "S10C", + "title": "Scenario 10: Event C", + "start": "2025-10-10T11:30:00Z", + "end": "2025-10-10T12:30:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#ffe66d" + } + }, + { + "id": "S10D", + "title": "Scenario 10: Event D", + "start": "2025-10-10T12:00:00Z", + "end": "2025-10-10T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#a8e6cf" + } + }, + { + "id": "S10E", + "title": "Scenario 10: Event E", + "start": "2025-10-10T12:00:00Z", + "end": "2025-10-10T13:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#dda15e" + } + }, + { + "id": "169", + "title": "Morgen Standup", + "start": "2025-10-13T05:00:00Z", + "end": "2025-10-13T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "170", + "title": "Produktvejledning", + "start": "2025-10-13T07:00:00Z", + "end": "2025-10-13T08:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#9c27b0" + } + }, + { + "id": "171", + "title": "Team Standup", + "start": "2025-10-14T05:00:00Z", + "end": "2025-10-14T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "172", + "title": "Udviklingssession", + "start": "2025-10-14T06:00:00Z", + "end": "2025-10-14T09:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "173", + "title": "Klient Gennemgang", + "start": "2025-10-15T11:00:00Z", + "end": "2025-10-15T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "174", + "title": "Team Standup", + "start": "2025-10-16T05:00:00Z", + "end": "2025-10-16T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "175", + "title": "Arkitektur Workshop", + "start": "2025-10-16T10:00:00Z", + "end": "2025-10-16T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#009688" + } + }, + { + "id": "176", + "title": "Team Standup", + "start": "2025-10-17T05:00:00Z", + "end": "2025-10-17T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "177", + "title": "Sprint Review", + "start": "2025-10-17T10:00:00Z", + "end": "2025-10-17T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "178", + "title": "Weekend Kodning", + "start": "2025-10-18T06:00:00Z", + "end": "2025-10-18T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 240, + "color": "#3f51b5" + } + } +] \ No newline at end of file diff --git a/wwwroot/data/mock-resource-events.json b/wwwroot/data/mock-resource-events.json new file mode 100644 index 0000000..a569174 --- /dev/null +++ b/wwwroot/data/mock-resource-events.json @@ -0,0 +1,135 @@ +{ + "date": "2025-08-05", + "resources": [ + { + "name": "karina.knudsen", + "displayName": "Karina Knudsen", + "avatarUrl": "/avatars/karina.jpg", + "employeeId": "EMP001", + "events": [ + { + "id": "1", + "title": "Balayage langt hår", + "start": "2025-08-05T10:00:00", + "end": "2025-08-05T11:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#9c27b0" } + }, + { + "id": "2", + "title": "Klipning og styling", + "start": "2025-08-05T14:00:00", + "end": "2025-08-05T15:30:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#e91e63" } + } + ] + }, + { + "name": "maria.hansen", + "displayName": "Maria Hansen", + "avatarUrl": "/avatars/maria.jpg", + "employeeId": "EMP002", + "events": [ + { + "id": "3", + "title": "Permanent", + "start": "2025-08-05T09:00:00", + "end": "2025-08-05T11:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#3f51b5" } + }, + { + "id": "4", + "title": "Farve behandling", + "start": "2025-08-05T13:00:00", + "end": "2025-08-05T15:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#ff9800" } + } + ] + }, + { + "name": "lars.nielsen", + "displayName": "Lars Nielsen", + "avatarUrl": "/avatars/lars.jpg", + "employeeId": "EMP003", + "events": [ + { + "id": "5", + "title": "Herreklipning", + "start": "2025-08-05T11:00:00", + "end": "2025-08-05T11:30:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#795548" } + }, + { + "id": "6", + "title": "Skæg trimning", + "start": "2025-08-05T16:00:00", + "end": "2025-08-05T16:30:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#607d8b" } + } + ] + }, + { + "name": "anna.petersen", + "displayName": "Anna Petersen", + "avatarUrl": "/avatars/anna.jpg", + "employeeId": "EMP004", + "events": [ + { + "id": "7", + "title": "Bryllupsfrisure", + "start": "2025-08-05T08:00:00", + "end": "2025-08-05T10:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#009688" } + } + ] + }, + { + "name": "thomas.olsen", + "displayName": "Thomas Olsen", + "avatarUrl": "/avatars/thomas.jpg", + "employeeId": "EMP005", + "events": [ + { + "id": "8", + "title": "Highlights", + "start": "2025-08-05T12:00:00", + "end": "2025-08-05T14:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#8bc34a" } + }, + { + "id": "9", + "title": "Styling konsultation", + "start": "2025-08-05T15:00:00", + "end": "2025-08-05T15:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#cddc39" } + } + ] + } + ] +} \ No newline at end of file