Replaces DateCalculator with DateService for improved date and time operations, including timezone handling. This change enhances the calendar's accuracy and flexibility in managing dates, especially concerning timezone configurations. It also corrects a typo in the `allDay` dataset attribute.
262 lines
No EOL
8.6 KiB
TypeScript
262 lines
No EOL
8.6 KiB
TypeScript
import { calendarConfig } from '../core/CalendarConfig';
|
|
import { ColumnBounds } from './ColumnDetectionUtils';
|
|
import { DateService } from './DateService';
|
|
import { TimeFormatter } from './TimeFormatter';
|
|
|
|
/**
|
|
* PositionUtils - Static positioning utilities using singleton calendarConfig
|
|
* 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');
|
|
|
|
/**
|
|
* Convert minutes to pixels
|
|
*/
|
|
public static minutesToPixels(minutes: number): number {
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const pixelsPerHour = gridSettings.hourHeight;
|
|
return (minutes / 60) * pixelsPerHour;
|
|
}
|
|
|
|
/**
|
|
* Convert pixels to minutes
|
|
*/
|
|
public static pixelsToMinutes(pixels: number): number {
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const pixelsPerHour = gridSettings.hourHeight;
|
|
return (pixels / pixelsPerHour) * 60;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
|
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
|
|
|
return PositionUtils.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();
|
|
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
|
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
|
|
|
return PositionUtils.minutesToPixels(minutesFromDayStart);
|
|
}
|
|
|
|
/**
|
|
* Convert pixels to time using DateService
|
|
*/
|
|
public static pixelsToTime(pixels: number): string {
|
|
const minutes = PositionUtils.pixelsToMinutes(pixels);
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
|
const totalMinutes = dayStartMinutes + minutes;
|
|
|
|
return PositionUtils.dateService.minutesToTime(totalMinutes);
|
|
}
|
|
|
|
/**
|
|
* Beregn event position og størrelse
|
|
*/
|
|
public static calculateEventPosition(startTime: string | Date, endTime: string | Date): {
|
|
top: number;
|
|
height: number;
|
|
duration: number;
|
|
} {
|
|
let startPixels: number;
|
|
let endPixels: number;
|
|
|
|
if (typeof startTime === 'string') {
|
|
startPixels = PositionUtils.timeToPixels(startTime);
|
|
} else {
|
|
startPixels = PositionUtils.dateToPixels(startTime);
|
|
}
|
|
|
|
if (typeof endTime === 'string') {
|
|
endPixels = PositionUtils.timeToPixels(endTime);
|
|
} else {
|
|
endPixels = PositionUtils.dateToPixels(endTime);
|
|
}
|
|
|
|
const height = Math.max(endPixels - startPixels, PositionUtils.getMinimumEventHeight());
|
|
const duration = PositionUtils.pixelsToMinutes(height);
|
|
|
|
return {
|
|
top: startPixels,
|
|
height,
|
|
duration
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Snap position til grid interval
|
|
*/
|
|
public static snapToGrid(pixels: number): number {
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const snapInterval = gridSettings.snapInterval;
|
|
const snapPixels = PositionUtils.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();
|
|
const snapInterval = gridSettings.snapInterval;
|
|
|
|
const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval;
|
|
return PositionUtils.dateService.minutesToTime(snappedMinutes);
|
|
}
|
|
|
|
/**
|
|
* Beregn kolonne position for overlappende events
|
|
*/
|
|
public static 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
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check om to events overlapper i tid
|
|
*/
|
|
public static 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 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 {
|
|
|
|
const relativeY = clientY - column.boundingClientRect.top;
|
|
|
|
// Snap til grid
|
|
return PositionUtils.snapToGrid(relativeY);
|
|
}
|
|
|
|
/**
|
|
* Valider at tid er inden for arbejdstimer
|
|
*/
|
|
public static isWithinWorkHours(timeString: string): boolean {
|
|
const [hours] = timeString.split(':').map(Number);
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
return hours >= gridSettings.workStartHour && hours < gridSettings.workEndHour;
|
|
}
|
|
|
|
/**
|
|
* Valider at tid er inden for dag grænser
|
|
*/
|
|
public static isWithinDayBounds(timeString: string): boolean {
|
|
const [hours] = timeString.split(':').map(Number);
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
return hours >= gridSettings.dayStartHour && hours < gridSettings.dayEndHour;
|
|
}
|
|
|
|
/**
|
|
* Hent minimum event højde i pixels
|
|
*/
|
|
public static getMinimumEventHeight(): number {
|
|
// Minimum 15 minutter
|
|
return PositionUtils.minutesToPixels(15);
|
|
}
|
|
|
|
/**
|
|
* Hent maksimum event højde i pixels (hele dagen)
|
|
*/
|
|
public static getMaximumEventHeight(): number {
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const dayDurationHours = gridSettings.dayEndHour - gridSettings.dayStartHour;
|
|
return dayDurationHours * gridSettings.hourHeight;
|
|
}
|
|
|
|
/**
|
|
* Beregn total kalender højde
|
|
*/
|
|
public static getTotalCalendarHeight(): number {
|
|
return PositionUtils.getMaximumEventHeight();
|
|
}
|
|
|
|
/**
|
|
* Convert ISO datetime to time string with UTC-to-local conversion
|
|
*/
|
|
public static isoToTimeString(isoString: string): string {
|
|
const date = new Date(isoString);
|
|
return TimeFormatter.formatTime(date);
|
|
}
|
|
|
|
/**
|
|
* Convert time string to ISO datetime using DateService
|
|
*/
|
|
public static timeStringToIso(timeString: string, date: Date = new Date()): string {
|
|
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
|
|
const hours = Math.floor(totalMinutes / 60);
|
|
const minutes = totalMinutes % 60;
|
|
|
|
const newDate = new Date(date);
|
|
newDate.setHours(hours, minutes, 0, 0);
|
|
|
|
return newDate.toISOString();
|
|
}
|
|
|
|
/**
|
|
* Calculate event duration using DateService
|
|
*/
|
|
public static calculateDuration(startTime: string | Date, endTime: string | Date): number {
|
|
return PositionUtils.dateService.getDurationMinutes(startTime, endTime);
|
|
}
|
|
|
|
/**
|
|
* Format duration to readable text (Danish)
|
|
*/
|
|
public static 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`;
|
|
}
|
|
} |