Refactor CalendarConfig to static configuration class
Converts CalendarConfig to a pure static configuration management class with improved initialization and dependency handling Removes event bus dependencies and simplifies configuration loading Adds static methods for config management and initialization Improves flexibility and reduces class complexity
This commit is contained in:
parent
8bbb2f05d3
commit
349e1e8293
7 changed files with 3299 additions and 318 deletions
|
|
@ -1,8 +1,7 @@
|
||||||
// Calendar configuration management
|
// Calendar configuration management
|
||||||
|
// Pure static configuration class - no dependencies, no events
|
||||||
|
|
||||||
import { eventBus } from './EventBus';
|
import { ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
|
||||||
import { CalendarConfig as ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes';
|
|
||||||
import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter';
|
import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,376 +85,281 @@ interface TimeFormatConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calendar configuration management
|
* Calendar configuration management - Pure static config
|
||||||
*/
|
*/
|
||||||
export class CalendarConfig {
|
export class CalendarConfig {
|
||||||
private config: ICalendarConfig;
|
private static config: ICalendarConfig = {
|
||||||
private calendarMode: CalendarMode = 'date';
|
// Scrollbar styling
|
||||||
private selectedDate: Date | null = null;
|
scrollbarWidth: 16, // Width of scrollbar in pixels
|
||||||
private gridSettings: GridSettings;
|
scrollbarColor: '#666', // Scrollbar thumb color
|
||||||
private dateViewSettings: DateViewSettings;
|
scrollbarTrackColor: '#f0f0f0', // Scrollbar track color
|
||||||
private resourceViewSettings: ResourceViewSettings;
|
scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color
|
||||||
private currentWorkWeek: string = 'standard';
|
scrollbarBorderRadius: 6, // Border radius for scrollbar thumb
|
||||||
private timeFormatConfig: TimeFormatConfig;
|
|
||||||
|
|
||||||
constructor() {
|
// Interaction settings
|
||||||
this.config = {
|
allowDrag: true,
|
||||||
// Scrollbar styling
|
allowResize: true,
|
||||||
scrollbarWidth: 16, // Width of scrollbar in pixels
|
allowCreate: true,
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
// Grid display settings
|
// API settings
|
||||||
this.gridSettings = {
|
apiEndpoint: '/api/events',
|
||||||
hourHeight: 60,
|
dateFormat: 'YYYY-MM-DD',
|
||||||
dayStartHour: 0,
|
timeFormat: 'HH:mm',
|
||||||
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
|
// Feature flags
|
||||||
this.dateViewSettings = {
|
enableSearch: true,
|
||||||
period: 'week',
|
enableTouch: true,
|
||||||
weekDays: 7,
|
|
||||||
firstDayOfWeek: 1,
|
|
||||||
showAllDay: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resource view settings
|
// Event defaults
|
||||||
this.resourceViewSettings = {
|
defaultEventDuration: 60, // Minutes
|
||||||
maxResources: 10,
|
minEventDuration: 15, // Will be same as snapInterval
|
||||||
showAvatars: true,
|
maxEventDuration: 480 // 8 hours
|
||||||
avatarSize: 32,
|
};
|
||||||
resourceNameFormat: 'full',
|
|
||||||
showResourceDetails: true,
|
|
||||||
showAllDay: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Time format settings - default to Denmark with technical format
|
private static calendarMode: CalendarMode = 'date';
|
||||||
this.timeFormatConfig = {
|
private static selectedDate: Date | null = new Date();
|
||||||
timezone: 'Europe/Copenhagen',
|
private static currentWorkWeek: string = 'standard';
|
||||||
use24HourFormat: true,
|
|
||||||
locale: 'da-DK',
|
|
||||||
dateFormat: 'technical',
|
|
||||||
showSeconds: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set computed values
|
// Grid display settings
|
||||||
this.config.minEventDuration = this.gridSettings.snapInterval;
|
private static gridSettings: GridSettings = {
|
||||||
|
hourHeight: 60,
|
||||||
// Initialize TimeFormatter with default settings
|
dayStartHour: 0,
|
||||||
TimeFormatter.configure(this.timeFormatConfig);
|
dayEndHour: 24,
|
||||||
|
workStartHour: 8,
|
||||||
// Load calendar type from URL parameter
|
workEndHour: 17,
|
||||||
this.loadCalendarType();
|
snapInterval: 15,
|
||||||
|
gridStartThresholdMinutes: 30, // Events starting within ±15 min share grid columns
|
||||||
// Load from data attributes
|
showCurrentTime: true,
|
||||||
this.loadFromDOM();
|
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 {
|
static initialize(): void {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
// Set computed values
|
||||||
const typeParam = urlParams.get('type');
|
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
|
||||||
const dateParam = urlParams.get('date');
|
|
||||||
|
// Initialize TimeFormatter with default settings
|
||||||
// Set calendar mode
|
TimeFormatter.configure(CalendarConfig.timeFormatConfig);
|
||||||
if (typeParam === 'resource' || typeParam === 'date') {
|
|
||||||
this.calendarMode = typeParam;
|
// Load from data attributes
|
||||||
} else {
|
CalendarConfig.loadFromDOM();
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration from DOM data attributes
|
* Load configuration from DOM data attributes
|
||||||
*/
|
*/
|
||||||
private loadFromDOM(): void {
|
private static loadFromDOM(): void {
|
||||||
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
||||||
if (!calendar) return;
|
if (!calendar) return;
|
||||||
|
|
||||||
// Read data attributes
|
// Read data attributes
|
||||||
const attrs = calendar.dataset;
|
const attrs = calendar.dataset;
|
||||||
|
|
||||||
// Update date view settings
|
// Update date view settings
|
||||||
if (attrs.view) this.dateViewSettings.period = attrs.view as ViewPeriod;
|
if (attrs.view) CalendarConfig.dateViewSettings.period = attrs.view as ViewPeriod;
|
||||||
if (attrs.weekDays) this.dateViewSettings.weekDays = parseInt(attrs.weekDays);
|
if (attrs.weekDays) CalendarConfig.dateViewSettings.weekDays = parseInt(attrs.weekDays);
|
||||||
|
|
||||||
// Update grid settings
|
// Update grid settings
|
||||||
if (attrs.snapInterval) this.gridSettings.snapInterval = parseInt(attrs.snapInterval);
|
if (attrs.snapInterval) CalendarConfig.gridSettings.snapInterval = parseInt(attrs.snapInterval);
|
||||||
if (attrs.dayStartHour) this.gridSettings.dayStartHour = parseInt(attrs.dayStartHour);
|
if (attrs.dayStartHour) CalendarConfig.gridSettings.dayStartHour = parseInt(attrs.dayStartHour);
|
||||||
if (attrs.dayEndHour) this.gridSettings.dayEndHour = parseInt(attrs.dayEndHour);
|
if (attrs.dayEndHour) CalendarConfig.gridSettings.dayEndHour = parseInt(attrs.dayEndHour);
|
||||||
if (attrs.hourHeight) this.gridSettings.hourHeight = parseInt(attrs.hourHeight);
|
if (attrs.hourHeight) CalendarConfig.gridSettings.hourHeight = parseInt(attrs.hourHeight);
|
||||||
if (attrs.fitToWidth !== undefined) this.gridSettings.fitToWidth = attrs.fitToWidth === 'true';
|
if (attrs.fitToWidth !== undefined) CalendarConfig.gridSettings.fitToWidth = attrs.fitToWidth === 'true';
|
||||||
|
|
||||||
// Update computed values
|
// Update computed values
|
||||||
this.config.minEventDuration = this.gridSettings.snapInterval;
|
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a config value
|
* Get a config value
|
||||||
*/
|
*/
|
||||||
get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
|
static get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
|
||||||
return this.config[key];
|
return CalendarConfig.config[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a config value
|
* Set a config value (no events - use ConfigManager for updates with events)
|
||||||
*/
|
*/
|
||||||
set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
static set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
||||||
const oldValue = this.config[key];
|
CalendarConfig.config[key] = value;
|
||||||
this.config[key] = value;
|
|
||||||
|
|
||||||
// Update computed values handled in specific update methods
|
|
||||||
|
|
||||||
// Emit config update event
|
|
||||||
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
oldValue
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update multiple config values
|
* Update multiple config values (no events - use ConfigManager for updates with events)
|
||||||
*/
|
*/
|
||||||
update(updates: Partial<ICalendarConfig>): void {
|
static update(updates: Partial<ICalendarConfig>): void {
|
||||||
Object.entries(updates).forEach(([key, value]) => {
|
Object.entries(updates).forEach(([key, value]) => {
|
||||||
this.set(key as keyof ICalendarConfig, value);
|
CalendarConfig.set(key as keyof ICalendarConfig, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all config
|
* Get all config
|
||||||
*/
|
*/
|
||||||
getAll(): ICalendarConfig {
|
static getAll(): ICalendarConfig {
|
||||||
return { ...this.config };
|
return { ...CalendarConfig.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate derived values
|
* Calculate derived values
|
||||||
*/
|
*/
|
||||||
|
|
||||||
get minuteHeight(): number {
|
static get minuteHeight(): number {
|
||||||
return this.gridSettings.hourHeight / 60;
|
return CalendarConfig.gridSettings.hourHeight / 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
get totalHours(): number {
|
static get totalHours(): number {
|
||||||
return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour;
|
return CalendarConfig.gridSettings.dayEndHour - CalendarConfig.gridSettings.dayStartHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
get totalMinutes(): number {
|
static get totalMinutes(): number {
|
||||||
return this.totalHours * 60;
|
return CalendarConfig.totalHours * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
get slotsPerHour(): number {
|
static get slotsPerHour(): number {
|
||||||
return 60 / this.gridSettings.snapInterval;
|
return 60 / CalendarConfig.gridSettings.snapInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
get totalSlots(): number {
|
static get totalSlots(): number {
|
||||||
return this.totalHours * this.slotsPerHour;
|
return CalendarConfig.totalHours * CalendarConfig.slotsPerHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
get slotHeight(): number {
|
static get slotHeight(): number {
|
||||||
return this.gridSettings.hourHeight / this.slotsPerHour;
|
return CalendarConfig.gridSettings.hourHeight / CalendarConfig.slotsPerHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate snap interval
|
* Validate snap interval
|
||||||
*/
|
*/
|
||||||
isValidSnapInterval(interval: number): boolean {
|
static isValidSnapInterval(interval: number): boolean {
|
||||||
return [5, 10, 15, 30, 60].includes(interval);
|
return [5, 10, 15, 30, 60].includes(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get grid display settings
|
* Get grid display settings
|
||||||
*/
|
*/
|
||||||
getGridSettings(): GridSettings {
|
static getGridSettings(): GridSettings {
|
||||||
return { ...this.gridSettings };
|
return { ...CalendarConfig.gridSettings };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update grid display settings
|
* Update grid display settings (no events - use ConfigManager for updates with events)
|
||||||
*/
|
*/
|
||||||
updateGridSettings(updates: Partial<GridSettings>): void {
|
static updateGridSettings(updates: Partial<GridSettings>): void {
|
||||||
this.gridSettings = { ...this.gridSettings, ...updates };
|
CalendarConfig.gridSettings = { ...CalendarConfig.gridSettings, ...updates };
|
||||||
|
|
||||||
// Update computed values
|
// Update computed values
|
||||||
if (updates.snapInterval) {
|
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
|
* Get date view settings
|
||||||
*/
|
*/
|
||||||
getDateViewSettings(): DateViewSettings {
|
static getDateViewSettings(): DateViewSettings {
|
||||||
return { ...this.dateViewSettings };
|
return { ...CalendarConfig.dateViewSettings };
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update date view settings
|
|
||||||
*/
|
|
||||||
updateDateViewSettings(updates: Partial<DateViewSettings>): 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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get resource view settings
|
* Get resource view settings
|
||||||
*/
|
*/
|
||||||
getResourceViewSettings(): ResourceViewSettings {
|
static getResourceViewSettings(): ResourceViewSettings {
|
||||||
return { ...this.resourceViewSettings };
|
return { ...CalendarConfig.resourceViewSettings };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update resource view settings
|
|
||||||
*/
|
|
||||||
updateResourceViewSettings(updates: Partial<ResourceViewSettings>): 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
|
* Get calendar mode
|
||||||
*/
|
*/
|
||||||
getCalendarMode(): CalendarMode {
|
static getCalendarMode(): CalendarMode {
|
||||||
return this.calendarMode;
|
return CalendarConfig.calendarMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set calendar mode
|
* Set calendar mode
|
||||||
*/
|
*/
|
||||||
setCalendarMode(mode: CalendarMode): void {
|
static setCalendarMode(mode: CalendarMode): void {
|
||||||
const oldMode = this.calendarMode;
|
CalendarConfig.calendarMode = mode;
|
||||||
this.calendarMode = mode;
|
|
||||||
|
|
||||||
// Emit calendar mode change event
|
|
||||||
eventBus.emit(CoreEvents.VIEW_CHANGED, {
|
|
||||||
oldType: oldMode,
|
|
||||||
newType: mode
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get selected date
|
* Get selected date
|
||||||
*/
|
*/
|
||||||
getSelectedDate(): Date | null {
|
static getSelectedDate(): Date | null {
|
||||||
return this.selectedDate;
|
return CalendarConfig.selectedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set selected date
|
* Set selected date
|
||||||
* Note: Does not emit events - caller is responsible for event emission
|
* Note: Does not emit events - caller is responsible for event emission
|
||||||
*/
|
*/
|
||||||
setSelectedDate(date: Date): void {
|
static setSelectedDate(date: Date): void {
|
||||||
this.selectedDate = date;
|
CalendarConfig.selectedDate = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get work week presets
|
* Get work week presets
|
||||||
*/
|
*/
|
||||||
private getWorkWeekPresets(): { [key: string]: WorkWeekSettings } {
|
private static getWorkWeekPresets(): { [key: string]: WorkWeekSettings } {
|
||||||
return {
|
return {
|
||||||
'standard': {
|
'standard': {
|
||||||
id: 'standard',
|
id: 'standard',
|
||||||
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
|
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
|
||||||
totalDays: 5,
|
totalDays: 5,
|
||||||
firstWorkDay: 1
|
firstWorkDay: 1
|
||||||
},
|
},
|
||||||
'compressed': {
|
'compressed': {
|
||||||
id: 'compressed',
|
id: 'compressed',
|
||||||
workDays: [1,2,3,4], // Monday-Thursday (ISO)
|
workDays: [1,2,3,4], // Monday-Thursday (ISO)
|
||||||
totalDays: 4,
|
totalDays: 4,
|
||||||
firstWorkDay: 1
|
firstWorkDay: 1
|
||||||
},
|
},
|
||||||
'midweek': {
|
'midweek': {
|
||||||
id: 'midweek',
|
id: 'midweek',
|
||||||
workDays: [3,4,5], // Wednesday-Friday (ISO)
|
workDays: [3,4,5], // Wednesday-Friday (ISO)
|
||||||
totalDays: 3,
|
totalDays: 3,
|
||||||
firstWorkDay: 3
|
firstWorkDay: 3
|
||||||
},
|
},
|
||||||
'weekend': {
|
'weekend': {
|
||||||
id: 'weekend',
|
id: 'weekend',
|
||||||
workDays: [6,7], // Saturday-Sunday (ISO)
|
workDays: [6,7], // Saturday-Sunday (ISO)
|
||||||
totalDays: 2,
|
totalDays: 2,
|
||||||
firstWorkDay: 6
|
firstWorkDay: 6
|
||||||
},
|
},
|
||||||
'fullweek': {
|
'fullweek': {
|
||||||
id: 'fullweek',
|
id: 'fullweek',
|
||||||
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
|
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
|
||||||
totalDays: 7,
|
totalDays: 7,
|
||||||
|
|
@ -467,109 +371,115 @@ export class CalendarConfig {
|
||||||
/**
|
/**
|
||||||
* Get current work week settings
|
* Get current work week settings
|
||||||
*/
|
*/
|
||||||
getWorkWeekSettings(): WorkWeekSettings {
|
static getWorkWeekSettings(): WorkWeekSettings {
|
||||||
const presets = this.getWorkWeekPresets();
|
const presets = CalendarConfig.getWorkWeekPresets();
|
||||||
return presets[this.currentWorkWeek] || presets['standard'];
|
return presets[CalendarConfig.currentWorkWeek] || presets['standard'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set work week preset
|
* Set work week preset
|
||||||
* Note: Does not emit events - caller is responsible for event emission
|
* Note: Does not emit events - caller is responsible for event emission
|
||||||
*/
|
*/
|
||||||
setWorkWeek(workWeekId: string): void {
|
static setWorkWeek(workWeekId: string): void {
|
||||||
const presets = this.getWorkWeekPresets();
|
const presets = CalendarConfig.getWorkWeekPresets();
|
||||||
if (presets[workWeekId]) {
|
if (presets[workWeekId]) {
|
||||||
this.currentWorkWeek = workWeekId;
|
CalendarConfig.currentWorkWeek = workWeekId;
|
||||||
|
|
||||||
// Update dateViewSettings to match work week
|
// Update dateViewSettings to match work week
|
||||||
this.dateViewSettings.weekDays = presets[workWeekId].totalDays;
|
CalendarConfig.dateViewSettings.weekDays = presets[workWeekId].totalDays;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current work week ID
|
* Get current work week ID
|
||||||
*/
|
*/
|
||||||
getCurrentWorkWeek(): string {
|
static getCurrentWorkWeek(): string {
|
||||||
return this.currentWorkWeek;
|
return CalendarConfig.currentWorkWeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get time format settings
|
* Get time format settings
|
||||||
*/
|
*/
|
||||||
getTimeFormatSettings(): TimeFormatConfig {
|
static getTimeFormatSettings(): TimeFormatConfig {
|
||||||
return { ...this.timeFormatConfig };
|
return { ...CalendarConfig.timeFormatConfig };
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update time format settings
|
|
||||||
*/
|
|
||||||
updateTimeFormatSettings(updates: Partial<TimeFormatConfig>): 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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get configured timezone
|
* Get configured timezone
|
||||||
*/
|
*/
|
||||||
getTimezone(): string {
|
static getTimezone(): string {
|
||||||
return this.timeFormatConfig.timezone;
|
return CalendarConfig.timeFormatConfig.timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get configured locale
|
* Get configured locale
|
||||||
*/
|
*/
|
||||||
getLocale(): string {
|
static getLocale(): string {
|
||||||
return this.timeFormatConfig.locale;
|
return CalendarConfig.timeFormatConfig.locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if using 24-hour format
|
* Check if using 24-hour format
|
||||||
*/
|
*/
|
||||||
is24HourFormat(): boolean {
|
static is24HourFormat(): boolean {
|
||||||
return this.timeFormatConfig.use24HourFormat;
|
return CalendarConfig.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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current date format
|
* Get current date format
|
||||||
*/
|
*/
|
||||||
getDateFormat(): 'locale' | 'technical' {
|
static getDateFormat(): 'locale' | 'technical' {
|
||||||
return this.timeFormatConfig.dateFormat;
|
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<ICalendarConfig>) { 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<GridSettings>) { 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(); }
|
||||||
}
|
}
|
||||||
37
src/index.ts
37
src/index.ts
|
|
@ -19,10 +19,11 @@ import { ResizeHandleManager } from './managers/ResizeHandleManager';
|
||||||
import { EdgeScrollManager } from './managers/EdgeScrollManager';
|
import { EdgeScrollManager } from './managers/EdgeScrollManager';
|
||||||
import { DragHoverManager } from './managers/DragHoverManager';
|
import { DragHoverManager } from './managers/DragHoverManager';
|
||||||
import { HeaderManager } from './managers/HeaderManager';
|
import { HeaderManager } from './managers/HeaderManager';
|
||||||
|
import { ConfigManager } from './managers/ConfigManager';
|
||||||
|
|
||||||
// Import renderers
|
// Import renderers
|
||||||
import { DateHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer';
|
import { DateHeaderRenderer, ResourceHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer';
|
||||||
import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
|
import { DateColumnRenderer, ResourceColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
|
||||||
import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer';
|
import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer';
|
||||||
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
|
||||||
import { GridRenderer } from './renderers/GridRenderer';
|
import { GridRenderer } from './renderers/GridRenderer';
|
||||||
|
|
@ -66,7 +67,8 @@ async function handleDeepLinking(eventManager: EventManager, urlManager: URLMana
|
||||||
*/
|
*/
|
||||||
async function initializeCalendar(): Promise<void> {
|
async function initializeCalendar(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Use the singleton calendar configuration
|
// Initialize static calendar configuration
|
||||||
|
CalendarConfig.initialize();
|
||||||
|
|
||||||
// Create NovaDI container
|
// Create NovaDI container
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
|
|
@ -75,20 +77,25 @@ async function initializeCalendar(): Promise<void> {
|
||||||
// Enable debug mode for development
|
// Enable debug mode for development
|
||||||
eventBus.setDebug(true);
|
eventBus.setDebug(true);
|
||||||
|
|
||||||
builder.registerType(CalendarConfig).as<CalendarConfig>().singleInstance();
|
// Register CalendarConfig as singleton instance (static class, not instantiated)
|
||||||
|
builder.registerInstance(CalendarConfig).as<CalendarConfig>();
|
||||||
|
|
||||||
|
// Register ConfigManager for event-driven config updates
|
||||||
|
builder.registerType(ConfigManager).as<ConfigManager>().singleInstance();
|
||||||
|
|
||||||
// Bind core services as instances
|
// Bind core services as instances
|
||||||
builder.registerInstance(eventBus).as<IEventBus>();
|
builder.registerInstance(eventBus).as<IEventBus>();
|
||||||
|
|
||||||
// Register renderers with keyed registration based on calendar mode
|
// Register renderers based on calendar mode
|
||||||
// Date mode renderers
|
const calendarMode = CalendarConfig.getCalendarMode();
|
||||||
builder.registerType(DateHeaderRenderer).as<HeaderRenderer>().keyed('date');
|
if (calendarMode === 'resource') {
|
||||||
builder.registerType(DateColumnRenderer).as<ColumnRenderer>().keyed('date');
|
builder.registerType(ResourceHeaderRenderer).as<HeaderRenderer>().singleInstance();
|
||||||
builder.registerType(DateEventRenderer).as<EventRendererStrategy>().keyed('date');
|
builder.registerType(ResourceColumnRenderer).as<ColumnRenderer>().singleInstance();
|
||||||
|
} else {
|
||||||
// Resource mode renderers (using same renderers for now)
|
builder.registerType(DateHeaderRenderer).as<HeaderRenderer>().singleInstance();
|
||||||
builder.registerType(DateHeaderRenderer).as<HeaderRenderer>().keyed('resource');
|
builder.registerType(DateColumnRenderer).as<ColumnRenderer>().singleInstance();
|
||||||
builder.registerType(DateColumnRenderer).as<ColumnRenderer>().keyed('resource');
|
}
|
||||||
builder.registerType(DateEventRenderer).as<EventRendererStrategy>().keyed('resource');
|
builder.registerType(DateEventRenderer).as<EventRendererStrategy>().singleInstance();
|
||||||
|
|
||||||
// Register core services and utilities
|
// Register core services and utilities
|
||||||
builder.registerType(DateService).as<DateService>().singleInstance();
|
builder.registerType(DateService).as<DateService>().singleInstance();
|
||||||
|
|
@ -167,9 +174,11 @@ async function initializeCalendar(): Promise<void> {
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initializeCalendar().catch(error => {
|
initializeCalendar().catch(error => {
|
||||||
|
console.error('Calendar initialization failed:', error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
initializeCalendar().catch(error => {
|
initializeCalendar().catch(error => {
|
||||||
|
console.error('Calendar initialization failed:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
118
src/managers/ConfigManager.ts
Normal file
118
src/managers/ConfigManager.ts
Normal file
|
|
@ -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<K extends keyof ICalendarConfig>(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<ICalendarConfig>): void {
|
||||||
|
Object.entries(updates).forEach(([key, value]) => {
|
||||||
|
this.set(key as keyof ICalendarConfig, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update grid display settings and emit event
|
||||||
|
*/
|
||||||
|
updateGridSettings(updates: Partial<GridSettings>): 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,8 +57,8 @@ export class EventManager {
|
||||||
private async loadMockData(): Promise<void> {
|
private async loadMockData(): Promise<void> {
|
||||||
const calendarType = this.config.getCalendarMode();
|
const calendarType = this.config.getCalendarMode();
|
||||||
const jsonFile = calendarType === 'resource'
|
const jsonFile = calendarType === 'resource'
|
||||||
? '/src/data/mock-resource-events.json'
|
? '/data/mock-resource-events.json'
|
||||||
: '/src/data/mock-events.json';
|
: '/data/mock-events.json';
|
||||||
|
|
||||||
const response = await fetch(jsonFile);
|
const response = await fetch(jsonFile);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export interface CalendarEvent {
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarConfig {
|
export interface ICalendarConfig {
|
||||||
// Scrollbar styling
|
// Scrollbar styling
|
||||||
scrollbarWidth: number; // Width of scrollbar in pixels
|
scrollbarWidth: number; // Width of scrollbar in pixels
|
||||||
scrollbarColor: string; // Scrollbar thumb color
|
scrollbarColor: string; // Scrollbar thumb color
|
||||||
|
|
|
||||||
2809
wwwroot/data/mock-events.json
Normal file
2809
wwwroot/data/mock-events.json
Normal file
File diff suppressed because it is too large
Load diff
135
wwwroot/data/mock-resource-events.json
Normal file
135
wwwroot/data/mock-resource-events.json
Normal file
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue