Refactors dependency injection and configuration management
Replaces global singleton configuration with dependency injection Introduces more modular and testable approach to configuration Removes direct references to calendarConfig in multiple components Adds explicit configuration passing to constructors Improves code maintainability and reduces global state dependencies
This commit is contained in:
parent
fb48e410ea
commit
8bbb2f05d3
30 changed files with 365 additions and 559 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { TimeFormatter } from '../utils/TimeFormatter';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { DateService } from '../utils/DateService';
|
||||
|
|
@ -9,11 +9,13 @@ import { DateService } from '../utils/DateService';
|
|||
*/
|
||||
export abstract class BaseSwpEventElement extends HTMLElement {
|
||||
protected dateService: DateService;
|
||||
protected config: CalendarConfig;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
// TODO: Find better solution for web component DI
|
||||
this.config = new CalendarConfig();
|
||||
this.dateService = new DateService(this.config);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
|
@ -135,7 +137,7 @@ export class SwpEventElement extends BaseSwpEventElement {
|
|||
this.style.height = `${newHeight}px`;
|
||||
|
||||
// 2. Calculate new end time based on height
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const { hourHeight, snapInterval } = gridSettings;
|
||||
|
||||
// Get current start time
|
||||
|
|
@ -228,7 +230,7 @@ export class SwpEventElement extends BaseSwpEventElement {
|
|||
* Calculate start/end minutes from Y position
|
||||
*/
|
||||
private calculateTimesFromPosition(snappedY: number): { startMinutes: number; endMinutes: number } {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const { hourHeight, dayStartHour, snapInterval } = gridSettings;
|
||||
|
||||
// Get original duration
|
||||
|
|
@ -258,8 +260,8 @@ export class SwpEventElement extends BaseSwpEventElement {
|
|||
*/
|
||||
public static fromCalendarEvent(event: CalendarEvent): SwpEventElement {
|
||||
const element = document.createElement('swp-event') as SwpEventElement;
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
const dateService = new DateService(timezone);
|
||||
const config = new CalendarConfig();
|
||||
const dateService = new DateService(config);
|
||||
|
||||
element.dataset.eventId = event.id;
|
||||
element.dataset.title = event.title;
|
||||
|
|
@ -333,8 +335,8 @@ export class SwpAllDayEventElement extends BaseSwpEventElement {
|
|||
*/
|
||||
public static fromCalendarEvent(event: CalendarEvent): SwpAllDayEventElement {
|
||||
const element = document.createElement('swp-allday-event') as SwpAllDayEventElement;
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
const dateService = new DateService(timezone);
|
||||
const config = new CalendarConfig();
|
||||
const dateService = new DateService(config);
|
||||
|
||||
element.dataset.eventId = event.id;
|
||||
element.dataset.title = event.title;
|
||||
|
|
|
|||
30
src/index.ts
30
src/index.ts
|
|
@ -24,15 +24,25 @@ import { HeaderManager } from './managers/HeaderManager';
|
|||
import { DateHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer';
|
||||
import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
|
||||
import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer';
|
||||
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
|
||||
import { GridRenderer } from './renderers/GridRenderer';
|
||||
import { NavigationRenderer } from './renderers/NavigationRenderer';
|
||||
|
||||
// Import utilities and services
|
||||
import { DateService } from './utils/DateService';
|
||||
import { TimeFormatter } from './utils/TimeFormatter';
|
||||
import { PositionUtils } from './utils/PositionUtils';
|
||||
import { AllDayLayoutEngine } from './utils/AllDayLayoutEngine';
|
||||
import { WorkHoursManager } from './managers/WorkHoursManager';
|
||||
import { GridStyleManager } from './renderers/GridStyleManager';
|
||||
import { EventStackManager } from './managers/EventStackManager';
|
||||
import { EventLayoutCoordinator } from './managers/EventLayoutCoordinator';
|
||||
|
||||
/**
|
||||
* Handle deep linking functionality after managers are initialized
|
||||
*/
|
||||
async function handleDeepLinking(eventManager: EventManager): Promise<void> {
|
||||
async function handleDeepLinking(eventManager: EventManager, urlManager: URLManager): Promise<void> {
|
||||
try {
|
||||
const urlManager = new URLManager(eventBus);
|
||||
const eventId = urlManager.parseEventIdFromURL();
|
||||
|
||||
if (eventId) {
|
||||
|
|
@ -80,8 +90,19 @@ async function initializeCalendar(): Promise<void> {
|
|||
builder.registerType(DateColumnRenderer).as<ColumnRenderer>().keyed('resource');
|
||||
builder.registerType(DateEventRenderer).as<EventRendererStrategy>().keyed('resource');
|
||||
|
||||
// Register core services and utilities
|
||||
builder.registerType(DateService).as<DateService>().singleInstance();
|
||||
|
||||
builder.registerType(EventStackManager).as<EventStackManager>().singleInstance();
|
||||
builder.registerType(EventLayoutCoordinator).as<EventLayoutCoordinator>().singleInstance();
|
||||
builder.registerType(GridStyleManager).as<GridStyleManager>().singleInstance();
|
||||
builder.registerType(WorkHoursManager).as<WorkHoursManager>().singleInstance();
|
||||
builder.registerType(URLManager).as<URLManager>().singleInstance();
|
||||
builder.registerType(TimeFormatter).as<TimeFormatter>().singleInstance();
|
||||
builder.registerType(PositionUtils).as<PositionUtils>().singleInstance();
|
||||
// Note: AllDayLayoutEngine is instantiated per-operation with specific dates, not a singleton
|
||||
builder.registerType(NavigationRenderer).as<NavigationRenderer>().singleInstance();
|
||||
builder.registerType(AllDayEventRenderer).as<AllDayEventRenderer>().singleInstance();
|
||||
|
||||
builder.registerType(EventRenderingService).as<EventRenderingService>().singleInstance();
|
||||
builder.registerType(GridRenderer).as<GridRenderer>().singleInstance();
|
||||
builder.registerType(GridManager).as<GridManager>().singleInstance();
|
||||
|
|
@ -113,13 +134,14 @@ async function initializeCalendar(): Promise<void> {
|
|||
const edgeScrollManager = app.resolveType<EdgeScrollManager>();
|
||||
const dragHoverManager = app.resolveType<DragHoverManager>();
|
||||
const allDayManager = app.resolveType<AllDayManager>();
|
||||
const urlManager = app.resolveType<URLManager>();
|
||||
|
||||
// Initialize managers
|
||||
await calendarManager.initialize?.();
|
||||
await resizeHandleManager.initialize?.();
|
||||
|
||||
// Handle deep linking after managers are initialized
|
||||
await handleDeepLinking(eventManager);
|
||||
await handleDeepLinking(eventManager, urlManager);
|
||||
|
||||
// Expose to window for debugging (with proper typing)
|
||||
(window as Window & {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// All-day row height management and animations
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { ALL_DAY_CONSTANTS, calendarConfig } from '../core/CalendarConfig';
|
||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
|
|
@ -43,11 +43,14 @@ export class AllDayManager {
|
|||
private actualRowCount: number = 0;
|
||||
|
||||
|
||||
constructor(eventManager: EventManager) {
|
||||
constructor(
|
||||
eventManager: EventManager,
|
||||
allDayEventRenderer: AllDayEventRenderer,
|
||||
dateService: DateService
|
||||
) {
|
||||
this.eventManager = eventManager;
|
||||
this.allDayEventRenderer = new AllDayEventRenderer();
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
this.allDayEventRenderer = allDayEventRenderer;
|
||||
this.dateService = dateService;
|
||||
|
||||
// Sync CSS variable with TypeScript constant to ensure consistency
|
||||
document.documentElement.style.setProperty('--single-row-height', `${ALL_DAY_CONSTANTS.EVENT_HEIGHT}px`);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||
import { EventManager } from './EventManager';
|
||||
import { GridManager } from './GridManager';
|
||||
|
|
@ -8,7 +8,6 @@ import { ScrollManager } from './ScrollManager';
|
|||
|
||||
/**
|
||||
* CalendarManager - Main coordinator for all calendar managers
|
||||
* Uses singleton calendarConfig for consistent configuration access
|
||||
*/
|
||||
export class CalendarManager {
|
||||
private eventBus: IEventBus;
|
||||
|
|
@ -16,6 +15,7 @@ export class CalendarManager {
|
|||
private gridManager: GridManager;
|
||||
private eventRenderer: EventRenderingService;
|
||||
private scrollManager: ScrollManager;
|
||||
private config: CalendarConfig;
|
||||
private currentView: CalendarView = 'week';
|
||||
private currentDate: Date = new Date();
|
||||
private isInitialized: boolean = false;
|
||||
|
|
@ -25,14 +25,15 @@ export class CalendarManager {
|
|||
eventManager: EventManager,
|
||||
gridManager: GridManager,
|
||||
eventRenderingService: EventRenderingService,
|
||||
scrollManager: ScrollManager
|
||||
scrollManager: ScrollManager,
|
||||
config: CalendarConfig
|
||||
) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
this.gridManager = gridManager;
|
||||
this.eventRenderer = eventRenderingService;
|
||||
this.scrollManager = scrollManager;
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.config = config;
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ export class CalendarManager {
|
|||
|
||||
try {
|
||||
// Debug: Check calendar type
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const calendarType = this.config.getCalendarMode();
|
||||
|
||||
// Step 1: Load data
|
||||
await this.eventManager.loadData();
|
||||
|
|
@ -212,7 +213,7 @@ export class CalendarManager {
|
|||
this.eventBus.emit('workweek:header-update', {
|
||||
currentDate: this.currentDate,
|
||||
currentView: this.currentView,
|
||||
workweek: calendarConfig.getCurrentWorkWeek()
|
||||
workweek: this.config.getCurrentWorkWeek()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
|
||||
|
|
@ -49,9 +48,11 @@ export class DragDropManager {
|
|||
private targetY = 0;
|
||||
private currentY = 0;
|
||||
private targetColumn: ColumnBounds | null = null;
|
||||
private positionUtils: PositionUtils;
|
||||
|
||||
constructor(eventBus: IEventBus) {
|
||||
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
|
||||
this.eventBus = eventBus;
|
||||
this.positionUtils = positionUtils;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
|
@ -415,7 +416,7 @@ export class DragDropManager {
|
|||
const eventTopY = mouseY - this.mouseOffset.y;
|
||||
|
||||
// Snap the event top position, not the mouse position
|
||||
const snappedY = PositionUtils.getPositionFromCoordinate(eventTopY, column);
|
||||
const snappedY = this.positionUtils.getPositionFromCoordinate(eventTopY, column);
|
||||
|
||||
return Math.max(0, snappedY);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { EventStackManager, EventGroup, StackLink } from './EventStackManager';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
|
||||
export interface GridGroupLayout {
|
||||
events: CalendarEvent[];
|
||||
|
|
@ -30,9 +30,13 @@ export interface ColumnLayout {
|
|||
|
||||
export class EventLayoutCoordinator {
|
||||
private stackManager: EventStackManager;
|
||||
private config: CalendarConfig;
|
||||
private positionUtils: PositionUtils;
|
||||
|
||||
constructor() {
|
||||
this.stackManager = new EventStackManager();
|
||||
constructor(stackManager: EventStackManager, config: CalendarConfig, positionUtils: PositionUtils) {
|
||||
this.stackManager = stackManager;
|
||||
this.config = config;
|
||||
this.positionUtils = positionUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -55,7 +59,7 @@ export class EventLayoutCoordinator {
|
|||
|
||||
// Find events that could be in GRID with first event
|
||||
// Use expanding search to find chains (A→B→C where each conflicts with next)
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const thresholdMinutes = gridSettings.gridStartThresholdMinutes;
|
||||
|
||||
// Use refactored method for expanding grid candidates
|
||||
|
|
@ -78,7 +82,7 @@ export class EventLayoutCoordinator {
|
|||
|
||||
// Ensure we get the earliest event (explicit sort for robustness)
|
||||
const earliestEvent = [...gridCandidates].sort((a, b) => a.start.getTime() - b.start.getTime())[0];
|
||||
const position = PositionUtils.calculateEventPosition(earliestEvent.start, earliestEvent.end);
|
||||
const position = this.positionUtils.calculateEventPosition(earliestEvent.start, earliestEvent.end);
|
||||
const columns = this.allocateColumns(gridCandidates);
|
||||
|
||||
gridGroupLayouts.push({
|
||||
|
|
@ -100,7 +104,7 @@ export class EventLayoutCoordinator {
|
|||
renderedEventsWithLevels
|
||||
);
|
||||
|
||||
const position = PositionUtils.calculateEventPosition(firstEvent.start, firstEvent.end);
|
||||
const position = this.positionUtils.calculateEventPosition(firstEvent.start, firstEvent.end);
|
||||
stackedEventLayouts.push({
|
||||
event: firstEvent,
|
||||
stackLink: { stackLevel },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { DateService } from '../utils/DateService';
|
||||
import { ResourceData } from '../types/ManagerTypes';
|
||||
|
||||
|
|
@ -20,16 +20,21 @@ interface RawEventData {
|
|||
* Handles data loading with improved performance and caching
|
||||
*/
|
||||
export class EventManager {
|
||||
|
||||
|
||||
private events: CalendarEvent[] = [];
|
||||
private rawData: ResourceCalendarData | RawEventData[] | null = null;
|
||||
private eventCache = new Map<string, CalendarEvent[]>(); // Cache for period queries
|
||||
private lastCacheKey: string = '';
|
||||
private dateService: DateService;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(private eventBus: IEventBus) {
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
constructor(
|
||||
private eventBus: IEventBus,
|
||||
dateService: DateService,
|
||||
config: CalendarConfig
|
||||
) {
|
||||
this.dateService = dateService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,7 +55,7 @@ export class EventManager {
|
|||
* Optimized mock data loading with better resource handling
|
||||
*/
|
||||
private async loadMockData(): Promise<void> {
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const calendarType = this.config.getCalendarMode();
|
||||
const jsonFile = calendarType === 'resource'
|
||||
? '/src/data/mock-resource-events.json'
|
||||
: '/src/data/mock-events.json';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
|
||||
export interface StackLink {
|
||||
prev?: string; // Event ID of previous event in stack
|
||||
|
|
@ -30,6 +30,11 @@ export interface EventGroup {
|
|||
|
||||
export class EventStackManager {
|
||||
private static readonly STACK_OFFSET_PX = 15;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(config: CalendarConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PHASE 1: Start Time Grouping
|
||||
|
|
@ -46,7 +51,7 @@ export class EventStackManager {
|
|||
if (events.length === 0) return [];
|
||||
|
||||
// Get threshold from config
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const thresholdMinutes = gridSettings.gridStartThresholdMinutes;
|
||||
|
||||
// Sort events by start time
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
||||
import { GridRenderer } from '../renderers/GridRenderer';
|
||||
|
|
@ -23,11 +22,14 @@ export class GridManager {
|
|||
private styleManager: GridStyleManager;
|
||||
private dateService: DateService;
|
||||
|
||||
constructor(gridRenderer: GridRenderer) {
|
||||
// Inject GridRenderer via DI
|
||||
constructor(
|
||||
gridRenderer: GridRenderer,
|
||||
styleManager: GridStyleManager,
|
||||
dateService: DateService
|
||||
) {
|
||||
this.gridRenderer = gridRenderer;
|
||||
this.styleManager = new GridStyleManager();
|
||||
this.dateService = new DateService('Europe/Copenhagen');
|
||||
this.styleManager = styleManager;
|
||||
this.dateService = dateService;
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { HeaderRenderer, HeaderRenderContext } from '../renderers/HeaderRenderer';
|
||||
import { ResourceCalendarData } from '../types/CalendarTypes';
|
||||
|
|
@ -13,9 +13,11 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
|||
*/
|
||||
export class HeaderManager {
|
||||
private headerRenderer: HeaderRenderer;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(headerRenderer: HeaderRenderer) {
|
||||
constructor(headerRenderer: HeaderRenderer, config: CalendarConfig) {
|
||||
this.headerRenderer = headerRenderer;
|
||||
this.config = config;
|
||||
|
||||
// Bind handler methods for event listeners
|
||||
this.handleDragMouseEnterHeader = this.handleDragMouseEnterHeader.bind(this);
|
||||
|
|
@ -127,7 +129,7 @@ export class HeaderManager {
|
|||
// Render new header content using injected renderer
|
||||
const context: HeaderRenderContext = {
|
||||
currentWeek: currentDate,
|
||||
config: calendarConfig,
|
||||
config: this.config,
|
||||
resourceData: resourceData
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { DateService } from '../utils/DateService';
|
|||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { NavigationRenderer } from '../renderers/NavigationRenderer';
|
||||
import { GridRenderer } from '../renderers/GridRenderer';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
|
||||
/**
|
||||
* NavigationManager handles calendar navigation (prev/next/today buttons)
|
||||
|
|
@ -19,10 +18,16 @@ export class NavigationManager {
|
|||
private targetWeek: Date;
|
||||
private animationQueue: number = 0;
|
||||
|
||||
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService, gridRenderer: GridRenderer) {
|
||||
constructor(
|
||||
eventBus: IEventBus,
|
||||
eventRenderer: EventRenderingService,
|
||||
gridRenderer: GridRenderer,
|
||||
dateService: DateService,
|
||||
navigationRenderer: NavigationRenderer
|
||||
) {
|
||||
this.eventBus = eventBus;
|
||||
this.dateService = new DateService('Europe/Copenhagen');
|
||||
this.navigationRenderer = new NavigationRenderer(eventBus, eventRenderer);
|
||||
this.dateService = dateService;
|
||||
this.navigationRenderer = navigationRenderer;
|
||||
this.gridRenderer = gridRenderer;
|
||||
this.currentWeek = this.getISOWeekStart(new Date());
|
||||
this.targetWeek = new Date(this.currentWeek);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ResizeEndEventPayload } from '../types/EventTypes';
|
||||
|
||||
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
||||
|
|
@ -29,9 +29,11 @@ export class ResizeHandleManager {
|
|||
private unsubscribers: Array<() => void> = [];
|
||||
private pointerCaptured = false;
|
||||
private prevZ?: string;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor() {
|
||||
const grid = calendarConfig.getGridSettings();
|
||||
constructor(config: CalendarConfig) {
|
||||
this.config = config;
|
||||
const grid = this.config.getGridSettings();
|
||||
this.hourHeightPx = grid.hourHeight;
|
||||
this.snapMin = grid.snapInterval;
|
||||
this.minDurationMin = this.snapMin; // Use snap interval as minimum duration
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Custom scroll management for calendar week container
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
|
|
@ -14,8 +13,10 @@ export class ScrollManager {
|
|||
private timeAxis: HTMLElement | null = null;
|
||||
private calendarHeader: HTMLElement | null = null;
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
private positionUtils: PositionUtils;
|
||||
|
||||
constructor() {
|
||||
constructor(positionUtils: PositionUtils) {
|
||||
this.positionUtils = positionUtils;
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +113,7 @@ export class ScrollManager {
|
|||
scrollToHour(hour: number): void {
|
||||
// Create time string for the hour
|
||||
const timeString = `${hour.toString().padStart(2, '0')}:00`;
|
||||
const scrollTop = PositionUtils.timeToPixels(timeString);
|
||||
const scrollTop = this.positionUtils.timeToPixels(timeString);
|
||||
|
||||
this.scrollTo(scrollTop);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { EventBus } from '../core/EventBus';
|
||||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
|
||||
/**
|
||||
|
|
@ -9,17 +9,19 @@ import { CoreEvents } from '../constants/CoreEvents';
|
|||
*/
|
||||
export class ViewManager {
|
||||
private eventBus: IEventBus;
|
||||
private config: CalendarConfig;
|
||||
private currentView: CalendarView = 'week';
|
||||
private buttonListeners: Map<Element, EventListener> = new Map();
|
||||
|
||||
|
||||
// Cached DOM elements for performance
|
||||
private cachedViewButtons: NodeListOf<Element> | null = null;
|
||||
private cachedWorkweekButtons: NodeListOf<Element> | null = null;
|
||||
private lastButtonCacheTime: number = 0;
|
||||
private readonly CACHE_DURATION = 5000; // 5 seconds
|
||||
|
||||
constructor(eventBus: IEventBus) {
|
||||
constructor(eventBus: IEventBus, config: CalendarConfig) {
|
||||
this.eventBus = eventBus;
|
||||
this.config = config;
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
|
|
@ -140,13 +142,13 @@ export class ViewManager {
|
|||
*/
|
||||
private changeWorkweek(workweekId: string): void {
|
||||
// Update the calendar config (does not emit events)
|
||||
calendarConfig.setWorkWeek(workweekId);
|
||||
this.config.setWorkWeek(workweekId);
|
||||
|
||||
// Update button states using cached elements
|
||||
this.updateAllButtons();
|
||||
|
||||
// Emit workweek change event with full payload
|
||||
const settings = calendarConfig.getWorkWeekSettings();
|
||||
const settings = this.config.getWorkWeekSettings();
|
||||
this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, {
|
||||
workWeekId: workweekId,
|
||||
settings: settings
|
||||
|
|
@ -166,7 +168,7 @@ export class ViewManager {
|
|||
this.updateButtonGroup(
|
||||
this.getWorkweekButtons(),
|
||||
'data-workweek',
|
||||
calendarConfig.getCurrentWorkWeek()
|
||||
this.config.getCurrentWorkWeek()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Work hours management for per-column scheduling
|
||||
|
||||
import { DateService } from '../utils/DateService';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
/**
|
||||
|
|
@ -35,11 +35,14 @@ export interface WorkScheduleConfig {
|
|||
*/
|
||||
export class WorkHoursManager {
|
||||
private dateService: DateService;
|
||||
private config: CalendarConfig;
|
||||
private positionUtils: PositionUtils;
|
||||
private workSchedule: WorkScheduleConfig;
|
||||
|
||||
constructor() {
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
constructor(dateService: DateService, config: CalendarConfig, positionUtils: PositionUtils) {
|
||||
this.dateService = dateService;
|
||||
this.config = config;
|
||||
this.positionUtils = positionUtils;
|
||||
|
||||
// Default work schedule - will be loaded from JSON later
|
||||
this.workSchedule = {
|
||||
|
|
@ -98,8 +101,8 @@ export class WorkHoursManager {
|
|||
if (workHours === 'off') {
|
||||
return null; // Full day will be colored via CSS background
|
||||
}
|
||||
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const dayStartHour = gridSettings.dayStartHour;
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
|
||||
|
|
@ -128,7 +131,7 @@ export class WorkHoursManager {
|
|||
const endTime = `${workHours.end.toString().padStart(2, '0')}:00`;
|
||||
|
||||
// Use PositionUtils for consistent position calculation
|
||||
const position = PositionUtils.calculateEventPosition(startTime, endTime);
|
||||
const position = this.positionUtils.calculateEventPosition(startTime, endTime);
|
||||
|
||||
return { top: position.top, height: position.height };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,17 +25,20 @@ export interface ColumnRenderContext {
|
|||
* Date-based column renderer (original functionality)
|
||||
*/
|
||||
export class DateColumnRenderer implements ColumnRenderer {
|
||||
private dateService!: DateService;
|
||||
private workHoursManager!: WorkHoursManager;
|
||||
private dateService: DateService;
|
||||
private workHoursManager: WorkHoursManager;
|
||||
|
||||
constructor(
|
||||
dateService: DateService,
|
||||
workHoursManager: WorkHoursManager
|
||||
) {
|
||||
this.dateService = dateService;
|
||||
this.workHoursManager = workHoursManager;
|
||||
}
|
||||
|
||||
render(columnContainer: HTMLElement, context: ColumnRenderContext): void {
|
||||
const { currentWeek, config } = context;
|
||||
|
||||
// Initialize date service and work hours manager
|
||||
const timezone = config.getTimezone?.() || 'Europe/Copenhagen';
|
||||
this.dateService = new DateService(timezone);
|
||||
this.workHoursManager = new WorkHoursManager();
|
||||
|
||||
const workWeekSettings = config.getWorkWeekSettings();
|
||||
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
|
||||
const dateSettings = config.getDateViewSettings();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Event rendering strategy interface and implementations
|
||||
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||
|
|
@ -34,14 +34,23 @@ export class DateEventRenderer implements EventRendererStrategy {
|
|||
private dateService: DateService;
|
||||
private stackManager: EventStackManager;
|
||||
private layoutCoordinator: EventLayoutCoordinator;
|
||||
private config: CalendarConfig;
|
||||
private positionUtils: PositionUtils;
|
||||
private draggedClone: HTMLElement | null = null;
|
||||
private originalEvent: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
this.stackManager = new EventStackManager();
|
||||
this.layoutCoordinator = new EventLayoutCoordinator();
|
||||
constructor(
|
||||
dateService: DateService,
|
||||
stackManager: EventStackManager,
|
||||
layoutCoordinator: EventLayoutCoordinator,
|
||||
config: CalendarConfig,
|
||||
positionUtils: PositionUtils
|
||||
) {
|
||||
this.dateService = dateService;
|
||||
this.stackManager = stackManager;
|
||||
this.layoutCoordinator = layoutCoordinator;
|
||||
this.config = config;
|
||||
this.positionUtils = positionUtils;
|
||||
}
|
||||
|
||||
private applyDragStyling(element: HTMLElement): void {
|
||||
|
|
@ -303,7 +312,7 @@ export class DateEventRenderer implements EventRendererStrategy {
|
|||
// (e.g., if container starts at 07:00 and event starts at 08:15, offset = 75 min)
|
||||
const timeDiffMs = event.start.getTime() - containerStart.getTime();
|
||||
const timeDiffMinutes = timeDiffMs / (1000 * 60);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const relativeTop = timeDiffMinutes > 0 ? (timeDiffMinutes / 60) * gridSettings.hourHeight : 0;
|
||||
|
||||
// Events in grid columns are positioned absolutely within their column container
|
||||
|
|
@ -333,7 +342,7 @@ export class DateEventRenderer implements EventRendererStrategy {
|
|||
|
||||
protected calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
|
||||
// Delegate to PositionUtils for centralized position calculation
|
||||
return PositionUtils.calculateEventPosition(event.start, event.end);
|
||||
return this.positionUtils.calculateEventPosition(event.start, event.end);
|
||||
}
|
||||
|
||||
clearEvents(container?: HTMLElement): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { EventBus } from '../core/EventBus';
|
||||
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { EventManager } from '../managers/EventManager';
|
||||
import { EventRendererStrategy } from './EventRenderer';
|
||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||
|
|
@ -20,14 +19,16 @@ export class EventRenderingService {
|
|||
|
||||
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
|
||||
|
||||
constructor(eventBus: IEventBus, eventManager: EventManager, strategy: EventRendererStrategy) {
|
||||
constructor(
|
||||
eventBus: IEventBus,
|
||||
eventManager: EventManager,
|
||||
strategy: EventRendererStrategy,
|
||||
dateService: DateService
|
||||
) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
this.strategy = strategy;
|
||||
|
||||
// Initialize DateService
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
this.dateService = dateService;
|
||||
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
||||
import { ColumnRenderer, ColumnRenderContext } from './ColumnRenderer';
|
||||
import { eventBus } from '../core/EventBus';
|
||||
|
|
@ -15,11 +15,16 @@ export class GridRenderer {
|
|||
private cachedTimeAxis: HTMLElement | null = null;
|
||||
private dateService: DateService;
|
||||
private columnRenderer: ColumnRenderer;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(columnRenderer: ColumnRenderer) {
|
||||
const timezone = calendarConfig.getTimezone?.();
|
||||
this.dateService = new DateService(timezone);
|
||||
constructor(
|
||||
columnRenderer: ColumnRenderer,
|
||||
dateService: DateService,
|
||||
config: CalendarConfig
|
||||
) {
|
||||
this.dateService = dateService;
|
||||
this.columnRenderer = columnRenderer;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public renderGrid(
|
||||
|
|
@ -80,7 +85,7 @@ export class GridRenderer {
|
|||
private createOptimizedTimeAxis(): HTMLElement {
|
||||
const timeAxis = document.createElement('swp-time-axis');
|
||||
const timeAxisContent = document.createElement('swp-time-axis-content');
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const startHour = gridSettings.dayStartHour;
|
||||
const endHour = gridSettings.dayEndHour;
|
||||
|
||||
|
|
@ -142,7 +147,7 @@ export class GridRenderer {
|
|||
): void {
|
||||
const context: ColumnRenderContext = {
|
||||
currentWeek: currentDate, // ColumnRenderer expects currentWeek property
|
||||
config: calendarConfig,
|
||||
config: this.config,
|
||||
resourceData: resourceData
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ResourceCalendarData } from '../types/CalendarTypes';
|
||||
|
||||
interface GridSettings {
|
||||
|
|
@ -16,7 +16,10 @@ interface GridSettings {
|
|||
* Separated from GridManager to follow Single Responsibility Principle
|
||||
*/
|
||||
export class GridStyleManager {
|
||||
constructor() {
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(config: CalendarConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -24,9 +27,9 @@ export class GridStyleManager {
|
|||
*/
|
||||
public updateGridStyles(resourceData: ResourceCalendarData | null = null): void {
|
||||
const root = document.documentElement;
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const calendarType = this.config.getCalendarMode();
|
||||
|
||||
// Set CSS variables for time and grid measurements
|
||||
this.setTimeVariables(root, gridSettings);
|
||||
|
|
@ -66,9 +69,9 @@ export class GridStyleManager {
|
|||
if (calendarType === 'resource' && resourceData) {
|
||||
return resourceData.resources.length;
|
||||
} else if (calendarType === 'date') {
|
||||
const dateSettings = calendarConfig.getDateViewSettings();
|
||||
const workWeekSettings = calendarConfig.getWorkWeekSettings();
|
||||
|
||||
const dateSettings = this.config.getDateViewSettings();
|
||||
const workWeekSettings = this.config.getWorkWeekSettings();
|
||||
|
||||
switch (dateSettings.period) {
|
||||
case 'day':
|
||||
return 1;
|
||||
|
|
@ -80,8 +83,8 @@ export class GridStyleManager {
|
|||
return workWeekSettings.totalDays;
|
||||
}
|
||||
}
|
||||
|
||||
return calendarConfig.getWorkWeekSettings().totalDays; // Default to work week
|
||||
|
||||
return this.config.getWorkWeekSettings().totalDays; // Default to work week
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class DateHeaderRenderer implements HeaderRenderer {
|
|||
// Initialize date service with timezone and locale from config
|
||||
const timezone = config.getTimezone?.() || 'Europe/Copenhagen';
|
||||
const locale = config.getLocale?.() || 'da-DK';
|
||||
this.dateService = new DateService(timezone);
|
||||
this.dateService = new DateService(config);
|
||||
|
||||
const workWeekSettings = config.getWorkWeekSettings();
|
||||
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ColumnBounds } from './ColumnDetectionUtils';
|
||||
import { DateService } from './DateService';
|
||||
import { TimeFormatter } from './TimeFormatter';
|
||||
|
||||
/**
|
||||
* PositionUtils - Static positioning utilities using singleton calendarConfig
|
||||
* PositionUtils - Positioning utilities with dependency injection
|
||||
* Focuses on pixel/position calculations while delegating date operations
|
||||
*
|
||||
* Note: Uses DateService with date-fns for all date/time operations
|
||||
*/
|
||||
export class PositionUtils {
|
||||
private static dateService = new DateService('Europe/Copenhagen');
|
||||
|
||||
private dateService: DateService;
|
||||
private config: CalendarConfig;
|
||||
|
||||
constructor(dateService: DateService, config: CalendarConfig) {
|
||||
this.dateService = dateService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert minutes to pixels
|
||||
*/
|
||||
public static minutesToPixels(minutes: number): number {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public minutesToPixels(minutes: number): number {
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const pixelsPerHour = gridSettings.hourHeight;
|
||||
return (minutes / 60) * pixelsPerHour;
|
||||
}
|
||||
|
|
@ -24,8 +30,8 @@ export class PositionUtils {
|
|||
/**
|
||||
* Convert pixels to minutes
|
||||
*/
|
||||
public static pixelsToMinutes(pixels: number): number {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public pixelsToMinutes(pixels: number): number {
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const pixelsPerHour = gridSettings.hourHeight;
|
||||
return (pixels / pixelsPerHour) * 60;
|
||||
}
|
||||
|
|
@ -33,43 +39,43 @@ export class PositionUtils {
|
|||
/**
|
||||
* Convert time (HH:MM) to pixels from day start using DateService
|
||||
*/
|
||||
public static timeToPixels(timeString: string): number {
|
||||
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public timeToPixels(timeString: string): number {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
||||
|
||||
return PositionUtils.minutesToPixels(minutesFromDayStart);
|
||||
|
||||
return this.minutesToPixels(minutesFromDayStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date object to pixels from day start using DateService
|
||||
*/
|
||||
public static dateToPixels(date: Date): number {
|
||||
const totalMinutes = PositionUtils.dateService.getMinutesSinceMidnight(date);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public dateToPixels(date: Date): number {
|
||||
const totalMinutes = this.dateService.getMinutesSinceMidnight(date);
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
||||
|
||||
return PositionUtils.minutesToPixels(minutesFromDayStart);
|
||||
|
||||
return this.minutesToPixels(minutesFromDayStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert pixels to time using DateService
|
||||
*/
|
||||
public static pixelsToTime(pixels: number): string {
|
||||
const minutes = PositionUtils.pixelsToMinutes(pixels);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public pixelsToTime(pixels: number): string {
|
||||
const minutes = this.pixelsToMinutes(pixels);
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const totalMinutes = dayStartMinutes + minutes;
|
||||
|
||||
return PositionUtils.dateService.minutesToTime(totalMinutes);
|
||||
|
||||
return this.dateService.minutesToTime(totalMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beregn event position og størrelse
|
||||
*/
|
||||
public static calculateEventPosition(startTime: string | Date, endTime: string | Date): {
|
||||
public calculateEventPosition(startTime: string | Date, endTime: string | Date): {
|
||||
top: number;
|
||||
height: number;
|
||||
duration: number;
|
||||
|
|
@ -78,19 +84,19 @@ export class PositionUtils {
|
|||
let endPixels: number;
|
||||
|
||||
if (typeof startTime === 'string') {
|
||||
startPixels = PositionUtils.timeToPixels(startTime);
|
||||
startPixels = this.timeToPixels(startTime);
|
||||
} else {
|
||||
startPixels = PositionUtils.dateToPixels(startTime);
|
||||
startPixels = this.dateToPixels(startTime);
|
||||
}
|
||||
|
||||
if (typeof endTime === 'string') {
|
||||
endPixels = PositionUtils.timeToPixels(endTime);
|
||||
endPixels = this.timeToPixels(endTime);
|
||||
} else {
|
||||
endPixels = PositionUtils.dateToPixels(endTime);
|
||||
endPixels = this.dateToPixels(endTime);
|
||||
}
|
||||
|
||||
const height = Math.max(endPixels - startPixels, PositionUtils.getMinimumEventHeight());
|
||||
const duration = PositionUtils.pixelsToMinutes(height);
|
||||
const height = Math.max(endPixels - startPixels, this.getMinimumEventHeight());
|
||||
const duration = this.pixelsToMinutes(height);
|
||||
|
||||
return {
|
||||
top: startPixels,
|
||||
|
|
@ -102,40 +108,40 @@ export class PositionUtils {
|
|||
/**
|
||||
* Snap position til grid interval
|
||||
*/
|
||||
public static snapToGrid(pixels: number): number {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public snapToGrid(pixels: number): number {
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const snapInterval = gridSettings.snapInterval;
|
||||
const snapPixels = PositionUtils.minutesToPixels(snapInterval);
|
||||
|
||||
const snapPixels = this.minutesToPixels(snapInterval);
|
||||
|
||||
return Math.round(pixels / snapPixels) * snapPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap time to interval using DateService
|
||||
*/
|
||||
public static snapTimeToInterval(timeString: string): string {
|
||||
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public snapTimeToInterval(timeString: string): string {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const snapInterval = gridSettings.snapInterval;
|
||||
|
||||
|
||||
const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval;
|
||||
return PositionUtils.dateService.minutesToTime(snappedMinutes);
|
||||
return this.dateService.minutesToTime(snappedMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beregn kolonne position for overlappende events
|
||||
*/
|
||||
public static calculateColumnPosition(eventIndex: number, totalColumns: number, containerWidth: number): {
|
||||
public calculateColumnPosition(eventIndex: number, totalColumns: number, containerWidth: number): {
|
||||
left: number;
|
||||
width: number;
|
||||
} {
|
||||
const columnWidth = containerWidth / totalColumns;
|
||||
const left = eventIndex * columnWidth;
|
||||
|
||||
|
||||
// Lav lidt margin mellem kolonnerne
|
||||
const margin = 2;
|
||||
const adjustedWidth = columnWidth - margin;
|
||||
|
||||
|
||||
return {
|
||||
left: left + (margin / 2),
|
||||
width: Math.max(adjustedWidth, 50) // Minimum width
|
||||
|
|
@ -145,63 +151,63 @@ export class PositionUtils {
|
|||
/**
|
||||
* Check om to events overlapper i tid
|
||||
*/
|
||||
public static eventsOverlap(
|
||||
public eventsOverlap(
|
||||
start1: string | Date,
|
||||
end1: string | Date,
|
||||
start2: string | Date,
|
||||
end2: string | Date
|
||||
): boolean {
|
||||
const pos1 = PositionUtils.calculateEventPosition(start1, end1);
|
||||
const pos2 = PositionUtils.calculateEventPosition(start2, end2);
|
||||
|
||||
const pos1 = this.calculateEventPosition(start1, end1);
|
||||
const pos2 = this.calculateEventPosition(start2, end2);
|
||||
|
||||
const event1End = pos1.top + pos1.height;
|
||||
const event2End = pos2.top + pos2.height;
|
||||
|
||||
|
||||
return !(event1End <= pos2.top || event2End <= pos1.top);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beregn Y position fra mouse/touch koordinat
|
||||
*/
|
||||
public static getPositionFromCoordinate(clientY: number, column: ColumnBounds): number {
|
||||
|
||||
public getPositionFromCoordinate(clientY: number, column: ColumnBounds): number {
|
||||
|
||||
const relativeY = clientY - column.boundingClientRect.top;
|
||||
|
||||
|
||||
// Snap til grid
|
||||
return PositionUtils.snapToGrid(relativeY);
|
||||
return this.snapToGrid(relativeY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valider at tid er inden for arbejdstimer
|
||||
*/
|
||||
public static isWithinWorkHours(timeString: string): boolean {
|
||||
public isWithinWorkHours(timeString: string): boolean {
|
||||
const [hours] = timeString.split(':').map(Number);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
return hours >= gridSettings.workStartHour && hours < gridSettings.workEndHour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valider at tid er inden for dag grænser
|
||||
*/
|
||||
public static isWithinDayBounds(timeString: string): boolean {
|
||||
public isWithinDayBounds(timeString: string): boolean {
|
||||
const [hours] = timeString.split(':').map(Number);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
return hours >= gridSettings.dayStartHour && hours < gridSettings.dayEndHour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hent minimum event højde i pixels
|
||||
*/
|
||||
public static getMinimumEventHeight(): number {
|
||||
public getMinimumEventHeight(): number {
|
||||
// Minimum 15 minutter
|
||||
return PositionUtils.minutesToPixels(15);
|
||||
return this.minutesToPixels(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hent maksimum event højde i pixels (hele dagen)
|
||||
*/
|
||||
public static getMaximumEventHeight(): number {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
public getMaximumEventHeight(): number {
|
||||
const gridSettings = this.config.getGridSettings();
|
||||
const dayDurationHours = gridSettings.dayEndHour - gridSettings.dayStartHour;
|
||||
return dayDurationHours * gridSettings.hourHeight;
|
||||
}
|
||||
|
|
@ -209,14 +215,14 @@ export class PositionUtils {
|
|||
/**
|
||||
* Beregn total kalender højde
|
||||
*/
|
||||
public static getTotalCalendarHeight(): number {
|
||||
return PositionUtils.getMaximumEventHeight();
|
||||
public getTotalCalendarHeight(): number {
|
||||
return this.getMaximumEventHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ISO datetime to time string with UTC-to-local conversion
|
||||
*/
|
||||
public static isoToTimeString(isoString: string): string {
|
||||
public isoToTimeString(isoString: string): string {
|
||||
const date = new Date(isoString);
|
||||
return TimeFormatter.formatTime(date);
|
||||
}
|
||||
|
|
@ -224,34 +230,34 @@ export class PositionUtils {
|
|||
/**
|
||||
* Convert time string to ISO datetime using DateService with timezone handling
|
||||
*/
|
||||
public static timeStringToIso(timeString: string, date: Date = new Date()): string {
|
||||
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
|
||||
const newDate = PositionUtils.dateService.createDateAtTime(date, totalMinutes);
|
||||
return PositionUtils.dateService.toUTC(newDate);
|
||||
public timeStringToIso(timeString: string, date: Date = new Date()): string {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const newDate = this.dateService.createDateAtTime(date, totalMinutes);
|
||||
return this.dateService.toUTC(newDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate event duration using DateService
|
||||
*/
|
||||
public static calculateDuration(startTime: string | Date, endTime: string | Date): number {
|
||||
return PositionUtils.dateService.getDurationMinutes(startTime, endTime);
|
||||
public calculateDuration(startTime: string | Date, endTime: string | Date): number {
|
||||
return this.dateService.getDurationMinutes(startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration to readable text (Danish)
|
||||
*/
|
||||
public static formatDuration(minutes: number): string {
|
||||
public formatDuration(minutes: number): string {
|
||||
if (minutes < 60) {
|
||||
return `${minutes} min`;
|
||||
}
|
||||
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainingMinutes = minutes % 60;
|
||||
|
||||
|
||||
if (remainingMinutes === 0) {
|
||||
return `${hours} time${hours !== 1 ? 'r' : ''}`;
|
||||
}
|
||||
|
||||
|
||||
return `${hours}t ${remainingMinutes}m`;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,74 +27,52 @@ export class TimeFormatter {
|
|||
showSeconds: false // Don't show seconds by default
|
||||
};
|
||||
|
||||
private static dateService: DateService = new DateService('Europe/Copenhagen');
|
||||
// DateService will be initialized lazily to avoid circular dependency with CalendarConfig
|
||||
private static dateService: DateService | null = null;
|
||||
|
||||
private static getDateService(): DateService {
|
||||
if (!TimeFormatter.dateService) {
|
||||
// Create a minimal config object for DateService
|
||||
const config = {
|
||||
getTimezone: () => TimeFormatter.settings.timezone
|
||||
};
|
||||
TimeFormatter.dateService = new DateService(config as any);
|
||||
}
|
||||
return TimeFormatter.dateService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure time formatting settings
|
||||
*/
|
||||
static configure(settings: Partial<TimeFormatSettings>): void {
|
||||
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
|
||||
// Update DateService with new timezone
|
||||
TimeFormatter.dateService = new DateService(TimeFormatter.settings.timezone);
|
||||
// Reset DateService to pick up new timezone
|
||||
TimeFormatter.dateService = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current time format settings
|
||||
*/
|
||||
static getSettings(): TimeFormatSettings {
|
||||
return { ...TimeFormatter.settings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UTC date to configured timezone
|
||||
* Convert UTC date to configured timezone (internal helper)
|
||||
* @param utcDate - Date in UTC (or ISO string)
|
||||
* @returns Date object adjusted to configured timezone
|
||||
*/
|
||||
static convertToLocalTime(utcDate: Date | string): Date {
|
||||
private static convertToLocalTime(utcDate: Date | string): Date {
|
||||
if (typeof utcDate === 'string') {
|
||||
return TimeFormatter.dateService.fromUTC(utcDate);
|
||||
return TimeFormatter.getDateService().fromUTC(utcDate);
|
||||
}
|
||||
|
||||
|
||||
// If it's already a Date object, convert to UTC string first, then back to local
|
||||
const utcString = utcDate.toISOString();
|
||||
return TimeFormatter.dateService.fromUTC(utcString);
|
||||
return TimeFormatter.getDateService().fromUTC(utcString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offset for configured timezone
|
||||
* @param date - Reference date for calculating offset (handles DST)
|
||||
* @returns Offset in minutes
|
||||
*/
|
||||
static getTimezoneOffset(date: Date = new Date()): number {
|
||||
const utc = new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
|
||||
const targetTime = new Date(utc.toLocaleString('en-US', { timeZone: TimeFormatter.settings.timezone }));
|
||||
return (targetTime.getTime() - utc.getTime()) / 60000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in 12-hour format
|
||||
* @param date - Date to format
|
||||
* @returns Formatted time string (e.g., "9:00 AM")
|
||||
*/
|
||||
static format12Hour(date: Date): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
|
||||
return localDate.toLocaleTimeString(TimeFormatter.settings.locale, {
|
||||
timeZone: TimeFormatter.settings.timezone,
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in 24-hour format using DateService
|
||||
* Format time in 24-hour format using DateService (internal helper)
|
||||
* @param date - Date to format
|
||||
* @returns Formatted time string (e.g., "09:00")
|
||||
*/
|
||||
static format24Hour(date: Date): string {
|
||||
private static format24Hour(date: Date): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
return TimeFormatter.dateService.formatTime(localDate, TimeFormatter.settings.showSeconds);
|
||||
return TimeFormatter.getDateService().formatTime(localDate, TimeFormatter.settings.showSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,38 +81,8 @@ export class TimeFormatter {
|
|||
* @returns Formatted time string
|
||||
*/
|
||||
static formatTime(date: Date): string {
|
||||
return TimeFormatter.settings.use24HourFormat
|
||||
? TimeFormatter.format24Hour(date)
|
||||
: TimeFormatter.format12Hour(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time from total minutes since midnight using DateService
|
||||
* @param totalMinutes - Minutes since midnight (e.g., 540 for 9:00 AM)
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
static formatTimeFromMinutes(totalMinutes: number): string {
|
||||
return TimeFormatter.dateService.formatTimeFromMinutes(totalMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date and time together
|
||||
* @param date - Date to format
|
||||
* @returns Formatted date and time string
|
||||
*/
|
||||
static formatDateTime(date: Date): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
|
||||
const dateStr = localDate.toLocaleDateString(TimeFormatter.settings.locale, {
|
||||
timeZone: TimeFormatter.settings.timezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
|
||||
const timeStr = TimeFormatter.formatTime(date);
|
||||
|
||||
return `${dateStr} ${timeStr}`;
|
||||
// Always use 24-hour format (12-hour support removed as unused)
|
||||
return TimeFormatter.format24Hour(date);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -146,77 +94,6 @@ export class TimeFormatter {
|
|||
static formatTimeRange(startDate: Date, endDate: Date): string {
|
||||
const localStart = TimeFormatter.convertToLocalTime(startDate);
|
||||
const localEnd = TimeFormatter.convertToLocalTime(endDate);
|
||||
return TimeFormatter.dateService.formatTimeRange(localStart, localEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current timezone observes daylight saving time
|
||||
* @param date - Reference date
|
||||
* @returns True if DST is active
|
||||
*/
|
||||
static isDaylightSavingTime(date: Date = new Date()): boolean {
|
||||
const january = new Date(date.getFullYear(), 0, 1);
|
||||
const july = new Date(date.getFullYear(), 6, 1);
|
||||
|
||||
const janOffset = TimeFormatter.getTimezoneOffset(january);
|
||||
const julOffset = TimeFormatter.getTimezoneOffset(july);
|
||||
|
||||
return Math.max(janOffset, julOffset) !== TimeFormatter.getTimezoneOffset(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone abbreviation (e.g., "CET", "CEST")
|
||||
* @param date - Reference date
|
||||
* @returns Timezone abbreviation
|
||||
*/
|
||||
static getTimezoneAbbreviation(date: Date = new Date()): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
|
||||
return localDate.toLocaleTimeString('en-US', {
|
||||
timeZone: TimeFormatter.settings.timezone,
|
||||
timeZoneName: 'short'
|
||||
}).split(' ').pop() || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date in technical format: yyyy-mm-dd using DateService
|
||||
*/
|
||||
static formatDateTechnical(date: Date): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
return TimeFormatter.dateService.formatDate(localDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in technical format: hh:mm or hh:mm:ss using DateService
|
||||
*/
|
||||
static formatTimeTechnical(date: Date, includeSeconds: boolean = false): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
return TimeFormatter.dateService.formatTime(localDate, includeSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date and time in technical format: yyyy-mm-dd hh:mm:ss using DateService
|
||||
*/
|
||||
static formatDateTimeTechnical(date: Date): string {
|
||||
const localDate = TimeFormatter.convertToLocalTime(date);
|
||||
return TimeFormatter.dateService.formatTechnicalDateTime(localDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert local date to UTC ISO string using DateService
|
||||
* @param localDate - Date in local timezone
|
||||
* @returns ISO string in UTC (with 'Z' suffix)
|
||||
*/
|
||||
static toUTC(localDate: Date): string {
|
||||
return TimeFormatter.dateService.toUTC(localDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UTC ISO string to local date using DateService
|
||||
* @param utcString - ISO string in UTC
|
||||
* @returns Date in local timezone
|
||||
*/
|
||||
static fromUTC(utcString: string): Date {
|
||||
return TimeFormatter.dateService.fromUTC(utcString);
|
||||
return TimeFormatter.getDateService().formatTimeRange(localStart, localEnd);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue