Calendar/src/v2/core/DateService.ts
Janus C. H. Knudsen ad2df353b5 Adds workweek settings and dynamic view configuration
Introduces settings service for managing tenant-specific calendar configurations

Enables dynamic workweek presets with configurable work days
Improves view switching with enhanced UI components
Adds flexible calendar rendering based on tenant settings

Extends DateService to support workweek date generation
2025-12-15 22:24:32 +01:00

177 lines
5.4 KiB
TypeScript

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import isoWeek from 'dayjs/plugin/isoWeek';
import { ITimeFormatConfig } from './ITimeFormatConfig';
// Enable dayjs plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(isoWeek);
export class DateService {
private timezone: string;
private baseDate: dayjs.Dayjs;
constructor(private config: ITimeFormatConfig, baseDate?: Date) {
this.timezone = config.timezone;
// Allow setting a fixed base date for demo/testing purposes
this.baseDate = baseDate ? dayjs(baseDate) : dayjs();
}
/**
* Set a fixed base date (useful for demos with static mock data)
*/
setBaseDate(date: Date): void {
this.baseDate = dayjs(date);
}
/**
* Get the current base date (either fixed or today)
*/
getBaseDate(): Date {
return this.baseDate.toDate();
}
parseISO(isoString: string): Date {
return dayjs(isoString).toDate();
}
getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date);
}
getWeekDates(offset = 0, days = 7): string[] {
const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week');
return Array.from({ length: days }, (_, i) =>
monday.add(i, 'day').format('YYYY-MM-DD')
);
}
/**
* Get dates for specific weekdays within a week
* @param offset - Week offset from base date (0 = current week)
* @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday)
* @returns Array of date strings in YYYY-MM-DD format
*/
getWorkWeekDates(offset: number, workDays: number[]): string[] {
const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week');
return workDays.map(isoDay => {
// ISO: 1=Monday, 7=Sunday → days from Monday: 0-6
const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
return monday.add(daysFromMonday, 'day').format('YYYY-MM-DD');
});
}
// ============================================
// FORMATTING
// ============================================
formatTime(date: Date, showSeconds = false): string {
const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm';
return dayjs(date).format(pattern);
}
formatTimeRange(start: Date, end: Date): string {
return `${this.formatTime(start)} - ${this.formatTime(end)}`;
}
formatDate(date: Date): string {
return dayjs(date).format('YYYY-MM-DD');
}
getDateKey(date: Date): string {
return this.formatDate(date);
}
// ============================================
// COLUMN KEY
// ============================================
/**
* Build a uniform columnKey from grouping segments
* Handles any combination of date, resource, team, etc.
*
* @example
* buildColumnKey({ date: '2025-12-09' }) → "2025-12-09"
* buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001"
*/
buildColumnKey(segments: Record<string, string>): string {
// Always put date first if present, then other segments alphabetically
const date = segments.date;
const others = Object.entries(segments)
.filter(([k]) => k !== 'date')
.sort(([a], [b]) => a.localeCompare(b))
.map(([, v]) => v);
return date ? [date, ...others].join(':') : others.join(':');
}
/**
* Parse a columnKey back into segments
* Assumes format: "date:resource:..." or just "date"
*/
parseColumnKey(columnKey: string): { date: string; resource?: string } {
const parts = columnKey.split(':');
return {
date: parts[0],
resource: parts[1]
};
}
/**
* Extract dateKey from columnKey (first segment)
*/
getDateFromColumnKey(columnKey: string): string {
return columnKey.split(':')[0];
}
// ============================================
// TIME CALCULATIONS
// ============================================
timeToMinutes(timeString: string): number {
const parts = timeString.split(':').map(Number);
const hours = parts[0] || 0;
const minutes = parts[1] || 0;
return hours * 60 + minutes;
}
minutesToTime(totalMinutes: number): string {
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return dayjs().hour(hours).minute(minutes).format('HH:mm');
}
getMinutesSinceMidnight(date: Date): number {
const d = dayjs(date);
return d.hour() * 60 + d.minute();
}
// ============================================
// UTC CONVERSIONS
// ============================================
toUTC(localDate: Date): string {
return dayjs.tz(localDate, this.timezone).utc().toISOString();
}
fromUTC(utcString: string): Date {
return dayjs.utc(utcString).tz(this.timezone).toDate();
}
// ============================================
// DATE CREATION
// ============================================
createDateAtTime(baseDate: Date | string, timeString: string): Date {
const totalMinutes = this.timeToMinutes(timeString);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate();
}
getISOWeekDay(date: Date | string): number {
return dayjs(date).isoWeekday(); // 1=Monday, 7=Sunday
}
}