Batch update, WIP

This commit is contained in:
Janus C. H. Knudsen 2025-11-03 22:04:37 +01:00
parent 8ec5f52872
commit 989c9bd69d
25 changed files with 68 additions and 123 deletions

View file

@ -97,68 +97,12 @@ export class Configuration {
return Configuration._instance;
}
// Computed properties
get minuteHeight(): number {
return this.gridSettings.hourHeight / 60;
}
get totalHours(): number {
return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour;
}
get totalMinutes(): number {
return this.totalHours * 60;
}
get slotsPerHour(): number {
return 60 / this.gridSettings.snapInterval;
}
get totalSlots(): number {
return this.totalHours * this.slotsPerHour;
}
get slotHeight(): number {
return this.gridSettings.hourHeight / this.slotsPerHour;
}
// Backward compatibility getters
getGridSettings(): IGridSettings {
return this.gridSettings;
}
getDateViewSettings(): IDateViewSettings {
return this.dateViewSettings;
}
// Helper methods
getWorkWeekSettings(): IWorkWeekSettings {
return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard'];
}
getCurrentWorkWeek(): string {
return this.currentWorkWeek;
}
getTimezone(): string {
return this.timeFormatConfig.timezone;
}
getLocale(): string {
return this.timeFormatConfig.locale;
}
getTimeFormatSettings(): ITimeFormatConfig {
return this.timeFormatConfig;
}
is24HourFormat(): boolean {
return this.timeFormatConfig.use24HourFormat;
}
getDateFormat(): 'locale' | 'technical' {
return this.timeFormatConfig.dateFormat;
}
setWorkWeek(workWeekId: string): void {
if (WORK_WEEK_PRESETS[workWeekId]) {
this.currentWorkWeek = workWeekId;
@ -169,10 +113,6 @@ export class Configuration {
setSelectedDate(date: Date): void {
this.selectedDate = date;
}
isValidSnapInterval(interval: number): boolean {
return [5, 10, 15, 30, 60].includes(interval);
}
}
// Backward compatibility alias

View file

@ -14,3 +14,12 @@ export interface IGridSettings {
showCurrentTime: boolean;
showWorkHours: boolean;
}
/**
* Grid settings utility functions
*/
export namespace GridSettingsUtils {
export function isValidSnapInterval(interval: number): boolean {
return [5, 10, 15, 30, 60].includes(interval);
}
}

View file

@ -1,5 +1,5 @@
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { TimeFormatter } from '../utils/TimeFormatter';
import { PositionUtils } from '../utils/PositionUtils';
import { DateService } from '../utils/DateService';
@ -137,7 +137,7 @@ export class SwpEventElement extends BaseSwpEventElement {
this.style.height = `${newHeight}px`;
// 2. Calculate new end time based on height
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const { hourHeight, snapInterval } = gridSettings;
// Get current start time
@ -230,7 +230,7 @@ export class SwpEventElement extends BaseSwpEventElement {
* Calculate start/end minutes from Y position
*/
private calculateTimesFromPosition(snappedY: number): { startMinutes: number; endMinutes: number } {
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const { hourHeight, dayStartHour, snapInterval } = gridSettings;
// Get original duration

View file

@ -1,8 +1,8 @@
// Main entry point for Calendar Plantempus
import { Container } from '@novadi/core';
import { eventBus } from './core/EventBus';
import { ConfigManager } from './configuration/ConfigManager';
import { Configuration } from './configuration/CalendarConfig';
import { ConfigManager } from './configurations/ConfigManager';
import { Configuration } from './configurations/CalendarConfig';
import { URLManager } from './utils/URLManager';
import { IEventBus } from './types/CalendarTypes';

View file

@ -1,7 +1,7 @@
// All-day row height management and animations
import { eventBus } from '../core/EventBus';
import { ALL_DAY_CONSTANTS } from '../configuration/CalendarConfig';
import { ALL_DAY_CONSTANTS } from '../configurations/CalendarConfig';
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
import { AllDayLayoutEngine, IEventLayout } from '../utils/AllDayLayoutEngine';
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';

View file

@ -1,5 +1,5 @@
import { CoreEvents } from '../constants/CoreEvents';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { EventManager } from './EventManager';
import { GridManager } from './GridManager';
@ -206,7 +206,7 @@ export class CalendarManager {
this.eventBus.emit('workweek:header-update', {
currentDate: this.currentDate,
currentView: this.currentView,
workweek: this.config.getCurrentWorkWeek()
workweek: this.config.currentWorkWeek
});
}

View file

@ -8,7 +8,7 @@
import { ICalendarEvent } from '../types/CalendarTypes';
import { EventStackManager, IEventGroup, IStackLink } from './EventStackManager';
import { PositionUtils } from '../utils/PositionUtils';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
export interface IGridGroupLayout {
events: ICalendarEvent[];
@ -59,7 +59,7 @@ export class EventLayoutCoordinator {
// Find events that could be in GRID with first event
// Use expanding search to find chains (A→B→C where each conflicts with next)
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const thresholdMinutes = gridSettings.gridStartThresholdMinutes;
// Use refactored method for expanding grid candidates

View file

@ -1,6 +1,6 @@
import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { DateService } from '../utils/DateService';
import { IEventRepository } from '../repositories/IEventRepository';

View file

@ -14,7 +14,7 @@
*/
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
export interface IStackLink {
prev?: string; // Event ID of previous event in stack
@ -51,7 +51,7 @@ export class EventStackManager {
if (events.length === 0) return [];
// Get threshold from config
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const thresholdMinutes = gridSettings.gridStartThresholdMinutes;
// Sort events by start time

View file

@ -1,5 +1,5 @@
import { eventBus } from '../core/EventBus';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
import { IHeaderRenderer, IHeaderRenderContext } from '../renderers/DateHeaderRenderer';
import { IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IHeaderReadyEventPayload } from '../types/EventTypes';

View file

@ -1,6 +1,6 @@
import { eventBus } from '../core/EventBus';
import { CoreEvents } from '../constants/CoreEvents';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { IResizeEndEventPayload } from '../types/EventTypes';
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
@ -33,7 +33,7 @@ export class ResizeHandleManager {
constructor(config: Configuration) {
this.config = config;
const grid = this.config.getGridSettings();
const grid = this.config.gridSettings;
this.hourHeightPx = grid.hourHeight;
this.snapMin = grid.snapInterval;
this.minDurationMin = this.snapMin; // Use snap interval as minimum duration

View file

@ -1,5 +1,5 @@
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
@ -113,7 +113,7 @@ export class ViewManager {
this.updateButtonGroup(
this.getWorkweekButtons(),
'data-workweek',
this.config.getCurrentWorkWeek()
this.config.currentWorkWeek
);
}

View file

@ -1,7 +1,7 @@
// Work hours management for per-column scheduling
import { DateService } from '../utils/DateService';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { PositionUtils } from '../utils/PositionUtils';
/**
@ -102,7 +102,7 @@ export class WorkHoursManager {
return null; // Full day will be colored via CSS background
}
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const dayStartHour = gridSettings.dayStartHour;
const hourHeight = gridSettings.hourHeight;

View file

@ -1,6 +1,6 @@
// Column rendering strategy interface and implementations
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { DateService } from '../utils/DateService';
import { WorkHoursManager } from '../managers/WorkHoursManager';
@ -39,7 +39,7 @@ export class DateColumnRenderer implements IColumnRenderer {
const workWeekSettings = config.getWorkWeekSettings();
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
const dateSettings = config.getDateViewSettings();
const dateSettings = config.dateViewSettings;
const daysToShow = dates.slice(0, dateSettings.weekDays);

View file

@ -1,6 +1,6 @@
// Header rendering strategy interface and implementations
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { DateService } from '../utils/DateService';
/**
@ -33,13 +33,13 @@ export class DateHeaderRenderer implements IHeaderRenderer {
calendarHeader.appendChild(allDayContainer);
// Initialize date service with timezone and locale from config
const timezone = config.getTimezone();
const locale = config.getLocale();
const timezone = config.timeFormatConfig.timezone;
const locale = config.timeFormatConfig.locale;
this.dateService = new DateService(config);
const workWeekSettings = config.getWorkWeekSettings();
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
const weekDays = config.getDateViewSettings().weekDays;
const weekDays = config.dateViewSettings.weekDays;
const daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => {

View file

@ -1,7 +1,7 @@
// Event rendering strategy interface and implementations
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { SwpEventElement } from '../elements/SwpEventElement';
import { PositionUtils } from '../utils/PositionUtils';
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
@ -312,7 +312,7 @@ export class DateEventRenderer implements IEventRenderer {
// (e.g., if container starts at 07:00 and event starts at 08:15, offset = 75 min)
const timeDiffMs = event.start.getTime() - containerStart.getTime();
const timeDiffMinutes = timeDiffMs / (1000 * 60);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const relativeTop = timeDiffMinutes > 0 ? (timeDiffMinutes / 60) * gridSettings.hourHeight : 0;
// Events in grid columns are positioned absolutely within their column container

View file

@ -1,4 +1,4 @@
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { CalendarView } from '../types/CalendarTypes';
import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
import { eventBus } from '../core/EventBus';
@ -179,7 +179,7 @@ export class GridRenderer {
private createOptimizedTimeAxis(): HTMLElement {
const timeAxis = document.createElement('swp-time-axis');
const timeAxisContent = document.createElement('swp-time-axis-content');
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const startHour = gridSettings.dayStartHour;
const endHour = gridSettings.dayEndHour;

View file

@ -29,13 +29,13 @@ import {
fromZonedTime,
formatInTimeZone
} from 'date-fns-tz';
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
export class DateService {
private timezone: string;
constructor(config: Configuration) {
this.timezone = config.getTimezone();
this.timezone = config.timeFormatConfig.timezone;
}
// ============================================

View file

@ -1,4 +1,4 @@
import { Configuration } from '../configuration/CalendarConfig';
import { Configuration } from '../configurations/CalendarConfig';
import { IColumnBounds } from './ColumnDetectionUtils';
import { DateService } from './DateService';
import { TimeFormatter } from './TimeFormatter';
@ -22,7 +22,7 @@ export class PositionUtils {
* Convert minutes to pixels
*/
public minutesToPixels(minutes: number): number {
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const pixelsPerHour = gridSettings.hourHeight;
return (minutes / 60) * pixelsPerHour;
}
@ -31,7 +31,7 @@ export class PositionUtils {
* Convert pixels to minutes
*/
public pixelsToMinutes(pixels: number): number {
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const pixelsPerHour = gridSettings.hourHeight;
return (pixels / pixelsPerHour) * 60;
}
@ -41,7 +41,7 @@ export class PositionUtils {
*/
public timeToPixels(timeString: string): number {
const totalMinutes = this.dateService.timeToMinutes(timeString);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const dayStartMinutes = gridSettings.dayStartHour * 60;
const minutesFromDayStart = totalMinutes - dayStartMinutes;
@ -53,7 +53,7 @@ export class PositionUtils {
*/
public dateToPixels(date: Date): number {
const totalMinutes = this.dateService.getMinutesSinceMidnight(date);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const dayStartMinutes = gridSettings.dayStartHour * 60;
const minutesFromDayStart = totalMinutes - dayStartMinutes;
@ -65,7 +65,7 @@ export class PositionUtils {
*/
public pixelsToTime(pixels: number): string {
const minutes = this.pixelsToMinutes(pixels);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const dayStartMinutes = gridSettings.dayStartHour * 60;
const totalMinutes = dayStartMinutes + minutes;
@ -109,7 +109,7 @@ export class PositionUtils {
* Snap position til grid interval
*/
public snapToGrid(pixels: number): number {
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const snapInterval = gridSettings.snapInterval;
const snapPixels = this.minutesToPixels(snapInterval);
@ -121,7 +121,7 @@ export class PositionUtils {
*/
public snapTimeToInterval(timeString: string): string {
const totalMinutes = this.dateService.timeToMinutes(timeString);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const snapInterval = gridSettings.snapInterval;
const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval;
@ -182,7 +182,7 @@ export class PositionUtils {
*/
public isWithinWorkHours(timeString: string): boolean {
const [hours] = timeString.split(':').map(Number);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
return hours >= gridSettings.workStartHour && hours < gridSettings.workEndHour;
}
@ -191,7 +191,7 @@ export class PositionUtils {
*/
public isWithinDayBounds(timeString: string): boolean {
const [hours] = timeString.split(':').map(Number);
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
return hours >= gridSettings.dayStartHour && hours < gridSettings.dayEndHour;
}
@ -207,7 +207,7 @@ export class PositionUtils {
* Hent maksimum event højde i pixels (hele dagen)
*/
public getMaximumEventHeight(): number {
const gridSettings = this.config.getGridSettings();
const gridSettings = this.config.gridSettings;
const dayDurationHours = gridSettings.dayEndHour - gridSettings.dayStartHour;
return dayDurationHours * gridSettings.hourHeight;
}

View file

@ -9,32 +9,24 @@
*/
import { DateService } from './DateService';
export interface ITimeFormatSettings {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}
import { ITimeFormatConfig } from '../configurations/TimeFormatConfig';
export class TimeFormatter {
private static settings: ITimeFormatSettings = {
timezone: 'Europe/Copenhagen', // Default to Denmark
use24HourFormat: true, // 24-hour format standard in Denmark
locale: 'da-DK', // Danish locale
dateFormat: 'technical', // Use technical format yyyy-mm-dd hh:mm:ss
showSeconds: false // Don't show seconds by default
};
private static settings: ITimeFormatConfig | null = null;
// DateService will be initialized lazily to avoid circular dependency with CalendarConfig
private static dateService: DateService | null = null;
private static getDateService(): DateService {
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 = {
getTimezone: () => TimeFormatter.settings.timezone
timeFormatConfig: {
timezone: TimeFormatter.settings.timezone
}
};
TimeFormatter.dateService = new DateService(config as any);
}
@ -43,9 +35,10 @@ export class TimeFormatter {
/**
* Configure time formatting settings
* Must be called before using TimeFormatter
*/
static configure(settings: Partial<ITimeFormatSettings>): void {
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
static configure(settings: ITimeFormatConfig): void {
TimeFormatter.settings = settings;
// Reset DateService to pick up new timezone
TimeFormatter.dateService = null;
}
@ -71,6 +64,9 @@ export class TimeFormatter {
* @returns Formatted time string (e.g., "09:00")
*/
private static format24Hour(date: Date): string {
if (!TimeFormatter.settings) {
throw new Error('TimeFormatter must be configured before use. Call TimeFormatter.configure() first.');
}
const localDate = TimeFormatter.convertToLocalTime(date);
return TimeFormatter.getDateService().formatTime(localDate, TimeFormatter.settings.showSeconds);
}