diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts index 3fda062..2cf9e20 100644 --- a/src/managers/CalendarManager.ts +++ b/src/managers/CalendarManager.ts @@ -6,6 +6,7 @@ import { EventManager } from './EventManager.js'; import { GridManager } from './GridManager.js'; import { EventRenderingService } from '../renderers/EventRendererManager.js'; import { ScrollManager } from './ScrollManager.js'; +import { DateCalculator } from '../utils/DateCalculator.js'; /** * CalendarManager - Main coordinator for all calendar managers @@ -18,6 +19,7 @@ export class CalendarManager { private gridManager: GridManager; private eventRenderer: EventRenderingService; private scrollManager: ScrollManager; + private dateCalculator: DateCalculator; private currentView: CalendarView = 'week'; private currentDate: Date = new Date(); private isInitialized: boolean = false; @@ -36,6 +38,7 @@ export class CalendarManager { this.gridManager = gridManager; this.eventRenderer = eventRenderer; this.scrollManager = scrollManager; + this.dateCalculator = new DateCalculator(config); this.setupEventListeners(); console.log('📋 CalendarManager: Created with proper dependency injection'); } @@ -428,8 +431,8 @@ export class CalendarManager { // Trigger event rendering for the current date range using correct method this.eventRenderer.renderEvents({ container: container as HTMLElement, - startDate: new Date(periodData.startDate), - endDate: new Date(periodData.endDate) + startDate: new Date(periodData.start), + endDate: new Date(periodData.end) }); } @@ -471,10 +474,10 @@ export class CalendarManager { const lastDate = new Date(lastDateStr); // Calculate week number from first date - const weekNumber = this.getWeekNumber(firstDate); + const weekNumber = this.dateCalculator.getWeekNumber(firstDate); // Format date range - const dateRange = this.formatDateRange(firstDate, lastDate); + const dateRange = this.dateCalculator.formatDateRange(firstDate, lastDate); console.log('CalendarManager: Week info from columns:', { firstDate: firstDateStr, @@ -492,41 +495,5 @@ export class CalendarManager { }); } - /** - * Helper method to get week number - */ - private getWeekNumber(date: Date): number { - const start = new Date(date.getFullYear(), 0, 1); - const days = Math.floor((date.getTime() - start.getTime()) / (24 * 60 * 60 * 1000)); - return Math.ceil((days + start.getDay() + 1) / 7); - } - - /** - * Helper method to get week start (Sunday) - */ - private getWeekStart(date: Date): Date { - const weekStart = new Date(date); - weekStart.setDate(date.getDate() - date.getDay()); - return weekStart; - } - - /** - * Helper method to format date range - */ - private formatDateRange(start: Date, end: Date): string { - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - - const startMonth = months[start.getMonth()]; - const endMonth = months[end.getMonth()]; - const startDay = start.getDate(); - const endDay = end.getDate(); - const year = start.getFullYear(); - - if (startMonth === endMonth) { - return `${startMonth} ${startDay} - ${endDay}, ${year}`; - } else { - return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${year}`; - } - } } \ No newline at end of file diff --git a/src/renderers/ColumnRenderer.ts b/src/renderers/ColumnRenderer.ts index a6e55f9..19a896e 100644 --- a/src/renderers/ColumnRenderer.ts +++ b/src/renderers/ColumnRenderer.ts @@ -2,6 +2,7 @@ import { CalendarConfig } from '../core/CalendarConfig'; import { ResourceCalendarData } from '../types/CalendarTypes'; +import { DateCalculator } from '../utils/DateCalculator'; /** * Interface for column rendering strategies @@ -23,10 +24,15 @@ export interface ColumnRenderContext { * Date-based column renderer (original functionality) */ export class DateColumnRenderer implements ColumnRenderer { + private dateCalculator: DateCalculator; + render(columnContainer: HTMLElement, context: ColumnRenderContext): void { const { currentWeek, config } = context; - const dates = this.getWeekDates(currentWeek, config); + // Initialize date calculator + this.dateCalculator = new DateCalculator(config); + + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dateSettings = config.getDateViewSettings(); const daysToShow = dates.slice(0, dateSettings.weekDays); @@ -34,7 +40,7 @@ export class DateColumnRenderer implements ColumnRenderer { daysToShow.forEach((date) => { const column = document.createElement('swp-day-column'); - (column as any).dataset.date = this.formatDate(date); + (column as any).dataset.date = this.dateCalculator.formatISODate(date); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); @@ -43,26 +49,6 @@ export class DateColumnRenderer implements ColumnRenderer { }); } - private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { - const dates: Date[] = []; - const workWeekSettings = config.getWorkWeekSettings(); - - // Calculate dates based on actual work days (e.g., [1,2,3,4] for Mon-Thu) - workWeekSettings.workDays.forEach(dayOfWeek => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - dates.push(date); - }); - - return dates; - } - - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } } /** diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index cf2d734..95488a0 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -2,6 +2,7 @@ import { CalendarConfig } from '../core/CalendarConfig'; import { ResourceCalendarData } from '../types/CalendarTypes'; +import { DateCalculator } from '../utils/DateCalculator'; /** * Interface for header rendering strategies @@ -24,17 +25,22 @@ export interface HeaderRenderContext { * Date-based header renderer (original functionality) */ export class DateHeaderRenderer implements HeaderRenderer { + private dateCalculator: DateCalculator; + render(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek, config); + // Initialize date calculator with config + this.dateCalculator = new DateCalculator(config); + + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); const workWeekSettings = config.getWorkWeekSettings(); daysToShow.forEach((date, index) => { const header = document.createElement('swp-day-header'); - if (this.isToday(date)) { + if (this.dateCalculator.isToday(date)) { (header as any).dataset.today = 'true'; } @@ -42,7 +48,7 @@ export class DateHeaderRenderer implements HeaderRenderer { ${workWeekSettings.dayNames[index]} ${date.getDate()} `; - (header as any).dataset.date = this.formatDate(date); + (header as any).dataset.date = this.dateCalculator.formatISODate(date); calendarHeader.appendChild(header); }); @@ -54,7 +60,7 @@ export class DateHeaderRenderer implements HeaderRenderer { private renderAllDayEvents(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek, config); + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); @@ -68,8 +74,8 @@ export class DateHeaderRenderer implements HeaderRenderer { let endColumnIndex = -1; daysToShow.forEach((date, index) => { - const dateStr = this.formatDate(date); - const startDateStr = this.formatDate(startDate); + const dateStr = this.dateCalculator.formatISODate(date); + const startDateStr = this.dateCalculator.formatISODate(startDate); if (dateStr === startDateStr) { startColumnIndex = index; @@ -102,31 +108,6 @@ export class DateHeaderRenderer implements HeaderRenderer { }); } - private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { - const dates: Date[] = []; - const workWeekSettings = config.getWorkWeekSettings(); - - // Calculate dates based on actual work days (e.g., [1,2,3,4] for Mon-Thu) - workWeekSettings.workDays.forEach(dayOfWeek => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - dates.push(date); - }); - - return dates; - } - - private isToday(date: Date): boolean { - const today = new Date(); - return date.toDateString() === today.toDateString(); - } - - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } } diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index bc22ce3..0d3f614 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -2,6 +2,7 @@ import { IEventBus } from '../types/CalendarTypes'; import { EventTypes } from '../constants/EventTypes'; import { DateUtils } from '../utils/DateUtils'; import { CalendarConfig } from '../core/CalendarConfig'; +import { DateCalculator } from '../utils/DateCalculator'; /** * NavigationRenderer - Handles DOM rendering for navigation containers @@ -10,10 +11,12 @@ import { CalendarConfig } from '../core/CalendarConfig'; export class NavigationRenderer { private eventBus: IEventBus; private config: CalendarConfig; + private dateCalculator: DateCalculator; constructor(eventBus: IEventBus, config: CalendarConfig) { this.eventBus = eventBus; this.config = config; + this.dateCalculator = new DateCalculator(config); this.setupEventListeners(); } @@ -99,17 +102,14 @@ export class NavigationRenderer { header.innerHTML = ''; dayColumns.innerHTML = ''; - // Render headers for target week + // Get dates using DateCalculator + const dates = this.dateCalculator.getWorkWeekDates(weekStart); const workWeekSettings = this.config.getWorkWeekSettings(); - workWeekSettings.workDays.forEach((dayOfWeek, i) => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - + + // Render headers for target week + dates.forEach((date, i) => { const headerElement = document.createElement('swp-day-header'); - if (this.isToday(date)) { + if (this.dateCalculator.isToday(date)) { headerElement.dataset.today = 'true'; } @@ -117,20 +117,15 @@ export class NavigationRenderer { ${workWeekSettings.dayNames[i]} ${date.getDate()} `; - headerElement.dataset.date = this.formatDate(date); + headerElement.dataset.date = this.dateCalculator.formatISODate(date); header.appendChild(headerElement); }); // Render day columns for target week - workWeekSettings.workDays.forEach(dayOfWeek => { + dates.forEach(date => { const column = document.createElement('swp-day-column'); - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - column.dataset.date = this.formatDate(date); + column.dataset.date = this.dateCalculator.formatISODate(date); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); @@ -139,18 +134,4 @@ export class NavigationRenderer { }); } - /** - * Utility method to format date - */ - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } - - /** - * Check if date is today - */ - private isToday(date: Date): boolean { - const today = new Date(); - return date.toDateString() === today.toDateString(); - } } \ No newline at end of file diff --git a/src/utils/DateCalculator.ts b/src/utils/DateCalculator.ts new file mode 100644 index 0000000..3ca32f5 --- /dev/null +++ b/src/utils/DateCalculator.ts @@ -0,0 +1,194 @@ +/** + * DateCalculator - Centralized date calculation logic for calendar + * Handles all date computations with proper week start handling + */ + +import { CalendarConfig } from '../core/CalendarConfig'; + +export class DateCalculator { + private config: CalendarConfig; + + constructor(config: CalendarConfig) { + this.config = config; + } + + /** + * Get dates for work week based on week start and work days configuration + * @param weekStart - The start date of the week (could be Sunday or Monday) + * @returns Array of dates for the configured work days + */ + getWorkWeekDates(weekStart: Date): Date[] { + const dates: Date[] = []; + const workWeekSettings = this.config.getWorkWeekSettings(); + const dateSettings = this.config.getDateViewSettings(); + const firstDayOfWeek = dateSettings.firstDayOfWeek; // 0=Sunday, 1=Monday + + // Get the actual start of the week based on configuration + const actualWeekStart = this.getWeekStart(weekStart, firstDayOfWeek); + + // Calculate dates for each work day + workWeekSettings.workDays.forEach(dayOfWeek => { + const date = new Date(actualWeekStart); + + if (firstDayOfWeek === 1 && dayOfWeek === 0) { + // For Monday-based weeks, Sunday is at the end (day 7) + date.setDate(actualWeekStart.getDate() + 7); + } else { + // Normal calculation + date.setDate(actualWeekStart.getDate() + dayOfWeek); + } + + dates.push(date); + }); + + return dates; + } + + /** + * Get the start of the week for a given date + * @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 { + const weekStart = new Date(date); + const currentDay = weekStart.getDay(); + + if (firstDayOfWeek === 1) { + // Monday as first day + const daysToSubtract = currentDay === 0 ? 6 : currentDay - 1; + weekStart.setDate(weekStart.getDate() - daysToSubtract); + } else { + // Sunday as first day + weekStart.setDate(weekStart.getDate() - currentDay); + } + + weekStart.setHours(0, 0, 0, 0); + return weekStart; + } + + /** + * Get the end of the week for a given date + * @param date - Any date in the week + * @param firstDayOfWeek - 0 for Sunday, 1 for Monday + * @returns The end date of the week + */ + getWeekEnd(date: Date, firstDayOfWeek: number = 1): Date { + const weekStart = this.getWeekStart(date, firstDayOfWeek); + 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) + */ + 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); + } + + /** + * Format a date range for display + * @param start - Start date + * @param end - End date + * @returns Formatted date range string + */ + formatDateRange(start: Date, end: Date): string { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + const startMonth = months[start.getMonth()]; + const endMonth = months[end.getMonth()]; + const startDay = start.getDate(); + const endDay = end.getDate(); + const startYear = start.getFullYear(); + const endYear = end.getFullYear(); + + if (startYear !== endYear) { + return `${startMonth} ${startDay}, ${startYear} - ${endMonth} ${endDay}, ${endYear}`; + } else if (startMonth === endMonth) { + return `${startMonth} ${startDay} - ${endDay}, ${startYear}`; + } else { + return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${startYear}`; + } + } + + /** + * Format a date to ISO date string (YYYY-MM-DD) + * @param date - Date to format + * @returns ISO date string + */ + formatISODate(date: Date): string { + 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 + */ + isToday(date: Date): boolean { + 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 + */ + addDays(date: Date, days: number): Date { + 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 + */ + addWeeks(date: Date, weeks: number): Date { + return this.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 + */ + getFullWeekDates(weekStart: Date): Date[] { + const dates: Date[] = []; + for (let i = 0; i < 7; i++) { + dates.push(this.addDays(weekStart, i)); + } + return dates; + } + + /** + * Get the day name for a date + * @param date - Date to get day name for + * @param format - 'short' or 'long' + * @returns Day name + */ + 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()]; + } +} + +// Create singleton instance with config +export function createDateCalculator(config: CalendarConfig): DateCalculator { + return new DateCalculator(config); +} \ No newline at end of file