Refactors date handling for ISO week compatibility
Centralizes all date calculations into a new `DateCalculator` class for better maintainability and consistency. Ensures correct ISO week handling (Monday as the first day) throughout the calendar. Updates `CalendarConfig` to use ISO day numbering (1-7 for Mon-Sun) for work week definitions. Fixes issue where date calculations were inconsistent. Enhances event rendering and navigation. Updates navigation logic to use pre-rendered events. Removes the need for `CONTAINER_READY_FOR_EVENTS` event.
This commit is contained in:
parent
efc1742dad
commit
7d513600d8
13 changed files with 230 additions and 343 deletions
|
|
@ -40,10 +40,9 @@ interface DateViewSettings {
|
||||||
*/
|
*/
|
||||||
interface WorkWeekSettings {
|
interface WorkWeekSettings {
|
||||||
id: string;
|
id: string;
|
||||||
workDays: number[]; // [1,2,3,4,5] for mon-fri
|
workDays: number[]; // ISO 8601: [1,2,3,4,5] for mon-fri (1=Mon, 7=Sun)
|
||||||
dayNames: string[]; // ['Mon','Tue','Wed','Thu','Fri']
|
|
||||||
totalDays: number; // 5
|
totalDays: number; // 5
|
||||||
firstWorkDay: number; // 1 = Monday
|
firstWorkDay: number; // ISO: 1 = Monday, 7 = Sunday
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -442,38 +441,33 @@ export class CalendarConfig {
|
||||||
return {
|
return {
|
||||||
'standard': {
|
'standard': {
|
||||||
id: 'standard',
|
id: 'standard',
|
||||||
workDays: [1,2,3,4,5],
|
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
|
||||||
dayNames: ['Mon','Tue','Wed','Thu','Fri'],
|
|
||||||
totalDays: 5,
|
totalDays: 5,
|
||||||
firstWorkDay: 1
|
firstWorkDay: 1
|
||||||
},
|
},
|
||||||
'compressed': {
|
'compressed': {
|
||||||
id: 'compressed',
|
id: 'compressed',
|
||||||
workDays: [1,2,3,4],
|
workDays: [1,2,3,4], // Monday-Thursday (ISO)
|
||||||
dayNames: ['Mon','Tue','Wed','Thu'],
|
|
||||||
totalDays: 4,
|
totalDays: 4,
|
||||||
firstWorkDay: 1
|
firstWorkDay: 1
|
||||||
},
|
},
|
||||||
'midweek': {
|
'midweek': {
|
||||||
id: 'midweek',
|
id: 'midweek',
|
||||||
workDays: [3,4,5],
|
workDays: [3,4,5], // Wednesday-Friday (ISO)
|
||||||
dayNames: ['Wed','Thu','Fri'],
|
|
||||||
totalDays: 3,
|
totalDays: 3,
|
||||||
firstWorkDay: 3
|
firstWorkDay: 3
|
||||||
},
|
},
|
||||||
'weekend': {
|
'weekend': {
|
||||||
id: 'weekend',
|
id: 'weekend',
|
||||||
workDays: [6,0],
|
workDays: [6,7], // Saturday-Sunday (ISO)
|
||||||
dayNames: ['Sat','Sun'],
|
|
||||||
totalDays: 2,
|
totalDays: 2,
|
||||||
firstWorkDay: 6
|
firstWorkDay: 6
|
||||||
},
|
},
|
||||||
'fullweek': {
|
'fullweek': {
|
||||||
id: 'fullweek',
|
id: 'fullweek',
|
||||||
workDays: [0,1,2,3,4,5,6],
|
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
|
||||||
dayNames: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
|
|
||||||
totalDays: 7,
|
totalDays: 7,
|
||||||
firstWorkDay: 0
|
firstWorkDay: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -511,6 +505,7 @@ export class CalendarConfig {
|
||||||
getCurrentWorkWeek(): string {
|
getCurrentWorkWeek(): string {
|
||||||
return this.currentWorkWeek;
|
return this.currentWorkWeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create singleton instance
|
// Create singleton instance
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { CalendarType } from '../types/CalendarTypes';
|
||||||
import { HeaderRenderer, DateHeaderRenderer, ResourceHeaderRenderer } from '../renderers/HeaderRenderer';
|
import { HeaderRenderer, DateHeaderRenderer, ResourceHeaderRenderer } from '../renderers/HeaderRenderer';
|
||||||
import { ColumnRenderer, DateColumnRenderer, ResourceColumnRenderer } from '../renderers/ColumnRenderer';
|
import { ColumnRenderer, DateColumnRenderer, ResourceColumnRenderer } from '../renderers/ColumnRenderer';
|
||||||
import { EventRendererStrategy, DateEventRenderer, ResourceEventRenderer } from '../renderers/EventRenderer';
|
import { EventRendererStrategy, DateEventRenderer, ResourceEventRenderer } from '../renderers/EventRenderer';
|
||||||
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderer configuration for a calendar type
|
* Renderer configuration for a calendar type
|
||||||
|
|
@ -34,13 +35,13 @@ export class CalendarTypeFactory {
|
||||||
this.registerRenderers('date', {
|
this.registerRenderers('date', {
|
||||||
headerRenderer: new DateHeaderRenderer(),
|
headerRenderer: new DateHeaderRenderer(),
|
||||||
columnRenderer: new DateColumnRenderer(),
|
columnRenderer: new DateColumnRenderer(),
|
||||||
eventRenderer: new DateEventRenderer()
|
eventRenderer: new DateEventRenderer(calendarConfig)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.registerRenderers('resource', {
|
this.registerRenderers('resource', {
|
||||||
headerRenderer: new ResourceHeaderRenderer(),
|
headerRenderer: new ResourceHeaderRenderer(),
|
||||||
columnRenderer: new ResourceColumnRenderer(),
|
columnRenderer: new ResourceColumnRenderer(),
|
||||||
eventRenderer: new ResourceEventRenderer()
|
eventRenderer: new ResourceEventRenderer(calendarConfig)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export class ManagerFactory {
|
||||||
const eventRenderer = new EventRenderingService(eventBus, eventManager);
|
const eventRenderer = new EventRenderingService(eventBus, eventManager);
|
||||||
const gridManager = new GridManager();
|
const gridManager = new GridManager();
|
||||||
const scrollManager = new ScrollManager();
|
const scrollManager = new ScrollManager();
|
||||||
const navigationManager = new NavigationManager(eventBus);
|
const navigationManager = new NavigationManager(eventBus, eventRenderer);
|
||||||
const viewManager = new ViewManager(eventBus);
|
const viewManager = new ViewManager(eventBus);
|
||||||
|
|
||||||
// CalendarManager depends on all other managers
|
// CalendarManager depends on all other managers
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { eventBus } from '../core/EventBus';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { EventTypes } from '../constants/EventTypes';
|
import { EventTypes } from '../constants/EventTypes';
|
||||||
import { StateEvents } from '../types/CalendarState';
|
import { StateEvents } from '../types/CalendarState';
|
||||||
import { DateUtils } from '../utils/DateUtils';
|
|
||||||
import { DateCalculator } from '../utils/DateCalculator';
|
import { DateCalculator } from '../utils/DateCalculator';
|
||||||
import { ResourceCalendarData } from '../types/CalendarTypes';
|
import { ResourceCalendarData } from '../types/CalendarTypes';
|
||||||
import { GridRenderer } from '../renderers/GridRenderer';
|
import { GridRenderer } from '../renderers/GridRenderer';
|
||||||
|
|
@ -105,6 +104,7 @@ export class GridManager {
|
||||||
this.render();
|
this.render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Handle events loaded
|
// Handle events loaded
|
||||||
eventBus.on(EventTypes.EVENTS_LOADED, (e: Event) => {
|
eventBus.on(EventTypes.EVENTS_LOADED, (e: Event) => {
|
||||||
const detail = (e as CustomEvent).detail;
|
const detail = (e as CustomEvent).detail;
|
||||||
|
|
@ -147,12 +147,14 @@ export class GridManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('GridManager: Starting render with grid element:', this.grid);
|
console.group(`🏗️ GRID RENDER: ${this.currentWeek?.toDateString()}`);
|
||||||
|
console.log('Updating grid styles and rendering structure...');
|
||||||
|
|
||||||
this.styleManager.updateGridStyles(this.resourceData);
|
this.styleManager.updateGridStyles(this.resourceData);
|
||||||
this.gridRenderer.renderGrid(this.grid, this.currentWeek!, this.resourceData, this.allDayEvents);
|
this.gridRenderer.renderGrid(this.grid, this.currentWeek!, this.resourceData, this.allDayEvents);
|
||||||
|
|
||||||
const columnCount = this.styleManager.getColumnCount(this.resourceData);
|
const columnCount = this.styleManager.getColumnCount(this.resourceData);
|
||||||
console.log(`GridManager: Render complete - created ${columnCount} columns`);
|
console.log(`Grid structure complete - ${columnCount} columns created`);
|
||||||
|
|
||||||
// Emit GRID_RENDERED event to trigger event rendering
|
// Emit GRID_RENDERED event to trigger event rendering
|
||||||
const weekEnd = this.currentWeek ? new Date(this.currentWeek.getTime() + 6 * 24 * 60 * 60 * 1000) : null;
|
const weekEnd = this.currentWeek ? new Date(this.currentWeek.getTime() + 6 * 24 * 60 * 60 * 1000) : null;
|
||||||
|
|
@ -163,6 +165,8 @@ export class GridManager {
|
||||||
endDate: weekEnd,
|
endDate: weekEnd,
|
||||||
columnCount: columnCount
|
columnCount: columnCount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column count calculation moved to GridStyleManager
|
// Column count calculation moved to GridStyleManager
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { IEventBus } from '../types/CalendarTypes.js';
|
import { IEventBus } from '../types/CalendarTypes.js';
|
||||||
import { DateUtils } from '../utils/DateUtils.js';
|
import { EventRenderingService } from '../renderers/EventRendererManager.js';
|
||||||
|
import { DateCalculator } from '../utils/DateCalculator.js';
|
||||||
import { EventTypes } from '../constants/EventTypes.js';
|
import { EventTypes } from '../constants/EventTypes.js';
|
||||||
import { NavigationRenderer } from '../renderers/NavigationRenderer.js';
|
import { NavigationRenderer } from '../renderers/NavigationRenderer.js';
|
||||||
import { calendarConfig } from '../core/CalendarConfig.js';
|
import { calendarConfig } from '../core/CalendarConfig.js';
|
||||||
|
|
@ -11,15 +12,17 @@ import { calendarConfig } from '../core/CalendarConfig.js';
|
||||||
export class NavigationManager {
|
export class NavigationManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private navigationRenderer: NavigationRenderer;
|
private navigationRenderer: NavigationRenderer;
|
||||||
|
private dateCalculator: DateCalculator;
|
||||||
private currentWeek: Date;
|
private currentWeek: Date;
|
||||||
private targetWeek: Date;
|
private targetWeek: Date;
|
||||||
private animationQueue: number = 0;
|
private animationQueue: number = 0;
|
||||||
|
|
||||||
constructor(eventBus: IEventBus) {
|
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) {
|
||||||
console.log('🧭 NavigationManager: Constructor called');
|
console.log('🧭 NavigationManager: Constructor called');
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.navigationRenderer = new NavigationRenderer(eventBus, calendarConfig);
|
this.dateCalculator = new DateCalculator(calendarConfig);
|
||||||
this.currentWeek = DateUtils.getWeekStart(new Date(), 0); // Sunday start like POC
|
this.navigationRenderer = new NavigationRenderer(eventBus, calendarConfig, eventRenderer);
|
||||||
|
this.currentWeek = this.dateCalculator.getISOWeekStart(new Date());
|
||||||
this.targetWeek = new Date(this.currentWeek);
|
this.targetWeek = new Date(this.currentWeek);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +86,7 @@ export class NavigationManager {
|
||||||
|
|
||||||
private navigateToToday(): void {
|
private navigateToToday(): void {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const todayWeekStart = DateUtils.getWeekStart(today, 0);
|
const todayWeekStart = this.dateCalculator.getISOWeekStart(today);
|
||||||
|
|
||||||
// Reset to today
|
// Reset to today
|
||||||
this.targetWeek = new Date(todayWeekStart);
|
this.targetWeek = new Date(todayWeekStart);
|
||||||
|
|
@ -101,7 +104,7 @@ export class NavigationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private navigateToDate(date: Date): void {
|
private navigateToDate(date: Date): void {
|
||||||
const weekStart = DateUtils.getWeekStart(date, 0);
|
const weekStart = this.dateCalculator.getISOWeekStart(date);
|
||||||
this.targetWeek = new Date(weekStart);
|
this.targetWeek = new Date(weekStart);
|
||||||
|
|
||||||
const currentTime = this.currentWeek.getTime();
|
const currentTime = this.currentWeek.getTime();
|
||||||
|
|
@ -128,14 +131,16 @@ export class NavigationManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`NavigationManager: Starting ${direction} animation to ${targetWeek.toDateString()}`);
|
console.group(`🎬 NAVIGATION ANIMATION: ${direction} to ${targetWeek.toDateString()}`);
|
||||||
|
console.log('1. Creating new container with events...');
|
||||||
|
|
||||||
let newGrid: HTMLElement;
|
let newGrid: HTMLElement;
|
||||||
|
|
||||||
// Always create a fresh container for consistent behavior
|
// Always create a fresh container for consistent behavior
|
||||||
console.log('NavigationManager: Creating new container');
|
|
||||||
newGrid = this.navigationRenderer.renderContainer(container as HTMLElement, targetWeek);
|
newGrid = this.navigationRenderer.renderContainer(container as HTMLElement, targetWeek);
|
||||||
|
|
||||||
|
console.log('2. Starting slide animation...');
|
||||||
|
|
||||||
// Clear any existing transforms before animation
|
// Clear any existing transforms before animation
|
||||||
newGrid.style.transform = '';
|
newGrid.style.transform = '';
|
||||||
(currentGrid as HTMLElement).style.transform = '';
|
(currentGrid as HTMLElement).style.transform = '';
|
||||||
|
|
@ -161,6 +166,8 @@ export class NavigationManager {
|
||||||
|
|
||||||
// Handle animation completion
|
// Handle animation completion
|
||||||
slideInAnimation.addEventListener('finish', () => {
|
slideInAnimation.addEventListener('finish', () => {
|
||||||
|
console.log('3. Animation finished, cleaning up...');
|
||||||
|
|
||||||
// Cleanup: Remove all old grids except the new one
|
// Cleanup: Remove all old grids except the new one
|
||||||
const allGrids = container.querySelectorAll('swp-grid-container');
|
const allGrids = container.querySelectorAll('swp-grid-container');
|
||||||
for (let i = 0; i < allGrids.length - 1; i++) {
|
for (let i = 0; i < allGrids.length - 1; i++) {
|
||||||
|
|
@ -184,24 +191,24 @@ export class NavigationManager {
|
||||||
this.updateWeekInfo();
|
this.updateWeekInfo();
|
||||||
this.eventBus.emit(EventTypes.WEEK_CHANGED, {
|
this.eventBus.emit(EventTypes.WEEK_CHANGED, {
|
||||||
weekStart: this.currentWeek,
|
weekStart: this.currentWeek,
|
||||||
weekEnd: DateUtils.addDays(this.currentWeek, 6)
|
weekEnd: this.dateCalculator.addDays(this.currentWeek, 6)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Emit animation complete event for ScrollManager
|
// Emit animation complete event for ScrollManager
|
||||||
this.eventBus.emit(EventTypes.NAVIGATION_ANIMATION_COMPLETE, {
|
this.eventBus.emit(EventTypes.NAVIGATION_ANIMATION_COMPLETE, {
|
||||||
direction,
|
direction,
|
||||||
weekStart: this.currentWeek
|
weekStart: this.currentWeek
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`NavigationManager: Completed ${direction} animation`);
|
console.log('✅ Animation completed successfully');
|
||||||
|
console.groupEnd();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWeekInfo(): void {
|
private updateWeekInfo(): void {
|
||||||
const weekNumber = DateUtils.getWeekNumber(this.currentWeek);
|
const weekNumber = this.dateCalculator.getWeekNumber(this.currentWeek);
|
||||||
const weekEnd = DateUtils.addDays(this.currentWeek, 6);
|
const weekEnd = this.dateCalculator.addDays(this.currentWeek, 6);
|
||||||
const dateRange = DateUtils.formatDateRange(this.currentWeek, weekEnd);
|
const dateRange = this.dateCalculator.formatDateRange(this.currentWeek, weekEnd);
|
||||||
|
|
||||||
// Notify other managers about week info update - DOM manipulation should happen via events
|
// Notify other managers about week info update - DOM manipulation should happen via events
|
||||||
this.eventBus.emit(EventTypes.WEEK_INFO_UPDATED, {
|
this.eventBus.emit(EventTypes.WEEK_INFO_UPDATED, {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export class ViewManager {
|
||||||
|
|
||||||
// Setup workweek preset button handlers
|
// Setup workweek preset button handlers
|
||||||
this.setupWorkweekButtonHandlers();
|
this.setupWorkweekButtonHandlers();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupViewButtonHandlers(): void {
|
private setupViewButtonHandlers(): void {
|
||||||
|
|
@ -64,6 +65,7 @@ export class ViewManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private initializeView(): void {
|
private initializeView(): void {
|
||||||
this.updateViewButtons();
|
this.updateViewButtons();
|
||||||
this.updateWorkweekButtons();
|
this.updateWorkweekButtons();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { CalendarConfig } from '../core/CalendarConfig';
|
||||||
import { DateUtils } from '../utils/DateUtils';
|
import { DateCalculator } from '../utils/DateCalculator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for event rendering strategies
|
* Interface for event rendering strategies
|
||||||
|
|
@ -16,6 +16,11 @@ export interface EventRendererStrategy {
|
||||||
* Base class for event renderers with common functionality
|
* Base class for event renderers with common functionality
|
||||||
*/
|
*/
|
||||||
export abstract class BaseEventRenderer implements EventRendererStrategy {
|
export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
|
protected dateCalculator: DateCalculator;
|
||||||
|
|
||||||
|
constructor(config: CalendarConfig) {
|
||||||
|
this.dateCalculator = new DateCalculator(config);
|
||||||
|
}
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void {
|
renderEvents(events: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void {
|
||||||
console.log('BaseEventRenderer: renderEvents called with', events.length, 'events');
|
console.log('BaseEventRenderer: renderEvents called with', events.length, 'events');
|
||||||
|
|
||||||
|
|
@ -70,8 +75,8 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
eventElement.style.backgroundColor = event.metadata?.color || '#3498db';
|
eventElement.style.backgroundColor = event.metadata?.color || '#3498db';
|
||||||
|
|
||||||
// Format time for display
|
// Format time for display
|
||||||
const startTime = this.formatTime(event.start);
|
const startTime = this.dateCalculator.formatTime(new Date(event.start));
|
||||||
const endTime = this.formatTime(event.end);
|
const endTime = this.dateCalculator.formatTime(new Date(event.end));
|
||||||
|
|
||||||
// Create event content
|
// Create event content
|
||||||
eventElement.innerHTML = `
|
eventElement.innerHTML = `
|
||||||
|
|
@ -160,15 +165,24 @@ export class DateEventRenderer extends BaseEventRenderer {
|
||||||
|
|
||||||
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
|
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
|
||||||
const columnDate = column.dataset.date;
|
const columnDate = column.dataset.date;
|
||||||
if (!columnDate) return [];
|
if (!columnDate) {
|
||||||
|
console.log(`DateEventRenderer: Column has no dataset.date`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const columnEvents = events.filter(event => {
|
const columnEvents = events.filter(event => {
|
||||||
const eventDate = new Date(event.start);
|
const eventDate = new Date(event.start);
|
||||||
const eventDateStr = DateUtils.formatDate(eventDate);
|
const eventDateStr = this.dateCalculator.formatISODate(eventDate);
|
||||||
return eventDateStr === columnDate;
|
const matches = eventDateStr === columnDate;
|
||||||
|
|
||||||
|
if (!matches) {
|
||||||
|
console.log(`DateEventRenderer: Event ${event.title} (${eventDateStr}) does not match column (${columnDate})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`DateEventRenderer: Column ${columnDate} has ${columnEvents.length} events`);
|
console.log(`DateEventRenderer: Column ${columnDate} has ${columnEvents.length} events from ${events.length} total`);
|
||||||
return columnEvents;
|
return columnEvents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,7 @@ export class EventRenderingService {
|
||||||
* Render events in a specific container for a given period
|
* Render events in a specific container for a given period
|
||||||
*/
|
*/
|
||||||
public renderEvents(context: RenderContext): void {
|
public renderEvents(context: RenderContext): void {
|
||||||
console.log('EventRenderer: Rendering events for period', {
|
console.log(` 📅 Getting events for ${context.startDate.toDateString()} - ${context.endDate.toDateString()}`);
|
||||||
startDate: context.startDate,
|
|
||||||
endDate: context.endDate,
|
|
||||||
container: context.container
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get events from EventManager for the period
|
// Get events from EventManager for the period
|
||||||
const events = this.eventManager.getEventsForPeriod(
|
const events = this.eventManager.getEventsForPeriod(
|
||||||
|
|
@ -43,17 +39,17 @@ export class EventRenderingService {
|
||||||
context.endDate
|
context.endDate
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`EventRenderer: Found ${events.length} events for period`);
|
console.log(` 📊 Found ${events.length} events for period`);
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
console.log('EventRenderer: No events to render for this period');
|
console.log(' ⚠️ No events to render for this period');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use cached strategy to render events in the specific container
|
// Use cached strategy to render events in the specific container
|
||||||
this.strategy.renderEvents(events, context.container, calendarConfig);
|
this.strategy.renderEvents(events, context.container, calendarConfig);
|
||||||
|
|
||||||
console.log(`EventRenderer: Successfully rendered ${events.length} events`);
|
console.log(` ✅ Rendered ${events.length} events successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEventListeners(): void {
|
private setupEventListeners(): void {
|
||||||
|
|
@ -63,10 +59,11 @@ export class EventRenderingService {
|
||||||
this.handleGridRendered(event as CustomEvent);
|
this.handleGridRendered(event as CustomEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => {
|
// CONTAINER_READY_FOR_EVENTS removed - events are now pre-rendered synchronously
|
||||||
console.log('EventRenderer: Received CONTAINER_READY_FOR_EVENTS event');
|
// this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => {
|
||||||
this.handleContainerReady(event as CustomEvent);
|
// console.log('EventRenderer: Received CONTAINER_READY_FOR_EVENTS event');
|
||||||
});
|
// this.handleContainerReady(event as CustomEvent);
|
||||||
|
// });
|
||||||
|
|
||||||
this.eventBus.on(EventTypes.VIEW_CHANGED, (event: Event) => {
|
this.eventBus.on(EventTypes.VIEW_CHANGED, (event: Event) => {
|
||||||
console.log('EventRenderer: Received VIEW_CHANGED event');
|
console.log('EventRenderer: Received VIEW_CHANGED event');
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,16 @@ export class DateHeaderRenderer implements HeaderRenderer {
|
||||||
const weekDays = config.get('weekDays');
|
const weekDays = config.get('weekDays');
|
||||||
const daysToShow = dates.slice(0, weekDays);
|
const daysToShow = dates.slice(0, weekDays);
|
||||||
|
|
||||||
const workWeekSettings = config.getWorkWeekSettings();
|
|
||||||
daysToShow.forEach((date, index) => {
|
daysToShow.forEach((date, index) => {
|
||||||
const header = document.createElement('swp-day-header');
|
const header = document.createElement('swp-day-header');
|
||||||
if (this.dateCalculator.isToday(date)) {
|
if (this.dateCalculator.isToday(date)) {
|
||||||
(header as any).dataset.today = 'true';
|
(header as any).dataset.today = 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dayName = this.dateCalculator.getDayName(date, 'short');
|
||||||
|
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<swp-day-name>${workWeekSettings.dayNames[index]}</swp-day-name>
|
<swp-day-name>${dayName}</swp-day-name>
|
||||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||||
`;
|
`;
|
||||||
(header as any).dataset.date = this.dateCalculator.formatISODate(date);
|
(header as any).dataset.date = this.dateCalculator.formatISODate(date);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { EventTypes } from '../constants/EventTypes';
|
import { EventTypes } from '../constants/EventTypes';
|
||||||
import { DateUtils } from '../utils/DateUtils';
|
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { CalendarConfig } from '../core/CalendarConfig';
|
||||||
import { DateCalculator } from '../utils/DateCalculator';
|
import { DateCalculator } from '../utils/DateCalculator';
|
||||||
|
import { EventRenderingService } from './EventRendererManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NavigationRenderer - Handles DOM rendering for navigation containers
|
* NavigationRenderer - Handles DOM rendering for navigation containers
|
||||||
|
|
@ -12,10 +12,12 @@ export class NavigationRenderer {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private config: CalendarConfig;
|
private config: CalendarConfig;
|
||||||
private dateCalculator: DateCalculator;
|
private dateCalculator: DateCalculator;
|
||||||
|
private eventRenderer: EventRenderingService;
|
||||||
|
|
||||||
constructor(eventBus: IEventBus, config: CalendarConfig) {
|
constructor(eventBus: IEventBus, config: CalendarConfig, eventRenderer: EventRenderingService) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.eventRenderer = eventRenderer;
|
||||||
this.dateCalculator = new DateCalculator(config);
|
this.dateCalculator = new DateCalculator(config);
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +53,10 @@ export class NavigationRenderer {
|
||||||
* Render a complete container with content and events
|
* Render a complete container with content and events
|
||||||
*/
|
*/
|
||||||
public renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement {
|
public renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement {
|
||||||
console.log('NavigationRenderer: Rendering new container for week:', weekStart.toDateString());
|
const weekEnd = this.dateCalculator.addDays(weekStart, 6);
|
||||||
|
|
||||||
|
console.group(`🎨 RENDERING CONTAINER: ${weekStart.toDateString()} - ${weekEnd.toDateString()}`);
|
||||||
|
console.log('1. Creating grid structure...');
|
||||||
|
|
||||||
// Create new grid container
|
// Create new grid container
|
||||||
const newGrid = document.createElement('swp-grid-container');
|
const newGrid = document.createElement('swp-grid-container');
|
||||||
|
|
@ -75,17 +80,18 @@ export class NavigationRenderer {
|
||||||
// Add to parent container
|
// Add to parent container
|
||||||
parentContainer.appendChild(newGrid);
|
parentContainer.appendChild(newGrid);
|
||||||
|
|
||||||
// Render week content (headers and columns)
|
console.log('2. Rendering headers and columns...');
|
||||||
this.renderWeekContentInContainer(newGrid, weekStart);
|
this.renderWeekContentInContainer(newGrid, weekStart);
|
||||||
|
|
||||||
// Emit event to trigger event rendering
|
console.log('3. Pre-rendering events synchronously...');
|
||||||
const weekEnd = DateUtils.addDays(weekStart, 6);
|
this.eventRenderer.renderEvents({
|
||||||
this.eventBus.emit(EventTypes.CONTAINER_READY_FOR_EVENTS, {
|
|
||||||
container: newGrid,
|
container: newGrid,
|
||||||
startDate: weekStart,
|
startDate: weekStart,
|
||||||
endDate: weekEnd
|
endDate: weekEnd
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('✅ Container ready with pre-rendered events');
|
||||||
|
console.groupEnd();
|
||||||
return newGrid;
|
return newGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +110,6 @@ export class NavigationRenderer {
|
||||||
|
|
||||||
// Get dates using DateCalculator
|
// Get dates using DateCalculator
|
||||||
const dates = this.dateCalculator.getWorkWeekDates(weekStart);
|
const dates = this.dateCalculator.getWorkWeekDates(weekStart);
|
||||||
const workWeekSettings = this.config.getWorkWeekSettings();
|
|
||||||
|
|
||||||
// Render headers for target week
|
// Render headers for target week
|
||||||
dates.forEach((date, i) => {
|
dates.forEach((date, i) => {
|
||||||
|
|
@ -113,8 +118,10 @@ export class NavigationRenderer {
|
||||||
headerElement.dataset.today = 'true';
|
headerElement.dataset.today = 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dayName = this.dateCalculator.getDayName(date, 'short');
|
||||||
|
|
||||||
headerElement.innerHTML = `
|
headerElement.innerHTML = `
|
||||||
<swp-day-name>${workWeekSettings.dayNames[i]}</swp-day-name>
|
<swp-day-name>${dayName}</swp-day-name>
|
||||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||||
`;
|
`;
|
||||||
headerElement.dataset.date = this.dateCalculator.formatISODate(date);
|
headerElement.dataset.date = this.dateCalculator.formatISODate(date);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export class DateCalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get dates for work week based on ISO week (starts Monday)
|
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
||||||
* @param weekStart - Any date in the week
|
* @param weekStart - Any date in the week
|
||||||
* @returns Array of dates for the configured work days
|
* @returns Array of dates for the configured work days
|
||||||
*/
|
*/
|
||||||
|
|
@ -21,21 +21,15 @@ export class DateCalculator {
|
||||||
const dates: Date[] = [];
|
const dates: Date[] = [];
|
||||||
const workWeekSettings = this.config.getWorkWeekSettings();
|
const workWeekSettings = this.config.getWorkWeekSettings();
|
||||||
|
|
||||||
// Get Monday of the ISO week
|
// Always use ISO week start (Monday)
|
||||||
const monday = this.getISOWeekStart(weekStart);
|
const mondayOfWeek = this.getISOWeekStart(weekStart);
|
||||||
|
|
||||||
// Calculate dates for each work day
|
|
||||||
workWeekSettings.workDays.forEach(dayOfWeek => {
|
|
||||||
const date = new Date(monday);
|
|
||||||
|
|
||||||
if (dayOfWeek === 0) {
|
|
||||||
// Sunday is 6 days after Monday
|
|
||||||
date.setDate(monday.getDate() + 6);
|
|
||||||
} else {
|
|
||||||
// Monday=1 becomes 0 days after, Tuesday=2 becomes 1 day after, etc.
|
|
||||||
date.setDate(monday.getDate() + dayOfWeek - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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);
|
dates.push(date);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,24 +50,14 @@ export class DateCalculator {
|
||||||
return monday;
|
return monday;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the start of the week for a given date (legacy method)
|
|
||||||
* @param date - Any date in the week
|
|
||||||
* @param firstDayOfWeek - 0 for Sunday, 1 for Monday
|
|
||||||
* @returns The start date of the week
|
|
||||||
*/
|
|
||||||
getWeekStart(date: Date, firstDayOfWeek: number = 1): Date {
|
|
||||||
return this.getISOWeekStart(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the end of the week for a given date
|
* Get the end of the ISO week for a given date
|
||||||
* @param date - Any date in the week
|
* @param date - Any date in the week
|
||||||
* @param firstDayOfWeek - 0 for Sunday, 1 for Monday
|
* @returns The end date of the ISO week (Sunday)
|
||||||
* @returns The end date of the week
|
|
||||||
*/
|
*/
|
||||||
getWeekEnd(date: Date, firstDayOfWeek: number = 1): Date {
|
getWeekEnd(date: Date): Date {
|
||||||
const weekStart = this.getWeekStart(date, firstDayOfWeek);
|
const weekStart = this.getISOWeekStart(date);
|
||||||
const weekEnd = new Date(weekStart);
|
const weekEnd = new Date(weekStart);
|
||||||
weekEnd.setDate(weekStart.getDate() + 6);
|
weekEnd.setDate(weekStart.getDate() + 6);
|
||||||
weekEnd.setHours(23, 59, 59, 999);
|
weekEnd.setHours(23, 59, 59, 999);
|
||||||
|
|
@ -94,28 +78,40 @@ export class DateCalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a date range for display
|
* Format a date range with customizable options
|
||||||
* @param start - Start date
|
* @param start - Start date
|
||||||
* @param end - End date
|
* @param end - End date
|
||||||
|
* @param options - Formatting options
|
||||||
* @returns Formatted date range string
|
* @returns Formatted date range string
|
||||||
*/
|
*/
|
||||||
formatDateRange(start: Date, end: Date): string {
|
formatDateRange(
|
||||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
options: {
|
||||||
|
locale?: string;
|
||||||
|
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
|
||||||
|
day?: 'numeric' | '2-digit';
|
||||||
|
year?: 'numeric' | '2-digit';
|
||||||
|
} = {}
|
||||||
|
): string {
|
||||||
|
const { locale = 'en-US', month = 'short', day = 'numeric' } = options;
|
||||||
|
|
||||||
const startMonth = months[start.getMonth()];
|
|
||||||
const endMonth = months[end.getMonth()];
|
|
||||||
const startDay = start.getDate();
|
|
||||||
const endDay = end.getDate();
|
|
||||||
const startYear = start.getFullYear();
|
const startYear = start.getFullYear();
|
||||||
const endYear = end.getFullYear();
|
const endYear = end.getFullYear();
|
||||||
|
|
||||||
if (startYear !== endYear) {
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
return `${startMonth} ${startDay}, ${startYear} - ${endMonth} ${endDay}, ${endYear}`;
|
month,
|
||||||
} else if (startMonth === endMonth) {
|
day,
|
||||||
return `${startMonth} ${startDay} - ${endDay}, ${startYear}`;
|
year: startYear !== endYear ? 'numeric' : undefined
|
||||||
} else {
|
});
|
||||||
return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${startYear}`;
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (typeof formatter.formatRange === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
return formatter.formatRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -173,18 +169,110 @@ export class DateCalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the day name for a date
|
* Get the day name for a date using Intl.DateTimeFormat
|
||||||
* @param date - Date to get day name for
|
* @param date - Date to get day name for
|
||||||
* @param format - 'short' or 'long'
|
* @param format - 'short' or 'long'
|
||||||
* @returns Day name
|
* @returns Day name
|
||||||
*/
|
*/
|
||||||
getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
|
getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
|
||||||
const days = {
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
weekday: format
|
||||||
long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
});
|
||||||
};
|
return formatter.format(date);
|
||||||
return days[format][date.getDay()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time to HH:MM
|
||||||
|
* @param date - Date to format
|
||||||
|
* @returns Time string
|
||||||
|
*/
|
||||||
|
formatTime(date: Date): string {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
formatTime12(date: Date): string {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
minutesToTime(minutes: number): string {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
timeToMinutes(timeStr: string): number {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
getMinutesSinceMidnight(date: Date | string): number {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
getDurationMinutes(start: Date | string, end: Date | string): number {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
isSameDay(date1: Date, date2: Date): boolean {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
isMultiDay(start: Date | string, end: Date | string): boolean {
|
||||||
|
const startDate = typeof start === 'string' ? new Date(start) : start;
|
||||||
|
const endDate = typeof end === 'string' ? new Date(end) : end;
|
||||||
|
return !this.isSameDay(startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create singleton instance with config
|
// Create singleton instance with config
|
||||||
|
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
// Date and time utility functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Date and time utility functions
|
|
||||||
*/
|
|
||||||
export class DateUtils {
|
|
||||||
/**
|
|
||||||
* Get start of week for a given date
|
|
||||||
*/
|
|
||||||
static getWeekStart(date: Date, firstDayOfWeek: number = 1): Date {
|
|
||||||
const d = new Date(date);
|
|
||||||
const day = d.getDay();
|
|
||||||
const diff = (day - firstDayOfWeek + 7) % 7;
|
|
||||||
d.setDate(d.getDate() - diff);
|
|
||||||
d.setHours(0, 0, 0, 0);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get end of week for a given date
|
|
||||||
*/
|
|
||||||
static getWeekEnd(date: Date, firstDayOfWeek: number = 1): Date {
|
|
||||||
const start = this.getWeekStart(date, firstDayOfWeek);
|
|
||||||
const end = new Date(start);
|
|
||||||
end.setDate(end.getDate() + 6);
|
|
||||||
end.setHours(23, 59, 59, 999);
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format date to YYYY-MM-DD
|
|
||||||
*/
|
|
||||||
static formatDate(date: Date): string {
|
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format time to HH:MM
|
|
||||||
*/
|
|
||||||
static formatTime(date: Date): string {
|
|
||||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format time to 12-hour format
|
|
||||||
*/
|
|
||||||
static formatTime12(date: Date): string {
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
static minutesToTime(minutes: number): string {
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
static timeToMinutes(timeStr: string): number {
|
|
||||||
const [time] = timeStr.split('T').pop()!.split('.');
|
|
||||||
const [hours, minutes] = time.split(':').map(Number);
|
|
||||||
return hours * 60 + minutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get minutes since start of day
|
|
||||||
*/
|
|
||||||
static getMinutesSinceMidnight(date: Date | string): number {
|
|
||||||
const d = typeof date === 'string' ? new Date(date) : date;
|
|
||||||
return d.getHours() * 60 + d.getMinutes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate duration in minutes between two dates
|
|
||||||
*/
|
|
||||||
static getDurationMinutes(start: Date | string, end: Date | string): number {
|
|
||||||
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 date is today
|
|
||||||
*/
|
|
||||||
static isToday(date: Date): boolean {
|
|
||||||
const today = new Date();
|
|
||||||
return date.toDateString() === today.toDateString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if two dates are on the same day
|
|
||||||
*/
|
|
||||||
static isSameDay(date1: Date, date2: Date): boolean {
|
|
||||||
return date1.toDateString() === date2.toDateString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if event spans multiple days
|
|
||||||
*/
|
|
||||||
static isMultiDay(start: Date | string, end: Date | string): boolean {
|
|
||||||
const startDate = typeof start === 'string' ? new Date(start) : start;
|
|
||||||
const endDate = typeof end === 'string' ? new Date(end) : end;
|
|
||||||
return !this.isSameDay(startDate, endDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get day name
|
|
||||||
*/
|
|
||||||
static getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
|
|
||||||
const days = {
|
|
||||||
short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
||||||
long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
||||||
};
|
|
||||||
return days[format][date.getDay()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add days to date
|
|
||||||
*/
|
|
||||||
static addDays(date: Date, days: number): Date {
|
|
||||||
const result = new Date(date);
|
|
||||||
result.setDate(result.getDate() + days);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add minutes to date
|
|
||||||
*/
|
|
||||||
static addMinutes(date: Date, minutes: number): Date {
|
|
||||||
const result = new Date(date);
|
|
||||||
result.setMinutes(result.getMinutes() + minutes);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Snap time to nearest interval
|
|
||||||
*/
|
|
||||||
static snapToInterval(date: Date, intervalMinutes: number): Date {
|
|
||||||
const minutes = date.getMinutes();
|
|
||||||
const snappedMinutes = Math.round(minutes / intervalMinutes) * intervalMinutes;
|
|
||||||
const result = new Date(date);
|
|
||||||
result.setMinutes(snappedMinutes);
|
|
||||||
result.setSeconds(0);
|
|
||||||
result.setMilliseconds(0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current time in minutes since day start
|
|
||||||
*/
|
|
||||||
static getCurrentTimeMinutes(dayStartHour: number = 0): number {
|
|
||||||
const now = new Date();
|
|
||||||
const minutesSinceMidnight = now.getHours() * 60 + now.getMinutes();
|
|
||||||
return minutesSinceMidnight - (dayStartHour * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format duration to human readable string
|
|
||||||
*/
|
|
||||||
static formatDuration(minutes: number): string {
|
|
||||||
if (minutes < 60) {
|
|
||||||
return `${minutes} min`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
const mins = minutes % 60;
|
|
||||||
|
|
||||||
if (mins === 0) {
|
|
||||||
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${hours} hour${hours > 1 ? 's' : ''} ${mins} min`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ISO week number for a given date
|
|
||||||
*/
|
|
||||||
static getWeekNumber(date: Date): number {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get month names array
|
|
||||||
*/
|
|
||||||
static getMonthNames(format: 'short' | 'long' = 'short'): string[] {
|
|
||||||
const months = {
|
|
||||||
short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
||||||
long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
|
||||||
};
|
|
||||||
return months[format];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format date range for display (e.g., "Jan 15 - 21, 2024" or "Jan 15 - Feb 2, 2024")
|
|
||||||
*/
|
|
||||||
static formatDateRange(startDate: Date, endDate: Date): string {
|
|
||||||
const monthNames = this.getMonthNames('short');
|
|
||||||
|
|
||||||
const startMonth = monthNames[startDate.getMonth()];
|
|
||||||
const endMonth = monthNames[endDate.getMonth()];
|
|
||||||
const startDay = startDate.getDate();
|
|
||||||
const endDay = endDate.getDate();
|
|
||||||
const startYear = startDate.getFullYear();
|
|
||||||
const endYear = endDate.getFullYear();
|
|
||||||
|
|
||||||
if (startMonth === endMonth && startYear === endYear) {
|
|
||||||
return `${startMonth} ${startDay} - ${endDay}, ${startYear}`;
|
|
||||||
} else if (startYear !== endYear) {
|
|
||||||
return `${startMonth} ${startDay}, ${startYear} - ${endMonth} ${endDay}, ${endYear}`;
|
|
||||||
} else {
|
|
||||||
return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${startYear}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -125,6 +125,7 @@ swp-preset-button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Search container */
|
/* Search container */
|
||||||
swp-search-container {
|
swp-search-container {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue