Some ignored filles was missing
This commit is contained in:
parent
7db22245e2
commit
fd5ab6bc0d
268 changed files with 31970 additions and 4 deletions
42
wwwroot/js/utils/AllDayLayoutEngine.d.ts
vendored
Normal file
42
wwwroot/js/utils/AllDayLayoutEngine.d.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||
export interface IEventLayout {
|
||||
calenderEvent: ICalendarEvent;
|
||||
gridArea: string;
|
||||
startColumn: number;
|
||||
endColumn: number;
|
||||
row: number;
|
||||
columnSpan: number;
|
||||
}
|
||||
export declare class AllDayLayoutEngine {
|
||||
private weekDates;
|
||||
private tracks;
|
||||
constructor(weekDates: string[]);
|
||||
/**
|
||||
* Calculate layout for all events using clean day-based logic
|
||||
*/
|
||||
calculateLayout(events: ICalendarEvent[]): IEventLayout[];
|
||||
/**
|
||||
* Find available track for event spanning from startDay to endDay (0-based indices)
|
||||
*/
|
||||
private findAvailableTrack;
|
||||
/**
|
||||
* Check if track is available for the given day range (0-based indices)
|
||||
*/
|
||||
private isTrackAvailable;
|
||||
/**
|
||||
* Get start day index for event (1-based, 0 if not visible)
|
||||
*/
|
||||
private getEventStartDay;
|
||||
/**
|
||||
* Get end day index for event (1-based, 0 if not visible)
|
||||
*/
|
||||
private getEventEndDay;
|
||||
/**
|
||||
* Check if event is visible in the current date range
|
||||
*/
|
||||
private isEventVisible;
|
||||
/**
|
||||
* Format date to YYYY-MM-DD string using local date
|
||||
*/
|
||||
private formatDate;
|
||||
}
|
||||
108
wwwroot/js/utils/AllDayLayoutEngine.js
Normal file
108
wwwroot/js/utils/AllDayLayoutEngine.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
export class AllDayLayoutEngine {
|
||||
constructor(weekDates) {
|
||||
this.weekDates = weekDates;
|
||||
this.tracks = [];
|
||||
}
|
||||
/**
|
||||
* Calculate layout for all events using clean day-based logic
|
||||
*/
|
||||
calculateLayout(events) {
|
||||
let layouts = [];
|
||||
// Reset tracks for new calculation
|
||||
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
||||
// Filter to only visible events
|
||||
const visibleEvents = events.filter(event => this.isEventVisible(event));
|
||||
// Process events in input order (no sorting)
|
||||
for (const event of visibleEvents) {
|
||||
const startDay = this.getEventStartDay(event);
|
||||
const endDay = this.getEventEndDay(event);
|
||||
if (startDay > 0 && endDay > 0) {
|
||||
const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks
|
||||
// Mark days as occupied
|
||||
for (let day = startDay - 1; day <= endDay - 1; day++) {
|
||||
this.tracks[track][day] = true;
|
||||
}
|
||||
const layout = {
|
||||
calenderEvent: event,
|
||||
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
||||
startColumn: startDay,
|
||||
endColumn: endDay,
|
||||
row: track + 1,
|
||||
columnSpan: endDay - startDay + 1
|
||||
};
|
||||
layouts.push(layout);
|
||||
}
|
||||
}
|
||||
return layouts;
|
||||
}
|
||||
/**
|
||||
* Find available track for event spanning from startDay to endDay (0-based indices)
|
||||
*/
|
||||
findAvailableTrack(startDay, endDay) {
|
||||
for (let trackIndex = 0; trackIndex < this.tracks.length; trackIndex++) {
|
||||
if (this.isTrackAvailable(trackIndex, startDay, endDay)) {
|
||||
return trackIndex;
|
||||
}
|
||||
}
|
||||
// Create new track if none available
|
||||
this.tracks.push(new Array(this.weekDates.length).fill(false));
|
||||
return this.tracks.length - 1;
|
||||
}
|
||||
/**
|
||||
* Check if track is available for the given day range (0-based indices)
|
||||
*/
|
||||
isTrackAvailable(trackIndex, startDay, endDay) {
|
||||
for (let day = startDay; day <= endDay; day++) {
|
||||
if (this.tracks[trackIndex][day]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Get start day index for event (1-based, 0 if not visible)
|
||||
*/
|
||||
getEventStartDay(event) {
|
||||
const eventStartDate = this.formatDate(event.start);
|
||||
const firstVisibleDate = this.weekDates[0];
|
||||
// If event starts before visible range, clip to first visible day
|
||||
const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate;
|
||||
const dayIndex = this.weekDates.indexOf(clippedStartDate);
|
||||
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
||||
}
|
||||
/**
|
||||
* Get end day index for event (1-based, 0 if not visible)
|
||||
*/
|
||||
getEventEndDay(event) {
|
||||
const eventEndDate = this.formatDate(event.end);
|
||||
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
||||
// If event ends after visible range, clip to last visible day
|
||||
const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate;
|
||||
const dayIndex = this.weekDates.indexOf(clippedEndDate);
|
||||
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
||||
}
|
||||
/**
|
||||
* Check if event is visible in the current date range
|
||||
*/
|
||||
isEventVisible(event) {
|
||||
if (this.weekDates.length === 0)
|
||||
return false;
|
||||
const eventStartDate = this.formatDate(event.start);
|
||||
const eventEndDate = this.formatDate(event.end);
|
||||
const firstVisibleDate = this.weekDates[0];
|
||||
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
||||
// Event overlaps if it doesn't end before visible range starts
|
||||
// AND doesn't start after visible range ends
|
||||
return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate);
|
||||
}
|
||||
/**
|
||||
* Format date to YYYY-MM-DD string using local date
|
||||
*/
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=AllDayLayoutEngine.js.map
|
||||
1
wwwroot/js/utils/AllDayLayoutEngine.js.map
Normal file
1
wwwroot/js/utils/AllDayLayoutEngine.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"AllDayLayoutEngine.js","sourceRoot":"","sources":["../../../src/utils/AllDayLayoutEngine.ts"],"names":[],"mappings":"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"}
|
||||
30
wwwroot/js/utils/ColumnDetectionUtils.d.ts
vendored
Normal file
30
wwwroot/js/utils/ColumnDetectionUtils.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* ColumnDetectionUtils - Shared utility for column detection and caching
|
||||
* Used by both DragDropManager and AllDayManager for consistent column detection
|
||||
*/
|
||||
import { IMousePosition } from "../types/DragDropTypes";
|
||||
export interface IColumnBounds {
|
||||
date: string;
|
||||
left: number;
|
||||
right: number;
|
||||
boundingClientRect: DOMRect;
|
||||
element: HTMLElement;
|
||||
index: number;
|
||||
}
|
||||
export declare class ColumnDetectionUtils {
|
||||
private static columnBoundsCache;
|
||||
/**
|
||||
* Update column bounds cache for coordinate-based column detection
|
||||
*/
|
||||
static updateColumnBoundsCache(): void;
|
||||
/**
|
||||
* Get column date from X coordinate using cached bounds
|
||||
*/
|
||||
static getColumnBounds(position: IMousePosition): IColumnBounds | null;
|
||||
/**
|
||||
* Get column bounds by Date
|
||||
*/
|
||||
static getColumnBoundsByDate(date: Date): IColumnBounds | null;
|
||||
static getColumns(): IColumnBounds[];
|
||||
static getHeaderColumns(): IColumnBounds[];
|
||||
}
|
||||
87
wwwroot/js/utils/ColumnDetectionUtils.js
Normal file
87
wwwroot/js/utils/ColumnDetectionUtils.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* ColumnDetectionUtils - Shared utility for column detection and caching
|
||||
* Used by both DragDropManager and AllDayManager for consistent column detection
|
||||
*/
|
||||
export class ColumnDetectionUtils {
|
||||
/**
|
||||
* Update column bounds cache for coordinate-based column detection
|
||||
*/
|
||||
static updateColumnBoundsCache() {
|
||||
// Reset cache
|
||||
this.columnBoundsCache = [];
|
||||
// Find alle kolonner
|
||||
const columns = document.querySelectorAll('swp-day-column');
|
||||
let index = 1;
|
||||
// Cache hver kolonnes x-grænser
|
||||
columns.forEach(column => {
|
||||
const rect = column.getBoundingClientRect();
|
||||
const date = column.dataset.date;
|
||||
if (date) {
|
||||
this.columnBoundsCache.push({
|
||||
boundingClientRect: rect,
|
||||
element: column,
|
||||
date,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
index: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
// Sorter efter x-position (fra venstre til højre)
|
||||
this.columnBoundsCache.sort((a, b) => a.left - b.left);
|
||||
}
|
||||
/**
|
||||
* Get column date from X coordinate using cached bounds
|
||||
*/
|
||||
static getColumnBounds(position) {
|
||||
if (this.columnBoundsCache.length === 0) {
|
||||
this.updateColumnBoundsCache();
|
||||
}
|
||||
// Find den kolonne hvor x-koordinaten er indenfor grænserne
|
||||
let column = this.columnBoundsCache.find(col => position.x >= col.left && position.x <= col.right);
|
||||
if (column)
|
||||
return column;
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Get column bounds by Date
|
||||
*/
|
||||
static getColumnBoundsByDate(date) {
|
||||
if (this.columnBoundsCache.length === 0) {
|
||||
this.updateColumnBoundsCache();
|
||||
}
|
||||
// Convert Date to YYYY-MM-DD format
|
||||
let dateString = date.toISOString().split('T')[0];
|
||||
// Find column that matches the date
|
||||
let column = this.columnBoundsCache.find(col => col.date === dateString);
|
||||
return column || null;
|
||||
}
|
||||
static getColumns() {
|
||||
return [...this.columnBoundsCache];
|
||||
}
|
||||
static getHeaderColumns() {
|
||||
let dayHeaders = [];
|
||||
const dayColumns = document.querySelectorAll('swp-calendar-header swp-day-header');
|
||||
let index = 1;
|
||||
// Cache hver kolonnes x-grænser
|
||||
dayColumns.forEach(column => {
|
||||
const rect = column.getBoundingClientRect();
|
||||
const date = column.dataset.date;
|
||||
if (date) {
|
||||
dayHeaders.push({
|
||||
boundingClientRect: rect,
|
||||
element: column,
|
||||
date,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
index: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
// Sorter efter x-position (fra venstre til højre)
|
||||
dayHeaders.sort((a, b) => a.left - b.left);
|
||||
return dayHeaders;
|
||||
}
|
||||
}
|
||||
ColumnDetectionUtils.columnBoundsCache = [];
|
||||
//# sourceMappingURL=ColumnDetectionUtils.js.map
|
||||
1
wwwroot/js/utils/ColumnDetectionUtils.js.map
Normal file
1
wwwroot/js/utils/ColumnDetectionUtils.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"ColumnDetectionUtils.js","sourceRoot":"","sources":["../../../src/utils/ColumnDetectionUtils.ts"],"names":[],"mappings":"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"}
|
||||
149
wwwroot/js/utils/DateCalculator.d.ts
vendored
Normal file
149
wwwroot/js/utils/DateCalculator.d.ts
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* DateCalculator - Centralized date calculation logic for calendar
|
||||
* Handles all date computations with proper week start handling
|
||||
*/
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
export declare class DateCalculator {
|
||||
private static config;
|
||||
/**
|
||||
* Initialize DateCalculator with configuration
|
||||
* @param config - Calendar configuration
|
||||
*/
|
||||
static initialize(config: CalendarConfig): void;
|
||||
/**
|
||||
* Validate that a date is valid
|
||||
* @param date - Date to validate
|
||||
* @param methodName - Name of calling method for error messages
|
||||
* @throws Error if date is invalid
|
||||
*/
|
||||
private static validateDate;
|
||||
/**
|
||||
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
||||
* @param weekStart - Any date in the week
|
||||
* @returns Array of dates for the configured work days
|
||||
*/
|
||||
static getWorkWeekDates(weekStart: Date): Date[];
|
||||
/**
|
||||
* Get the start of the ISO week (Monday) for a given date
|
||||
* @param date - Any date in the week
|
||||
* @returns The Monday of the ISO week
|
||||
*/
|
||||
static getISOWeekStart(date: Date): Date;
|
||||
/**
|
||||
* Get the end of the ISO week for a given date
|
||||
* @param date - Any date in the week
|
||||
* @returns The end date of the ISO week (Sunday)
|
||||
*/
|
||||
static getWeekEnd(date: Date): Date;
|
||||
/**
|
||||
* Get week number for a date (ISO 8601)
|
||||
* @param date - The date to get week number for
|
||||
* @returns Week number (1-53)
|
||||
*/
|
||||
static getWeekNumber(date: Date): number;
|
||||
/**
|
||||
* Format a date range with customizable options
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted date range string
|
||||
*/
|
||||
static formatDateRange(start: Date, end: Date, options?: {
|
||||
locale?: string;
|
||||
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
|
||||
day?: 'numeric' | '2-digit';
|
||||
year?: 'numeric' | '2-digit';
|
||||
}): string;
|
||||
/**
|
||||
* Format a date to ISO date string (YYYY-MM-DD)
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
static formatISODate(date: Date): string;
|
||||
/**
|
||||
* Check if a date is today
|
||||
* @param date - Date to check
|
||||
* @returns True if the date is today
|
||||
*/
|
||||
static isToday(date: Date): boolean;
|
||||
/**
|
||||
* Add days to a date
|
||||
* @param date - Base date
|
||||
* @param days - Number of days to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
static addDays(date: Date, days: number): Date;
|
||||
/**
|
||||
* Add weeks to a date
|
||||
* @param date - Base date
|
||||
* @param weeks - Number of weeks to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
static addWeeks(date: Date, weeks: number): Date;
|
||||
/**
|
||||
* Get all dates in a week
|
||||
* @param weekStart - Start of the week
|
||||
* @returns Array of 7 dates for the full week
|
||||
*/
|
||||
static getFullWeekDates(weekStart: Date): Date[];
|
||||
/**
|
||||
* Get the day name for a date using Intl.DateTimeFormat
|
||||
* @param date - Date to get day name for
|
||||
* @param format - 'short' or 'long'
|
||||
* @returns Day name
|
||||
*/
|
||||
static getDayName(date: Date, format?: 'short' | 'long'): string;
|
||||
/**
|
||||
* Format time to HH:MM
|
||||
* @param date - Date to format
|
||||
* @returns Time string
|
||||
*/
|
||||
static formatTime(date: Date): string;
|
||||
/**
|
||||
* Format time to 12-hour format
|
||||
* @param date - Date to format
|
||||
* @returns 12-hour time string
|
||||
*/
|
||||
static formatTime12(date: Date): string;
|
||||
/**
|
||||
* Convert minutes since midnight to time string
|
||||
* @param minutes - Minutes since midnight
|
||||
* @returns Time string
|
||||
*/
|
||||
static minutesToTime(minutes: number): string;
|
||||
/**
|
||||
* Convert time string to minutes since midnight
|
||||
* @param timeStr - Time string
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
static timeToMinutes(timeStr: string): number;
|
||||
/**
|
||||
* Get minutes since start of day
|
||||
* @param date - Date or ISO string
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
static getMinutesSinceMidnight(date: Date | string): number;
|
||||
/**
|
||||
* Calculate duration in minutes between two dates
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns Duration in minutes
|
||||
*/
|
||||
static getDurationMinutes(start: Date | string, end: Date | string): number;
|
||||
/**
|
||||
* Check if two dates are on the same day
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns True if same day
|
||||
*/
|
||||
static isSameDay(date1: Date, date2: Date): boolean;
|
||||
/**
|
||||
* Check if event spans multiple days
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns True if spans multiple days
|
||||
*/
|
||||
static isMultiDay(start: Date | string, end: Date | string): boolean;
|
||||
constructor();
|
||||
}
|
||||
export declare function createDateCalculator(config: CalendarConfig): DateCalculator;
|
||||
260
wwwroot/js/utils/DateCalculator.js
Normal file
260
wwwroot/js/utils/DateCalculator.js
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/**
|
||||
* DateCalculator - Centralized date calculation logic for calendar
|
||||
* Handles all date computations with proper week start handling
|
||||
*/
|
||||
export class DateCalculator {
|
||||
/**
|
||||
* Initialize DateCalculator with configuration
|
||||
* @param config - Calendar configuration
|
||||
*/
|
||||
static initialize(config) {
|
||||
DateCalculator.config = config;
|
||||
}
|
||||
/**
|
||||
* Validate that a date is valid
|
||||
* @param date - Date to validate
|
||||
* @param methodName - Name of calling method for error messages
|
||||
* @throws Error if date is invalid
|
||||
*/
|
||||
static validateDate(date, methodName) {
|
||||
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
||||
throw new Error(`${methodName}: Invalid date provided - ${date}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
||||
* @param weekStart - Any date in the week
|
||||
* @returns Array of dates for the configured work days
|
||||
*/
|
||||
static getWorkWeekDates(weekStart) {
|
||||
DateCalculator.validateDate(weekStart, 'getWorkWeekDates');
|
||||
const dates = [];
|
||||
const workWeekSettings = DateCalculator.config.getWorkWeekSettings();
|
||||
// Always use ISO week start (Monday)
|
||||
const mondayOfWeek = DateCalculator.getISOWeekStart(weekStart);
|
||||
// Calculate dates for each work day using ISO numbering
|
||||
workWeekSettings.workDays.forEach(isoDay => {
|
||||
const date = new Date(mondayOfWeek);
|
||||
// ISO day 1=Monday is +0 days, ISO day 7=Sunday is +6 days
|
||||
const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
|
||||
date.setDate(mondayOfWeek.getDate() + daysFromMonday);
|
||||
dates.push(date);
|
||||
});
|
||||
return dates;
|
||||
}
|
||||
/**
|
||||
* Get the start of the ISO week (Monday) for a given date
|
||||
* @param date - Any date in the week
|
||||
* @returns The Monday of the ISO week
|
||||
*/
|
||||
static getISOWeekStart(date) {
|
||||
DateCalculator.validateDate(date, 'getISOWeekStart');
|
||||
const monday = new Date(date);
|
||||
const currentDay = monday.getDay();
|
||||
const daysToSubtract = currentDay === 0 ? 6 : currentDay - 1;
|
||||
monday.setDate(monday.getDate() - daysToSubtract);
|
||||
monday.setHours(0, 0, 0, 0);
|
||||
return monday;
|
||||
}
|
||||
/**
|
||||
* Get the end of the ISO week for a given date
|
||||
* @param date - Any date in the week
|
||||
* @returns The end date of the ISO week (Sunday)
|
||||
*/
|
||||
static getWeekEnd(date) {
|
||||
DateCalculator.validateDate(date, 'getWeekEnd');
|
||||
const weekStart = DateCalculator.getISOWeekStart(date);
|
||||
const weekEnd = new Date(weekStart);
|
||||
weekEnd.setDate(weekStart.getDate() + 6);
|
||||
weekEnd.setHours(23, 59, 59, 999);
|
||||
return weekEnd;
|
||||
}
|
||||
/**
|
||||
* Get week number for a date (ISO 8601)
|
||||
* @param date - The date to get week number for
|
||||
* @returns Week number (1-53)
|
||||
*/
|
||||
static getWeekNumber(date) {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
}
|
||||
/**
|
||||
* Format a date range with customizable options
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted date range string
|
||||
*/
|
||||
static formatDateRange(start, end, options = {}) {
|
||||
const { locale = 'en-US', month = 'short', day = 'numeric' } = options;
|
||||
const startYear = start.getFullYear();
|
||||
const endYear = end.getFullYear();
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
month,
|
||||
day,
|
||||
year: startYear !== endYear ? 'numeric' : undefined
|
||||
});
|
||||
// @ts-ignore
|
||||
if (typeof formatter.formatRange === 'function') {
|
||||
// @ts-ignore
|
||||
return formatter.formatRange(start, end);
|
||||
}
|
||||
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
||||
}
|
||||
/**
|
||||
* Format a date to ISO date string (YYYY-MM-DD)
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
static formatISODate(date) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
/**
|
||||
* Check if a date is today
|
||||
* @param date - Date to check
|
||||
* @returns True if the date is today
|
||||
*/
|
||||
static isToday(date) {
|
||||
const today = new Date();
|
||||
return date.toDateString() === today.toDateString();
|
||||
}
|
||||
/**
|
||||
* Add days to a date
|
||||
* @param date - Base date
|
||||
* @param days - Number of days to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
static addDays(date, days) {
|
||||
const result = new Date(date);
|
||||
result.setDate(result.getDate() + days);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Add weeks to a date
|
||||
* @param date - Base date
|
||||
* @param weeks - Number of weeks to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
static addWeeks(date, weeks) {
|
||||
return DateCalculator.addDays(date, weeks * 7);
|
||||
}
|
||||
/**
|
||||
* Get all dates in a week
|
||||
* @param weekStart - Start of the week
|
||||
* @returns Array of 7 dates for the full week
|
||||
*/
|
||||
static getFullWeekDates(weekStart) {
|
||||
const dates = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
dates.push(DateCalculator.addDays(weekStart, i));
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
/**
|
||||
* Get the day name for a date using Intl.DateTimeFormat
|
||||
* @param date - Date to get day name for
|
||||
* @param format - 'short' or 'long'
|
||||
* @returns Day name
|
||||
*/
|
||||
static getDayName(date, format = 'short') {
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
weekday: format
|
||||
});
|
||||
return formatter.format(date);
|
||||
}
|
||||
/**
|
||||
* Format time to HH:MM
|
||||
* @param date - Date to format
|
||||
* @returns Time string
|
||||
*/
|
||||
static formatTime(date) {
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
||||
}
|
||||
/**
|
||||
* Format time to 12-hour format
|
||||
* @param date - Date to format
|
||||
* @returns 12-hour time string
|
||||
*/
|
||||
static formatTime12(date) {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const period = hours >= 12 ? 'PM' : 'AM';
|
||||
const displayHours = hours % 12 || 12;
|
||||
return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
|
||||
}
|
||||
/**
|
||||
* Convert minutes since midnight to time string
|
||||
* @param minutes - Minutes since midnight
|
||||
* @returns Time string
|
||||
*/
|
||||
static minutesToTime(minutes) {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
const period = hours >= 12 ? 'PM' : 'AM';
|
||||
const displayHours = hours % 12 || 12;
|
||||
return `${displayHours}:${String(mins).padStart(2, '0')} ${period}`;
|
||||
}
|
||||
/**
|
||||
* Convert time string to minutes since midnight
|
||||
* @param timeStr - Time string
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
static timeToMinutes(timeStr) {
|
||||
const [time] = timeStr.split('T').pop().split('.');
|
||||
const [hours, minutes] = time.split(':').map(Number);
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
/**
|
||||
* Get minutes since start of day
|
||||
* @param date - Date or ISO string
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
static getMinutesSinceMidnight(date) {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.getHours() * 60 + d.getMinutes();
|
||||
}
|
||||
/**
|
||||
* Calculate duration in minutes between two dates
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns Duration in minutes
|
||||
*/
|
||||
static getDurationMinutes(start, end) {
|
||||
const startDate = typeof start === 'string' ? new Date(start) : start;
|
||||
const endDate = typeof end === 'string' ? new Date(end) : end;
|
||||
return Math.floor((endDate.getTime() - startDate.getTime()) / 60000);
|
||||
}
|
||||
/**
|
||||
* Check if two dates are on the same day
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns True if same day
|
||||
*/
|
||||
static isSameDay(date1, date2) {
|
||||
return date1.toDateString() === date2.toDateString();
|
||||
}
|
||||
/**
|
||||
* Check if event spans multiple days
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns True if spans multiple days
|
||||
*/
|
||||
static isMultiDay(start, end) {
|
||||
const startDate = typeof start === 'string' ? new Date(start) : start;
|
||||
const endDate = typeof end === 'string' ? new Date(end) : end;
|
||||
return !DateCalculator.isSameDay(startDate, endDate);
|
||||
}
|
||||
// Legacy constructor for backward compatibility
|
||||
constructor() {
|
||||
// Empty constructor - all methods are now static
|
||||
}
|
||||
}
|
||||
// Legacy factory function - deprecated, use static methods instead
|
||||
export function createDateCalculator(config) {
|
||||
DateCalculator.initialize(config);
|
||||
return new DateCalculator();
|
||||
}
|
||||
//# sourceMappingURL=DateCalculator.js.map
|
||||
1
wwwroot/js/utils/DateCalculator.js.map
Normal file
1
wwwroot/js/utils/DateCalculator.js.map
Normal file
File diff suppressed because one or more lines are too long
254
wwwroot/js/utils/DateService.d.ts
vendored
Normal file
254
wwwroot/js/utils/DateService.d.ts
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/**
|
||||
* DateService - Unified date/time service using day.js
|
||||
* Handles all date operations, timezone conversions, and formatting
|
||||
*/
|
||||
import { Configuration } from '../configurations/CalendarConfig';
|
||||
export declare class DateService {
|
||||
private timezone;
|
||||
constructor(config: Configuration);
|
||||
/**
|
||||
* Convert local date to UTC ISO string
|
||||
* @param localDate - Date in local timezone
|
||||
* @returns ISO string in UTC (with 'Z' suffix)
|
||||
*/
|
||||
toUTC(localDate: Date): string;
|
||||
/**
|
||||
* Convert UTC ISO string to local date
|
||||
* @param utcString - ISO string in UTC
|
||||
* @returns Date in local timezone
|
||||
*/
|
||||
fromUTC(utcString: string): Date;
|
||||
/**
|
||||
* Format time as HH:mm or HH:mm:ss
|
||||
* @param date - Date to format
|
||||
* @param showSeconds - Include seconds in output
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
formatTime(date: Date, showSeconds?: boolean): string;
|
||||
/**
|
||||
* Format time range as "HH:mm - HH:mm"
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @returns Formatted time range
|
||||
*/
|
||||
formatTimeRange(start: Date, end: Date): string;
|
||||
/**
|
||||
* Format date and time in technical format: yyyy-MM-dd HH:mm:ss
|
||||
* @param date - Date to format
|
||||
* @returns Technical datetime string
|
||||
*/
|
||||
formatTechnicalDateTime(date: Date): string;
|
||||
/**
|
||||
* Format date as yyyy-MM-dd
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
formatDate(date: Date): string;
|
||||
/**
|
||||
* Format date as "Month Year" (e.g., "January 2025")
|
||||
* @param date - Date to format
|
||||
* @param locale - Locale for month name (default: 'en-US')
|
||||
* @returns Formatted month and year
|
||||
*/
|
||||
formatMonthYear(date: Date, locale?: string): string;
|
||||
/**
|
||||
* Format date as ISO string (same as formatDate for compatibility)
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
formatISODate(date: Date): string;
|
||||
/**
|
||||
* Format time in 12-hour format with AM/PM
|
||||
* @param date - Date to format
|
||||
* @returns Time string in 12-hour format (e.g., "2:30 PM")
|
||||
*/
|
||||
formatTime12(date: Date): string;
|
||||
/**
|
||||
* Get day name for a date
|
||||
* @param date - Date to get day name for
|
||||
* @param format - 'short' (e.g., 'Mon') or 'long' (e.g., 'Monday')
|
||||
* @param locale - Locale for day name (default: 'da-DK')
|
||||
* @returns Day name
|
||||
*/
|
||||
getDayName(date: Date, format?: 'short' | 'long', locale?: string): string;
|
||||
/**
|
||||
* Format a date range with customizable options
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted date range string
|
||||
*/
|
||||
formatDateRange(start: Date, end: Date, options?: {
|
||||
locale?: string;
|
||||
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
|
||||
day?: 'numeric' | '2-digit';
|
||||
year?: 'numeric' | '2-digit';
|
||||
}): string;
|
||||
/**
|
||||
* Convert time string (HH:mm or HH:mm:ss) to total minutes since midnight
|
||||
* @param timeString - Time in format HH:mm or HH:mm:ss
|
||||
* @returns Total minutes since midnight
|
||||
*/
|
||||
timeToMinutes(timeString: string): number;
|
||||
/**
|
||||
* Convert total minutes since midnight to time string HH:mm
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns Time string in format HH:mm
|
||||
*/
|
||||
minutesToTime(totalMinutes: number): string;
|
||||
/**
|
||||
* Format time from total minutes (alias for minutesToTime)
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns Time string in format HH:mm
|
||||
*/
|
||||
formatTimeFromMinutes(totalMinutes: number): string;
|
||||
/**
|
||||
* Get minutes since midnight for a given date
|
||||
* @param date - Date to calculate from
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
getMinutesSinceMidnight(date: Date): number;
|
||||
/**
|
||||
* Calculate duration in minutes between two dates
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns Duration in minutes
|
||||
*/
|
||||
getDurationMinutes(start: Date | string, end: Date | string): number;
|
||||
/**
|
||||
* Get start and end of week (Monday to Sunday)
|
||||
* @param date - Reference date
|
||||
* @returns Object with start and end dates
|
||||
*/
|
||||
getWeekBounds(date: Date): {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
/**
|
||||
* Add weeks to a date
|
||||
* @param date - Base date
|
||||
* @param weeks - Number of weeks to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addWeeks(date: Date, weeks: number): Date;
|
||||
/**
|
||||
* Add months to a date
|
||||
* @param date - Base date
|
||||
* @param months - Number of months to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addMonths(date: Date, months: number): Date;
|
||||
/**
|
||||
* Get ISO week number (1-53)
|
||||
* @param date - Date to get week number for
|
||||
* @returns ISO week number
|
||||
*/
|
||||
getWeekNumber(date: Date): number;
|
||||
/**
|
||||
* Get all dates in a full week (7 days starting from given date)
|
||||
* @param weekStart - Start date of the week
|
||||
* @returns Array of 7 dates
|
||||
*/
|
||||
getFullWeekDates(weekStart: Date): Date[];
|
||||
/**
|
||||
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
||||
* @param weekStart - Any date in the week
|
||||
* @param workDays - Array of ISO day numbers (1=Monday, 7=Sunday)
|
||||
* @returns Array of dates for the specified work days
|
||||
*/
|
||||
getWorkWeekDates(weekStart: Date, workDays: number[]): Date[];
|
||||
/**
|
||||
* Create a date at a specific time (minutes since midnight)
|
||||
* @param baseDate - Base date (date component)
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns New date with specified time
|
||||
*/
|
||||
createDateAtTime(baseDate: Date, totalMinutes: number): Date;
|
||||
/**
|
||||
* Snap date to nearest interval
|
||||
* @param date - Date to snap
|
||||
* @param intervalMinutes - Snap interval in minutes
|
||||
* @returns Snapped date
|
||||
*/
|
||||
snapToInterval(date: Date, intervalMinutes: number): Date;
|
||||
/**
|
||||
* Check if two dates are the same day
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns True if same day
|
||||
*/
|
||||
isSameDay(date1: Date, date2: Date): boolean;
|
||||
/**
|
||||
* Get start of day
|
||||
* @param date - Date
|
||||
* @returns Start of day (00:00:00)
|
||||
*/
|
||||
startOfDay(date: Date): Date;
|
||||
/**
|
||||
* Get end of day
|
||||
* @param date - Date
|
||||
* @returns End of day (23:59:59.999)
|
||||
*/
|
||||
endOfDay(date: Date): Date;
|
||||
/**
|
||||
* Add days to a date
|
||||
* @param date - Base date
|
||||
* @param days - Number of days to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addDays(date: Date, days: number): Date;
|
||||
/**
|
||||
* Add minutes to a date
|
||||
* @param date - Base date
|
||||
* @param minutes - Number of minutes to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addMinutes(date: Date, minutes: number): Date;
|
||||
/**
|
||||
* Parse ISO string to date
|
||||
* @param isoString - ISO date string
|
||||
* @returns Parsed date
|
||||
*/
|
||||
parseISO(isoString: string): Date;
|
||||
/**
|
||||
* Check if date is valid
|
||||
* @param date - Date to check
|
||||
* @returns True if valid
|
||||
*/
|
||||
isValid(date: Date): boolean;
|
||||
/**
|
||||
* Calculate difference in calendar days between two dates
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns Number of calendar days between dates (can be negative)
|
||||
*/
|
||||
differenceInCalendarDays(date1: Date, date2: Date): number;
|
||||
/**
|
||||
* Validate date range (start must be before or equal to end)
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @returns True if valid range
|
||||
*/
|
||||
isValidRange(start: Date, end: Date): boolean;
|
||||
/**
|
||||
* Check if date is within reasonable bounds (1900-2100)
|
||||
* @param date - Date to check
|
||||
* @returns True if within bounds
|
||||
*/
|
||||
isWithinBounds(date: Date): boolean;
|
||||
/**
|
||||
* Validate date with comprehensive checks
|
||||
* @param date - Date to validate
|
||||
* @param options - Validation options
|
||||
* @returns Validation result with error message
|
||||
*/
|
||||
validateDate(date: Date, options?: {
|
||||
requireFuture?: boolean;
|
||||
requirePast?: boolean;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
}): {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
418
wwwroot/js/utils/DateService.js
Normal file
418
wwwroot/js/utils/DateService.js
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
/**
|
||||
* DateService - Unified date/time service using day.js
|
||||
* Handles all date operations, timezone conversions, and formatting
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
||||
// Enable day.js plugins
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(isoWeek);
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
export class DateService {
|
||||
constructor(config) {
|
||||
this.timezone = config.timeFormatConfig.timezone;
|
||||
}
|
||||
// ============================================
|
||||
// CORE CONVERSIONS
|
||||
// ============================================
|
||||
/**
|
||||
* Convert local date to UTC ISO string
|
||||
* @param localDate - Date in local timezone
|
||||
* @returns ISO string in UTC (with 'Z' suffix)
|
||||
*/
|
||||
toUTC(localDate) {
|
||||
return dayjs.tz(localDate, this.timezone).utc().toISOString();
|
||||
}
|
||||
/**
|
||||
* Convert UTC ISO string to local date
|
||||
* @param utcString - ISO string in UTC
|
||||
* @returns Date in local timezone
|
||||
*/
|
||||
fromUTC(utcString) {
|
||||
return dayjs.utc(utcString).tz(this.timezone).toDate();
|
||||
}
|
||||
// ============================================
|
||||
// FORMATTING
|
||||
// ============================================
|
||||
/**
|
||||
* Format time as HH:mm or HH:mm:ss
|
||||
* @param date - Date to format
|
||||
* @param showSeconds - Include seconds in output
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
formatTime(date, showSeconds = false) {
|
||||
const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm';
|
||||
return dayjs(date).format(pattern);
|
||||
}
|
||||
/**
|
||||
* Format time range as "HH:mm - HH:mm"
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @returns Formatted time range
|
||||
*/
|
||||
formatTimeRange(start, end) {
|
||||
return `${this.formatTime(start)} - ${this.formatTime(end)}`;
|
||||
}
|
||||
/**
|
||||
* Format date and time in technical format: yyyy-MM-dd HH:mm:ss
|
||||
* @param date - Date to format
|
||||
* @returns Technical datetime string
|
||||
*/
|
||||
formatTechnicalDateTime(date) {
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
/**
|
||||
* Format date as yyyy-MM-dd
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
formatDate(date) {
|
||||
return dayjs(date).format('YYYY-MM-DD');
|
||||
}
|
||||
/**
|
||||
* Format date as "Month Year" (e.g., "January 2025")
|
||||
* @param date - Date to format
|
||||
* @param locale - Locale for month name (default: 'en-US')
|
||||
* @returns Formatted month and year
|
||||
*/
|
||||
formatMonthYear(date, locale = 'en-US') {
|
||||
return date.toLocaleDateString(locale, { month: 'long', year: 'numeric' });
|
||||
}
|
||||
/**
|
||||
* Format date as ISO string (same as formatDate for compatibility)
|
||||
* @param date - Date to format
|
||||
* @returns ISO date string
|
||||
*/
|
||||
formatISODate(date) {
|
||||
return this.formatDate(date);
|
||||
}
|
||||
/**
|
||||
* Format time in 12-hour format with AM/PM
|
||||
* @param date - Date to format
|
||||
* @returns Time string in 12-hour format (e.g., "2:30 PM")
|
||||
*/
|
||||
formatTime12(date) {
|
||||
return dayjs(date).format('h:mm A');
|
||||
}
|
||||
/**
|
||||
* Get day name for a date
|
||||
* @param date - Date to get day name for
|
||||
* @param format - 'short' (e.g., 'Mon') or 'long' (e.g., 'Monday')
|
||||
* @param locale - Locale for day name (default: 'da-DK')
|
||||
* @returns Day name
|
||||
*/
|
||||
getDayName(date, format = 'short', locale = 'da-DK') {
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
weekday: format
|
||||
});
|
||||
return formatter.format(date);
|
||||
}
|
||||
/**
|
||||
* Format a date range with customizable options
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted date range string
|
||||
*/
|
||||
formatDateRange(start, end, options = {}) {
|
||||
const { locale = 'en-US', month = 'short', day = 'numeric' } = options;
|
||||
const startYear = start.getFullYear();
|
||||
const endYear = end.getFullYear();
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
month,
|
||||
day,
|
||||
year: startYear !== endYear ? 'numeric' : undefined
|
||||
});
|
||||
// @ts-ignore - formatRange is available in modern browsers
|
||||
if (typeof formatter.formatRange === 'function') {
|
||||
// @ts-ignore
|
||||
return formatter.formatRange(start, end);
|
||||
}
|
||||
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
||||
}
|
||||
// ============================================
|
||||
// TIME CALCULATIONS
|
||||
// ============================================
|
||||
/**
|
||||
* Convert time string (HH:mm or HH:mm:ss) to total minutes since midnight
|
||||
* @param timeString - Time in format HH:mm or HH:mm:ss
|
||||
* @returns Total minutes since midnight
|
||||
*/
|
||||
timeToMinutes(timeString) {
|
||||
const parts = timeString.split(':').map(Number);
|
||||
const hours = parts[0] || 0;
|
||||
const minutes = parts[1] || 0;
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
/**
|
||||
* Convert total minutes since midnight to time string HH:mm
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns Time string in format HH:mm
|
||||
*/
|
||||
minutesToTime(totalMinutes) {
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return dayjs().hour(hours).minute(minutes).format('HH:mm');
|
||||
}
|
||||
/**
|
||||
* Format time from total minutes (alias for minutesToTime)
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns Time string in format HH:mm
|
||||
*/
|
||||
formatTimeFromMinutes(totalMinutes) {
|
||||
return this.minutesToTime(totalMinutes);
|
||||
}
|
||||
/**
|
||||
* Get minutes since midnight for a given date
|
||||
* @param date - Date to calculate from
|
||||
* @returns Minutes since midnight
|
||||
*/
|
||||
getMinutesSinceMidnight(date) {
|
||||
const d = dayjs(date);
|
||||
return d.hour() * 60 + d.minute();
|
||||
}
|
||||
/**
|
||||
* Calculate duration in minutes between two dates
|
||||
* @param start - Start date or ISO string
|
||||
* @param end - End date or ISO string
|
||||
* @returns Duration in minutes
|
||||
*/
|
||||
getDurationMinutes(start, end) {
|
||||
const startDate = dayjs(start);
|
||||
const endDate = dayjs(end);
|
||||
return endDate.diff(startDate, 'minute');
|
||||
}
|
||||
// ============================================
|
||||
// WEEK OPERATIONS
|
||||
// ============================================
|
||||
/**
|
||||
* Get start and end of week (Monday to Sunday)
|
||||
* @param date - Reference date
|
||||
* @returns Object with start and end dates
|
||||
*/
|
||||
getWeekBounds(date) {
|
||||
const d = dayjs(date);
|
||||
return {
|
||||
start: d.startOf('week').add(1, 'day').toDate(), // Monday (day.js week starts on Sunday)
|
||||
end: d.endOf('week').add(1, 'day').toDate() // Sunday
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Add weeks to a date
|
||||
* @param date - Base date
|
||||
* @param weeks - Number of weeks to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addWeeks(date, weeks) {
|
||||
return dayjs(date).add(weeks, 'week').toDate();
|
||||
}
|
||||
/**
|
||||
* Add months to a date
|
||||
* @param date - Base date
|
||||
* @param months - Number of months to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addMonths(date, months) {
|
||||
return dayjs(date).add(months, 'month').toDate();
|
||||
}
|
||||
/**
|
||||
* Get ISO week number (1-53)
|
||||
* @param date - Date to get week number for
|
||||
* @returns ISO week number
|
||||
*/
|
||||
getWeekNumber(date) {
|
||||
return dayjs(date).isoWeek();
|
||||
}
|
||||
/**
|
||||
* Get all dates in a full week (7 days starting from given date)
|
||||
* @param weekStart - Start date of the week
|
||||
* @returns Array of 7 dates
|
||||
*/
|
||||
getFullWeekDates(weekStart) {
|
||||
const dates = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
dates.push(this.addDays(weekStart, i));
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
/**
|
||||
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
||||
* @param weekStart - Any date in the week
|
||||
* @param workDays - Array of ISO day numbers (1=Monday, 7=Sunday)
|
||||
* @returns Array of dates for the specified work days
|
||||
*/
|
||||
getWorkWeekDates(weekStart, workDays) {
|
||||
const dates = [];
|
||||
// Get Monday of the week
|
||||
const weekBounds = this.getWeekBounds(weekStart);
|
||||
const mondayOfWeek = this.startOfDay(weekBounds.start);
|
||||
// Calculate dates for each work day using ISO numbering
|
||||
workDays.forEach(isoDay => {
|
||||
const date = new Date(mondayOfWeek);
|
||||
// ISO day 1=Monday is +0 days, ISO day 7=Sunday is +6 days
|
||||
const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
|
||||
date.setDate(mondayOfWeek.getDate() + daysFromMonday);
|
||||
dates.push(date);
|
||||
});
|
||||
return dates;
|
||||
}
|
||||
// ============================================
|
||||
// GRID HELPERS
|
||||
// ============================================
|
||||
/**
|
||||
* Create a date at a specific time (minutes since midnight)
|
||||
* @param baseDate - Base date (date component)
|
||||
* @param totalMinutes - Minutes since midnight
|
||||
* @returns New date with specified time
|
||||
*/
|
||||
createDateAtTime(baseDate, totalMinutes) {
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate();
|
||||
}
|
||||
/**
|
||||
* Snap date to nearest interval
|
||||
* @param date - Date to snap
|
||||
* @param intervalMinutes - Snap interval in minutes
|
||||
* @returns Snapped date
|
||||
*/
|
||||
snapToInterval(date, intervalMinutes) {
|
||||
const minutes = this.getMinutesSinceMidnight(date);
|
||||
const snappedMinutes = Math.round(minutes / intervalMinutes) * intervalMinutes;
|
||||
return this.createDateAtTime(date, snappedMinutes);
|
||||
}
|
||||
// ============================================
|
||||
// UTILITY METHODS
|
||||
// ============================================
|
||||
/**
|
||||
* Check if two dates are the same day
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns True if same day
|
||||
*/
|
||||
isSameDay(date1, date2) {
|
||||
return dayjs(date1).isSame(date2, 'day');
|
||||
}
|
||||
/**
|
||||
* Get start of day
|
||||
* @param date - Date
|
||||
* @returns Start of day (00:00:00)
|
||||
*/
|
||||
startOfDay(date) {
|
||||
return dayjs(date).startOf('day').toDate();
|
||||
}
|
||||
/**
|
||||
* Get end of day
|
||||
* @param date - Date
|
||||
* @returns End of day (23:59:59.999)
|
||||
*/
|
||||
endOfDay(date) {
|
||||
return dayjs(date).endOf('day').toDate();
|
||||
}
|
||||
/**
|
||||
* Add days to a date
|
||||
* @param date - Base date
|
||||
* @param days - Number of days to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addDays(date, days) {
|
||||
return dayjs(date).add(days, 'day').toDate();
|
||||
}
|
||||
/**
|
||||
* Add minutes to a date
|
||||
* @param date - Base date
|
||||
* @param minutes - Number of minutes to add (can be negative)
|
||||
* @returns New date
|
||||
*/
|
||||
addMinutes(date, minutes) {
|
||||
return dayjs(date).add(minutes, 'minute').toDate();
|
||||
}
|
||||
/**
|
||||
* Parse ISO string to date
|
||||
* @param isoString - ISO date string
|
||||
* @returns Parsed date
|
||||
*/
|
||||
parseISO(isoString) {
|
||||
return dayjs(isoString).toDate();
|
||||
}
|
||||
/**
|
||||
* Check if date is valid
|
||||
* @param date - Date to check
|
||||
* @returns True if valid
|
||||
*/
|
||||
isValid(date) {
|
||||
return dayjs(date).isValid();
|
||||
}
|
||||
/**
|
||||
* Calculate difference in calendar days between two dates
|
||||
* @param date1 - First date
|
||||
* @param date2 - Second date
|
||||
* @returns Number of calendar days between dates (can be negative)
|
||||
*/
|
||||
differenceInCalendarDays(date1, date2) {
|
||||
const d1 = dayjs(date1).startOf('day');
|
||||
const d2 = dayjs(date2).startOf('day');
|
||||
return d1.diff(d2, 'day');
|
||||
}
|
||||
/**
|
||||
* Validate date range (start must be before or equal to end)
|
||||
* @param start - Start date
|
||||
* @param end - End date
|
||||
* @returns True if valid range
|
||||
*/
|
||||
isValidRange(start, end) {
|
||||
if (!this.isValid(start) || !this.isValid(end)) {
|
||||
return false;
|
||||
}
|
||||
return start.getTime() <= end.getTime();
|
||||
}
|
||||
/**
|
||||
* Check if date is within reasonable bounds (1900-2100)
|
||||
* @param date - Date to check
|
||||
* @returns True if within bounds
|
||||
*/
|
||||
isWithinBounds(date) {
|
||||
if (!this.isValid(date)) {
|
||||
return false;
|
||||
}
|
||||
const year = date.getFullYear();
|
||||
return year >= 1900 && year <= 2100;
|
||||
}
|
||||
/**
|
||||
* Validate date with comprehensive checks
|
||||
* @param date - Date to validate
|
||||
* @param options - Validation options
|
||||
* @returns Validation result with error message
|
||||
*/
|
||||
validateDate(date, options = {}) {
|
||||
if (!this.isValid(date)) {
|
||||
return { valid: false, error: 'Invalid date' };
|
||||
}
|
||||
if (!this.isWithinBounds(date)) {
|
||||
return { valid: false, error: 'Date out of bounds (1900-2100)' };
|
||||
}
|
||||
const now = new Date();
|
||||
if (options.requireFuture && date <= now) {
|
||||
return { valid: false, error: 'Date must be in the future' };
|
||||
}
|
||||
if (options.requirePast && date >= now) {
|
||||
return { valid: false, error: 'Date must be in the past' };
|
||||
}
|
||||
if (options.minDate && date < options.minDate) {
|
||||
return { valid: false, error: `Date must be after ${this.formatDate(options.minDate)}` };
|
||||
}
|
||||
if (options.maxDate && date > options.maxDate) {
|
||||
return { valid: false, error: `Date must be before ${this.formatDate(options.maxDate)}` };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=DateService.js.map
|
||||
1
wwwroot/js/utils/DateService.js.map
Normal file
1
wwwroot/js/utils/DateService.js.map
Normal file
File diff suppressed because one or more lines are too long
33
wwwroot/js/utils/OverlapDetector.d.ts
vendored
Normal file
33
wwwroot/js/utils/OverlapDetector.d.ts
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* OverlapDetector - Ren tidbaseret overlap detection
|
||||
* Ingen DOM manipulation, kun tidsberegninger
|
||||
*/
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
export type EventId = string & {
|
||||
readonly __brand: 'EventId';
|
||||
};
|
||||
export type OverlapResult = {
|
||||
overlappingEvents: CalendarEvent[];
|
||||
stackLinks: Map<EventId, StackLink>;
|
||||
};
|
||||
export interface StackLink {
|
||||
prev?: EventId;
|
||||
next?: EventId;
|
||||
stackLevel: number;
|
||||
}
|
||||
export declare class OverlapDetector {
|
||||
/**
|
||||
* Resolver hvilke events et givent event overlapper med i en kolonne
|
||||
* @param event - CalendarEvent der skal checkes for overlap
|
||||
* @param columnEvents - Array af CalendarEvent objekter i kolonnen
|
||||
* @returns Array af events som det givne event overlapper med
|
||||
*/
|
||||
resolveOverlap(event: CalendarEvent, columnEvents: CalendarEvent[]): CalendarEvent[];
|
||||
/**
|
||||
* Dekorerer events med stack linking data
|
||||
* @param newEvent - Det nye event der skal tilføjes
|
||||
* @param overlappingEvents - Events som det nye event overlapper med
|
||||
* @returns OverlapResult med overlappende events og stack links
|
||||
*/
|
||||
decorateWithStackLinks(newEvent: CalendarEvent, overlappingEvents: CalendarEvent[]): OverlapResult;
|
||||
}
|
||||
52
wwwroot/js/utils/OverlapDetector.js
Normal file
52
wwwroot/js/utils/OverlapDetector.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* OverlapDetector - Ren tidbaseret overlap detection
|
||||
* Ingen DOM manipulation, kun tidsberegninger
|
||||
*/
|
||||
export class OverlapDetector {
|
||||
/**
|
||||
* Resolver hvilke events et givent event overlapper med i en kolonne
|
||||
* @param event - CalendarEvent der skal checkes for overlap
|
||||
* @param columnEvents - Array af CalendarEvent objekter i kolonnen
|
||||
* @returns Array af events som det givne event overlapper med
|
||||
*/
|
||||
resolveOverlap(event, columnEvents) {
|
||||
return columnEvents.filter(existingEvent => {
|
||||
// To events overlapper hvis:
|
||||
// event starter før existing slutter OG
|
||||
// event slutter efter existing starter
|
||||
return event.start < existingEvent.end && event.end > existingEvent.start;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Dekorerer events med stack linking data
|
||||
* @param newEvent - Det nye event der skal tilføjes
|
||||
* @param overlappingEvents - Events som det nye event overlapper med
|
||||
* @returns OverlapResult med overlappende events og stack links
|
||||
*/
|
||||
decorateWithStackLinks(newEvent, overlappingEvents) {
|
||||
const stackLinks = new Map();
|
||||
if (overlappingEvents.length === 0) {
|
||||
return {
|
||||
overlappingEvents: [],
|
||||
stackLinks
|
||||
};
|
||||
}
|
||||
// Kombiner nyt event med eksisterende og sortér efter start tid (tidligste første)
|
||||
const allEvents = [...overlappingEvents, newEvent].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||
// Opret sammenhængende kæde - alle events bindes sammen
|
||||
allEvents.forEach((event, index) => {
|
||||
const stackLink = {
|
||||
stackLevel: index,
|
||||
prev: index > 0 ? allEvents[index - 1].id : undefined,
|
||||
next: index < allEvents.length - 1 ? allEvents[index + 1].id : undefined
|
||||
};
|
||||
stackLinks.set(event.id, stackLink);
|
||||
});
|
||||
overlappingEvents.push(newEvent);
|
||||
return {
|
||||
overlappingEvents,
|
||||
stackLinks
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=OverlapDetector.js.map
|
||||
1
wwwroot/js/utils/OverlapDetector.js.map
Normal file
1
wwwroot/js/utils/OverlapDetector.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"OverlapDetector.js","sourceRoot":"","sources":["../../../src/utils/OverlapDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,MAAM,OAAO,eAAe;IAE1B;;;;;OAKG;IACI,cAAc,CAAC,KAAoB,EAAE,YAA6B;QACvE,OAAO,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACzC,6BAA6B;YAC7B,wCAAwC;YACxC,uCAAuC;YACvC,OAAO,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACI,sBAAsB,CAAC,QAAuB,EAAE,iBAAkC;QACvF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;QAEjD,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,iBAAiB,EAAE,EAAE;gBACrB,UAAU;aACX,CAAC;QACJ,CAAC;QAED,mFAAmF;QACnF,MAAM,SAAS,GAAG,CAAC,GAAG,iBAAiB,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/D,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CACtC,CAAC;QAEF,wDAAwD;QACxD,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,SAAS,GAAc;gBAC3B,UAAU,EAAE,KAAK;gBACjB,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAa,CAAC,CAAC,CAAC,SAAS;gBAChE,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAa,CAAC,CAAC,CAAC,SAAS;aACpF,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAa,EAAE,SAAS,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QACH,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO;YACL,iBAAiB;YACjB,UAAU;SACX,CAAC;IACJ,CAAC;CACF"}
|
||||
101
wwwroot/js/utils/PositionUtils.d.ts
vendored
Normal file
101
wwwroot/js/utils/PositionUtils.d.ts
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { Configuration } from '../configurations/CalendarConfig';
|
||||
import { IColumnBounds } from './ColumnDetectionUtils';
|
||||
import { DateService } from './DateService';
|
||||
/**
|
||||
* 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 declare class PositionUtils {
|
||||
private dateService;
|
||||
private config;
|
||||
constructor(dateService: DateService, config: Configuration);
|
||||
/**
|
||||
* Convert minutes to pixels
|
||||
*/
|
||||
minutesToPixels(minutes: number): number;
|
||||
/**
|
||||
* Convert pixels to minutes
|
||||
*/
|
||||
pixelsToMinutes(pixels: number): number;
|
||||
/**
|
||||
* Convert time (HH:MM) to pixels from day start using DateService
|
||||
*/
|
||||
timeToPixels(timeString: string): number;
|
||||
/**
|
||||
* Convert Date object to pixels from day start using DateService
|
||||
*/
|
||||
dateToPixels(date: Date): number;
|
||||
/**
|
||||
* Convert pixels to time using DateService
|
||||
*/
|
||||
pixelsToTime(pixels: number): string;
|
||||
/**
|
||||
* Beregn event position og størrelse
|
||||
*/
|
||||
calculateEventPosition(startTime: string | Date, endTime: string | Date): {
|
||||
top: number;
|
||||
height: number;
|
||||
duration: number;
|
||||
};
|
||||
/**
|
||||
* Snap position til grid interval
|
||||
*/
|
||||
snapToGrid(pixels: number): number;
|
||||
/**
|
||||
* Snap time to interval using DateService
|
||||
*/
|
||||
snapTimeToInterval(timeString: string): string;
|
||||
/**
|
||||
* Beregn kolonne position for overlappende events
|
||||
*/
|
||||
calculateColumnPosition(eventIndex: number, totalColumns: number, containerWidth: number): {
|
||||
left: number;
|
||||
width: number;
|
||||
};
|
||||
/**
|
||||
* Check om to events overlapper i tid
|
||||
*/
|
||||
eventsOverlap(start1: string | Date, end1: string | Date, start2: string | Date, end2: string | Date): boolean;
|
||||
/**
|
||||
* Beregn Y position fra mouse/touch koordinat
|
||||
*/
|
||||
getPositionFromCoordinate(clientY: number, column: IColumnBounds): number;
|
||||
/**
|
||||
* Valider at tid er inden for arbejdstimer
|
||||
*/
|
||||
isWithinWorkHours(timeString: string): boolean;
|
||||
/**
|
||||
* Valider at tid er inden for dag grænser
|
||||
*/
|
||||
isWithinDayBounds(timeString: string): boolean;
|
||||
/**
|
||||
* Hent minimum event højde i pixels
|
||||
*/
|
||||
getMinimumEventHeight(): number;
|
||||
/**
|
||||
* Hent maksimum event højde i pixels (hele dagen)
|
||||
*/
|
||||
getMaximumEventHeight(): number;
|
||||
/**
|
||||
* Beregn total kalender højde
|
||||
*/
|
||||
getTotalCalendarHeight(): number;
|
||||
/**
|
||||
* Convert ISO datetime to time string with UTC-to-local conversion
|
||||
*/
|
||||
isoToTimeString(isoString: string): string;
|
||||
/**
|
||||
* Convert time string to ISO datetime using DateService with timezone handling
|
||||
*/
|
||||
timeStringToIso(timeString: string, date?: Date): string;
|
||||
/**
|
||||
* Calculate event duration using DateService
|
||||
*/
|
||||
calculateDuration(startTime: string | Date, endTime: string | Date): number;
|
||||
/**
|
||||
* Format duration to readable text (Danish)
|
||||
*/
|
||||
formatDuration(minutes: number): string;
|
||||
}
|
||||
209
wwwroot/js/utils/PositionUtils.js
Normal file
209
wwwroot/js/utils/PositionUtils.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
import { TimeFormatter } from './TimeFormatter';
|
||||
/**
|
||||
* 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 {
|
||||
constructor(dateService, config) {
|
||||
this.dateService = dateService;
|
||||
this.config = config;
|
||||
}
|
||||
/**
|
||||
* Convert minutes to pixels
|
||||
*/
|
||||
minutesToPixels(minutes) {
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const pixelsPerHour = gridSettings.hourHeight;
|
||||
return (minutes / 60) * pixelsPerHour;
|
||||
}
|
||||
/**
|
||||
* Convert pixels to minutes
|
||||
*/
|
||||
pixelsToMinutes(pixels) {
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const pixelsPerHour = gridSettings.hourHeight;
|
||||
return (pixels / pixelsPerHour) * 60;
|
||||
}
|
||||
/**
|
||||
* Convert time (HH:MM) to pixels from day start using DateService
|
||||
*/
|
||||
timeToPixels(timeString) {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
||||
return this.minutesToPixels(minutesFromDayStart);
|
||||
}
|
||||
/**
|
||||
* Convert Date object to pixels from day start using DateService
|
||||
*/
|
||||
dateToPixels(date) {
|
||||
const totalMinutes = this.dateService.getMinutesSinceMidnight(date);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const minutesFromDayStart = totalMinutes - dayStartMinutes;
|
||||
return this.minutesToPixels(minutesFromDayStart);
|
||||
}
|
||||
/**
|
||||
* Convert pixels to time using DateService
|
||||
*/
|
||||
pixelsToTime(pixels) {
|
||||
const minutes = this.pixelsToMinutes(pixels);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const dayStartMinutes = gridSettings.dayStartHour * 60;
|
||||
const totalMinutes = dayStartMinutes + minutes;
|
||||
return this.dateService.minutesToTime(totalMinutes);
|
||||
}
|
||||
/**
|
||||
* Beregn event position og størrelse
|
||||
*/
|
||||
calculateEventPosition(startTime, endTime) {
|
||||
let startPixels;
|
||||
let endPixels;
|
||||
if (typeof startTime === 'string') {
|
||||
startPixels = this.timeToPixels(startTime);
|
||||
}
|
||||
else {
|
||||
startPixels = this.dateToPixels(startTime);
|
||||
}
|
||||
if (typeof endTime === 'string') {
|
||||
endPixels = this.timeToPixels(endTime);
|
||||
}
|
||||
else {
|
||||
endPixels = this.dateToPixels(endTime);
|
||||
}
|
||||
const height = Math.max(endPixels - startPixels, this.getMinimumEventHeight());
|
||||
const duration = this.pixelsToMinutes(height);
|
||||
return {
|
||||
top: startPixels,
|
||||
height,
|
||||
duration
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Snap position til grid interval
|
||||
*/
|
||||
snapToGrid(pixels) {
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const snapInterval = gridSettings.snapInterval;
|
||||
const snapPixels = this.minutesToPixels(snapInterval);
|
||||
return Math.round(pixels / snapPixels) * snapPixels;
|
||||
}
|
||||
/**
|
||||
* Snap time to interval using DateService
|
||||
*/
|
||||
snapTimeToInterval(timeString) {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const snapInterval = gridSettings.snapInterval;
|
||||
const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval;
|
||||
return this.dateService.minutesToTime(snappedMinutes);
|
||||
}
|
||||
/**
|
||||
* Beregn kolonne position for overlappende events
|
||||
*/
|
||||
calculateColumnPosition(eventIndex, totalColumns, containerWidth) {
|
||||
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
|
||||
*/
|
||||
eventsOverlap(start1, end1, 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
|
||||
*/
|
||||
getPositionFromCoordinate(clientY, column) {
|
||||
const relativeY = clientY - column.boundingClientRect.top;
|
||||
// Snap til grid
|
||||
return this.snapToGrid(relativeY);
|
||||
}
|
||||
/**
|
||||
* Valider at tid er inden for arbejdstimer
|
||||
*/
|
||||
isWithinWorkHours(timeString) {
|
||||
const [hours] = timeString.split(':').map(Number);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
return hours >= gridSettings.workStartHour && hours < gridSettings.workEndHour;
|
||||
}
|
||||
/**
|
||||
* Valider at tid er inden for dag grænser
|
||||
*/
|
||||
isWithinDayBounds(timeString) {
|
||||
const [hours] = timeString.split(':').map(Number);
|
||||
const gridSettings = this.config.gridSettings;
|
||||
return hours >= gridSettings.dayStartHour && hours < gridSettings.dayEndHour;
|
||||
}
|
||||
/**
|
||||
* Hent minimum event højde i pixels
|
||||
*/
|
||||
getMinimumEventHeight() {
|
||||
// Minimum 15 minutter
|
||||
return this.minutesToPixels(15);
|
||||
}
|
||||
/**
|
||||
* Hent maksimum event højde i pixels (hele dagen)
|
||||
*/
|
||||
getMaximumEventHeight() {
|
||||
const gridSettings = this.config.gridSettings;
|
||||
const dayDurationHours = gridSettings.dayEndHour - gridSettings.dayStartHour;
|
||||
return dayDurationHours * gridSettings.hourHeight;
|
||||
}
|
||||
/**
|
||||
* Beregn total kalender højde
|
||||
*/
|
||||
getTotalCalendarHeight() {
|
||||
return this.getMaximumEventHeight();
|
||||
}
|
||||
/**
|
||||
* Convert ISO datetime to time string with UTC-to-local conversion
|
||||
*/
|
||||
isoToTimeString(isoString) {
|
||||
const date = new Date(isoString);
|
||||
return TimeFormatter.formatTime(date);
|
||||
}
|
||||
/**
|
||||
* Convert time string to ISO datetime using DateService with timezone handling
|
||||
*/
|
||||
timeStringToIso(timeString, date = new Date()) {
|
||||
const totalMinutes = this.dateService.timeToMinutes(timeString);
|
||||
const newDate = this.dateService.createDateAtTime(date, totalMinutes);
|
||||
return this.dateService.toUTC(newDate);
|
||||
}
|
||||
/**
|
||||
* Calculate event duration using DateService
|
||||
*/
|
||||
calculateDuration(startTime, endTime) {
|
||||
return this.dateService.getDurationMinutes(startTime, endTime);
|
||||
}
|
||||
/**
|
||||
* Format duration to readable text (Danish)
|
||||
*/
|
||||
formatDuration(minutes) {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=PositionUtils.js.map
|
||||
1
wwwroot/js/utils/PositionUtils.js.map
Normal file
1
wwwroot/js/utils/PositionUtils.js.map
Normal file
File diff suppressed because one or more lines are too long
45
wwwroot/js/utils/TimeFormatter.d.ts
vendored
Normal file
45
wwwroot/js/utils/TimeFormatter.d.ts
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* TimeFormatter - Centralized time formatting with timezone support
|
||||
* Now uses DateService internally for all date/time operations
|
||||
*
|
||||
* Handles conversion from UTC/Zulu time to configured timezone (default: Europe/Copenhagen)
|
||||
* Supports both 12-hour and 24-hour format configuration
|
||||
*
|
||||
* All events in the system are stored in UTC and must be converted to local timezone
|
||||
*/
|
||||
import { ITimeFormatConfig } from '../configurations/TimeFormatConfig';
|
||||
export declare class TimeFormatter {
|
||||
private static settings;
|
||||
private static dateService;
|
||||
private static getDateService;
|
||||
/**
|
||||
* Configure time formatting settings
|
||||
* Must be called before using TimeFormatter
|
||||
*/
|
||||
static configure(settings: ITimeFormatConfig): void;
|
||||
/**
|
||||
* Convert UTC date to configured timezone (internal helper)
|
||||
* @param utcDate - Date in UTC (or ISO string)
|
||||
* @returns Date object adjusted to configured timezone
|
||||
*/
|
||||
private static convertToLocalTime;
|
||||
/**
|
||||
* Format time in 24-hour format using DateService (internal helper)
|
||||
* @param date - Date to format
|
||||
* @returns Formatted time string (e.g., "09:00")
|
||||
*/
|
||||
private static format24Hour;
|
||||
/**
|
||||
* Format time according to current configuration
|
||||
* @param date - Date to format
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
static formatTime(date: Date): string;
|
||||
/**
|
||||
* Format time range (start - end) using DateService
|
||||
* @param startDate - Start date
|
||||
* @param endDate - End date
|
||||
* @returns Formatted time range string (e.g., "09:00 - 10:30")
|
||||
*/
|
||||
static formatTimeRange(startDate: Date, endDate: Date): string;
|
||||
}
|
||||
92
wwwroot/js/utils/TimeFormatter.js
Normal file
92
wwwroot/js/utils/TimeFormatter.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* TimeFormatter - Centralized time formatting with timezone support
|
||||
* Now uses DateService internally for all date/time operations
|
||||
*
|
||||
* Handles conversion from UTC/Zulu time to configured timezone (default: Europe/Copenhagen)
|
||||
* Supports both 12-hour and 24-hour format configuration
|
||||
*
|
||||
* All events in the system are stored in UTC and must be converted to local timezone
|
||||
*/
|
||||
import { DateService } from './DateService';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
// Enable day.js plugins for timezone formatting
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
export class TimeFormatter {
|
||||
static getDateService() {
|
||||
if (!TimeFormatter.dateService) {
|
||||
if (!TimeFormatter.settings) {
|
||||
throw new Error('TimeFormatter must be configured before use. Call TimeFormatter.configure() first.');
|
||||
}
|
||||
// Create a minimal config object for DateService
|
||||
const config = {
|
||||
timeFormatConfig: {
|
||||
timezone: TimeFormatter.settings.timezone
|
||||
}
|
||||
};
|
||||
TimeFormatter.dateService = new DateService(config);
|
||||
}
|
||||
return TimeFormatter.dateService;
|
||||
}
|
||||
/**
|
||||
* Configure time formatting settings
|
||||
* Must be called before using TimeFormatter
|
||||
*/
|
||||
static configure(settings) {
|
||||
TimeFormatter.settings = settings;
|
||||
// Reset DateService to pick up new timezone
|
||||
TimeFormatter.dateService = null;
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
if (typeof utcDate === 'string') {
|
||||
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.getDateService().fromUTC(utcString);
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
if (!TimeFormatter.settings) {
|
||||
throw new Error('TimeFormatter must be configured before use. Call TimeFormatter.configure() first.');
|
||||
}
|
||||
// Use day.js directly to format with timezone awareness
|
||||
const pattern = TimeFormatter.settings.showSeconds ? 'HH:mm:ss' : 'HH:mm';
|
||||
return dayjs.utc(date).tz(TimeFormatter.settings.timezone).format(pattern);
|
||||
}
|
||||
/**
|
||||
* Format time according to current configuration
|
||||
* @param date - Date to format
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
static formatTime(date) {
|
||||
// Always use 24-hour format (12-hour support removed as unused)
|
||||
return TimeFormatter.format24Hour(date);
|
||||
}
|
||||
/**
|
||||
* Format time range (start - end) using DateService
|
||||
* @param startDate - Start date
|
||||
* @param endDate - End date
|
||||
* @returns Formatted time range string (e.g., "09:00 - 10:30")
|
||||
*/
|
||||
static formatTimeRange(startDate, endDate) {
|
||||
const localStart = TimeFormatter.convertToLocalTime(startDate);
|
||||
const localEnd = TimeFormatter.convertToLocalTime(endDate);
|
||||
return TimeFormatter.getDateService().formatTimeRange(localStart, localEnd);
|
||||
}
|
||||
}
|
||||
TimeFormatter.settings = null;
|
||||
// DateService will be initialized lazily to avoid circular dependency with CalendarConfig
|
||||
TimeFormatter.dateService = null;
|
||||
//# sourceMappingURL=TimeFormatter.js.map
|
||||
1
wwwroot/js/utils/TimeFormatter.js.map
Normal file
1
wwwroot/js/utils/TimeFormatter.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"TimeFormatter.js","sourceRoot":"","sources":["../../../src/utils/TimeFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,kBAAkB,CAAC;AACnC,OAAO,QAAQ,MAAM,uBAAuB,CAAC;AAE7C,gDAAgD;AAChD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAClB,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,OAAO,aAAa;IAMhB,MAAM,CAAC,cAAc;QAC3B,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;YACxG,CAAC;YACD,iDAAiD;YACjD,MAAM,MAAM,GAAG;gBACb,gBAAgB,EAAE;oBAChB,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,QAAQ;iBAC1C;aACF,CAAC;YACF,aAAa,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,MAAa,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,aAAa,CAAC,WAAW,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,QAA2B;QAC1C,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAClC,4CAA4C;QAC5C,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB,CAAC,OAAsB;QACtD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACzD,CAAC;QAED,iFAAiF;QACjF,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACxC,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3D,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,YAAY,CAAC,IAAU;QACpC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACxG,CAAC;QAED,wDAAwD;QACxD,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;QAC1E,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7E,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,IAAU;QAC1B,gEAAgE;QAChE,OAAO,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,eAAe,CAAC,SAAe,EAAE,OAAa;QACnD,MAAM,UAAU,GAAG,aAAa,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC3D,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC;;AAjFc,sBAAQ,GAA6B,IAAI,CAAC;AAEzD,0FAA0F;AAC3E,yBAAW,GAAuB,IAAI,CAAC"}
|
||||
29
wwwroot/js/utils/URLManager.d.ts
vendored
Normal file
29
wwwroot/js/utils/URLManager.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { IEventBus } from '../types/CalendarTypes';
|
||||
/**
|
||||
* URLManager handles URL query parameter parsing and deep linking functionality
|
||||
* Follows event-driven architecture with no global state
|
||||
*/
|
||||
export declare class URLManager {
|
||||
private eventBus;
|
||||
constructor(eventBus: IEventBus);
|
||||
/**
|
||||
* Parse eventId from URL query parameters
|
||||
* @returns eventId string or null if not found
|
||||
*/
|
||||
parseEventIdFromURL(): string | null;
|
||||
/**
|
||||
* Get all query parameters as an object
|
||||
* @returns object with all query parameters
|
||||
*/
|
||||
getAllQueryParams(): Record<string, string>;
|
||||
/**
|
||||
* Update URL without page reload (for future use)
|
||||
* @param params object with parameters to update
|
||||
*/
|
||||
updateURL(params: Record<string, string | null>): void;
|
||||
/**
|
||||
* Check if current URL has any query parameters
|
||||
* @returns true if URL has query parameters
|
||||
*/
|
||||
hasQueryParams(): boolean;
|
||||
}
|
||||
76
wwwroot/js/utils/URLManager.js
Normal file
76
wwwroot/js/utils/URLManager.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* URLManager handles URL query parameter parsing and deep linking functionality
|
||||
* Follows event-driven architecture with no global state
|
||||
*/
|
||||
export class URLManager {
|
||||
constructor(eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
/**
|
||||
* Parse eventId from URL query parameters
|
||||
* @returns eventId string or null if not found
|
||||
*/
|
||||
parseEventIdFromURL() {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const eventId = urlParams.get('eventId');
|
||||
if (eventId && eventId.trim() !== '') {
|
||||
return eventId.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('URLManager: Failed to parse URL parameters:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get all query parameters as an object
|
||||
* @returns object with all query parameters
|
||||
*/
|
||||
getAllQueryParams() {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const params = {};
|
||||
for (const [key, value] of urlParams.entries()) {
|
||||
params[key] = value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('URLManager: Failed to parse URL parameters:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update URL without page reload (for future use)
|
||||
* @param params object with parameters to update
|
||||
*/
|
||||
updateURL(params) {
|
||||
try {
|
||||
const url = new URL(window.location.href);
|
||||
// Update or remove parameters
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value === null) {
|
||||
url.searchParams.delete(key);
|
||||
}
|
||||
else {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
// Update URL without page reload
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('URLManager: Failed to update URL:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if current URL has any query parameters
|
||||
* @returns true if URL has query parameters
|
||||
*/
|
||||
hasQueryParams() {
|
||||
return window.location.search.length > 0;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=URLManager.js.map
|
||||
1
wwwroot/js/utils/URLManager.js.map
Normal file
1
wwwroot/js/utils/URLManager.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"URLManager.js","sourceRoot":"","sources":["../../../src/utils/URLManager.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,OAAO,UAAU;IAGnB,YAAY,QAAmB;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACI,mBAAmB;QACtB,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAEzC,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;YAC1B,CAAC;YAED,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,iBAAiB;QACpB,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,MAAM,MAAM,GAA2B,EAAE,CAAC;YAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACxB,CAAC;YAED,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;YACnE,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,SAAS,CAAC,MAAqC;QAClD,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAE1C,8BAA8B;YAC9B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;gBAC5C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACjB,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACJ,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACrC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,iCAAiC;YACjC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,cAAc;QACjB,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7C,CAAC;CACJ"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue