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,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`;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue