Removes singleton export for CalendarConfig Updates dependency injection to use type registration Adds more flexible renderer and service configuration Integrates CalendarConfig with DateService initialization
575 lines
No EOL
14 KiB
TypeScript
575 lines
No EOL
14 KiB
TypeScript
// Calendar configuration management
|
|
|
|
import { eventBus } from './EventBus';
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
|
import { CalendarConfig as ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes';
|
|
import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter';
|
|
|
|
/**
|
|
* All-day event layout constants
|
|
*/
|
|
export const ALL_DAY_CONSTANTS = {
|
|
EVENT_HEIGHT: 22, // Height of single all-day event
|
|
EVENT_GAP: 2, // Gap between stacked events
|
|
CONTAINER_PADDING: 4, // Container padding (top + bottom)
|
|
MAX_COLLAPSED_ROWS: 4, // Show 4 rows when collapsed (3 events + 1 indicator row)
|
|
get SINGLE_ROW_HEIGHT() {
|
|
return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px
|
|
}
|
|
} as const;
|
|
|
|
/**
|
|
* Layout and timing settings for the calendar grid
|
|
*/
|
|
interface GridSettings {
|
|
// Time boundaries
|
|
dayStartHour: number;
|
|
dayEndHour: number;
|
|
workStartHour: number;
|
|
workEndHour: number;
|
|
|
|
// Layout settings
|
|
hourHeight: number;
|
|
snapInterval: number;
|
|
fitToWidth: boolean;
|
|
scrollToHour: number | null;
|
|
|
|
// Event grouping settings
|
|
gridStartThresholdMinutes: number; // ±N minutes for events to share grid columns
|
|
|
|
// Display options
|
|
showCurrentTime: boolean;
|
|
showWorkHours: boolean;
|
|
}
|
|
|
|
/**
|
|
* View settings for date-based calendar mode
|
|
*/
|
|
interface DateViewSettings {
|
|
period: ViewPeriod; // day/week/month
|
|
weekDays: number; // Number of days to show in week view
|
|
firstDayOfWeek: number; // 0=Sunday, 1=Monday
|
|
showAllDay: boolean; // Show all-day event row
|
|
}
|
|
|
|
/**
|
|
* Work week configuration settings
|
|
*/
|
|
interface WorkWeekSettings {
|
|
id: string;
|
|
workDays: number[]; // ISO 8601: [1,2,3,4,5] for mon-fri (1=Mon, 7=Sun)
|
|
totalDays: number; // 5
|
|
firstWorkDay: number; // ISO: 1 = Monday, 7 = Sunday
|
|
}
|
|
|
|
/**
|
|
* View settings for resource-based calendar mode
|
|
*/
|
|
interface ResourceViewSettings {
|
|
maxResources: number; // Maximum resources to display
|
|
showAvatars: boolean; // Display user avatars
|
|
avatarSize: number; // Avatar size in pixels
|
|
resourceNameFormat: 'full' | 'short'; // How to display names
|
|
showResourceDetails: boolean; // Show additional resource info
|
|
showAllDay: boolean; // Show all-day event row
|
|
}
|
|
|
|
/**
|
|
* Time format configuration settings
|
|
*/
|
|
interface TimeFormatConfig {
|
|
timezone: string;
|
|
use24HourFormat: boolean;
|
|
locale: string;
|
|
dateFormat: 'locale' | 'technical';
|
|
showSeconds: boolean;
|
|
}
|
|
|
|
/**
|
|
* Calendar configuration management
|
|
*/
|
|
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;
|
|
|
|
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
|
|
};
|
|
|
|
// 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
|
|
};
|
|
|
|
// Date view settings
|
|
this.dateViewSettings = {
|
|
period: 'week',
|
|
weekDays: 7,
|
|
firstDayOfWeek: 1,
|
|
showAllDay: true
|
|
};
|
|
|
|
// Resource view settings
|
|
this.resourceViewSettings = {
|
|
maxResources: 10,
|
|
showAvatars: true,
|
|
avatarSize: 32,
|
|
resourceNameFormat: 'full',
|
|
showResourceDetails: true,
|
|
showAllDay: true
|
|
};
|
|
|
|
// Time format settings - default to Denmark with technical format
|
|
this.timeFormatConfig = {
|
|
timezone: 'Europe/Copenhagen',
|
|
use24HourFormat: true,
|
|
locale: 'da-DK',
|
|
dateFormat: 'technical',
|
|
showSeconds: false
|
|
};
|
|
|
|
// 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();
|
|
}
|
|
|
|
/**
|
|
* Load calendar type and date from URL parameters
|
|
*/
|
|
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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load configuration from DOM data attributes
|
|
*/
|
|
private 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);
|
|
|
|
// 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';
|
|
|
|
// Update computed values
|
|
this.config.minEventDuration = this.gridSettings.snapInterval;
|
|
}
|
|
|
|
/**
|
|
* Get a config value
|
|
*/
|
|
get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
|
|
return this.config[key];
|
|
}
|
|
|
|
/**
|
|
* Set a config value
|
|
*/
|
|
set<K extends keyof ICalendarConfig>(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
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update multiple config values
|
|
*/
|
|
update(updates: Partial<ICalendarConfig>): void {
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
this.set(key as keyof ICalendarConfig, value);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all config
|
|
*/
|
|
getAll(): ICalendarConfig {
|
|
return { ...this.config };
|
|
}
|
|
|
|
/**
|
|
* Calculate derived values
|
|
*/
|
|
|
|
get minuteHeight(): number {
|
|
return this.gridSettings.hourHeight / 60;
|
|
}
|
|
|
|
get totalHours(): number {
|
|
return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour;
|
|
}
|
|
|
|
get totalMinutes(): number {
|
|
return this.totalHours * 60;
|
|
}
|
|
|
|
get slotsPerHour(): number {
|
|
return 60 / this.gridSettings.snapInterval;
|
|
}
|
|
|
|
get totalSlots(): number {
|
|
return this.totalHours * this.slotsPerHour;
|
|
}
|
|
|
|
get slotHeight(): number {
|
|
return this.gridSettings.hourHeight / this.slotsPerHour;
|
|
}
|
|
|
|
/**
|
|
* Validate snap interval
|
|
*/
|
|
isValidSnapInterval(interval: number): boolean {
|
|
return [5, 10, 15, 30, 60].includes(interval);
|
|
}
|
|
|
|
/**
|
|
* Get grid display settings
|
|
*/
|
|
getGridSettings(): GridSettings {
|
|
return { ...this.gridSettings };
|
|
}
|
|
|
|
/**
|
|
* Update grid display settings
|
|
*/
|
|
updateGridSettings(updates: Partial<GridSettings>): void {
|
|
this.gridSettings = { ...this.gridSettings, ...updates };
|
|
|
|
// Update computed values
|
|
if (updates.snapInterval) {
|
|
this.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<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
|
|
*/
|
|
getResourceViewSettings(): ResourceViewSettings {
|
|
return { ...this.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
|
|
*/
|
|
getCalendarMode(): CalendarMode {
|
|
return this.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
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get selected date
|
|
*/
|
|
getSelectedDate(): Date | null {
|
|
return this.selectedDate;
|
|
}
|
|
|
|
/**
|
|
* Set selected date
|
|
* Note: Does not emit events - caller is responsible for event emission
|
|
*/
|
|
setSelectedDate(date: Date): void {
|
|
this.selectedDate = date;
|
|
}
|
|
|
|
/**
|
|
* Get work week presets
|
|
*/
|
|
private getWorkWeekPresets(): { [key: string]: WorkWeekSettings } {
|
|
return {
|
|
'standard': {
|
|
id: 'standard',
|
|
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
|
|
totalDays: 5,
|
|
firstWorkDay: 1
|
|
},
|
|
'compressed': {
|
|
id: 'compressed',
|
|
workDays: [1,2,3,4], // Monday-Thursday (ISO)
|
|
totalDays: 4,
|
|
firstWorkDay: 1
|
|
},
|
|
'midweek': {
|
|
id: 'midweek',
|
|
workDays: [3,4,5], // Wednesday-Friday (ISO)
|
|
totalDays: 3,
|
|
firstWorkDay: 3
|
|
},
|
|
'weekend': {
|
|
id: 'weekend',
|
|
workDays: [6,7], // Saturday-Sunday (ISO)
|
|
totalDays: 2,
|
|
firstWorkDay: 6
|
|
},
|
|
'fullweek': {
|
|
id: 'fullweek',
|
|
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
|
|
totalDays: 7,
|
|
firstWorkDay: 1
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get current work week settings
|
|
*/
|
|
getWorkWeekSettings(): WorkWeekSettings {
|
|
const presets = this.getWorkWeekPresets();
|
|
return presets[this.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();
|
|
if (presets[workWeekId]) {
|
|
this.currentWorkWeek = workWeekId;
|
|
|
|
// Update dateViewSettings to match work week
|
|
this.dateViewSettings.weekDays = presets[workWeekId].totalDays;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current work week ID
|
|
*/
|
|
getCurrentWorkWeek(): string {
|
|
return this.currentWorkWeek;
|
|
}
|
|
|
|
/**
|
|
* Get time format settings
|
|
*/
|
|
getTimeFormatSettings(): TimeFormatConfig {
|
|
return { ...this.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
|
|
*/
|
|
getTimezone(): string {
|
|
return this.timeFormatConfig.timezone;
|
|
}
|
|
|
|
/**
|
|
* Get configured locale
|
|
*/
|
|
getLocale(): string {
|
|
return this.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 });
|
|
}
|
|
|
|
/**
|
|
* Get current date format
|
|
*/
|
|
getDateFormat(): 'locale' | 'technical' {
|
|
return this.timeFormatConfig.dateFormat;
|
|
}
|
|
|
|
} |