diff --git a/src/constants/EventTypes.ts b/src/constants/EventTypes.ts index 3134579..d51129d 100644 --- a/src/constants/EventTypes.ts +++ b/src/constants/EventTypes.ts @@ -13,6 +13,7 @@ export const EventTypes = { CONFIG_UPDATE: 'calendar:configupdate', CALENDAR_TYPE_CHANGED: 'calendar:calendartypechanged', SELECTED_DATE_CHANGED: 'calendar:selecteddatechanged', + WORKWEEK_CHANGED: 'calendar:workweekchanged', // View change events VIEW_CHANGE: 'calendar:viewchange', @@ -86,7 +87,7 @@ export const EventTypes = { // Management events (legacy - prefer StateEvents) REFRESH_REQUESTED: 'calendar:refreshrequested', RESET_REQUESTED: 'calendar:resetrequested', - CALENDAR_REFRESH_REQUESTED: 'calendar:refreshrequested', + CALENDAR_REFRESH_REQUESTED: 'calendar:calendarrefreshrequested', CALENDAR_RESET: 'calendar:reset', // System events diff --git a/src/core/CalendarConfig.ts b/src/core/CalendarConfig.ts index 7aeef1a..cef5e5c 100644 --- a/src/core/CalendarConfig.ts +++ b/src/core/CalendarConfig.ts @@ -35,6 +35,17 @@ interface DateViewSettings { showAllDay: boolean; // Show all-day event row } +/** + * Work week configuration settings + */ +interface WorkWeekSettings { + id: string; + workDays: number[]; // [1,2,3,4,5] for mon-fri + dayNames: string[]; // ['Mon','Tue','Wed','Thu','Fri'] + totalDays: number; // 5 + firstWorkDay: number; // 1 = Monday +} + /** * View settings for resource-based calendar mode */ @@ -57,6 +68,7 @@ export class CalendarConfig { private gridSettings: GridSettings; private dateViewSettings: DateViewSettings; private resourceViewSettings: ResourceViewSettings; + private currentWorkWeek: string = 'standard'; constructor() { this.config = { @@ -422,6 +434,83 @@ export class CalendarConfig { date: date }); } + + /** + * Get work week presets + */ + private getWorkWeekPresets(): { [key: string]: WorkWeekSettings } { + return { + 'standard': { + id: 'standard', + workDays: [1,2,3,4,5], + dayNames: ['Mon','Tue','Wed','Thu','Fri'], + totalDays: 5, + firstWorkDay: 1 + }, + 'compressed': { + id: 'compressed', + workDays: [1,2,3,4], + dayNames: ['Mon','Tue','Wed','Thu'], + totalDays: 4, + firstWorkDay: 1 + }, + 'midweek': { + id: 'midweek', + workDays: [3,4,5], + dayNames: ['Wed','Thu','Fri'], + totalDays: 3, + firstWorkDay: 3 + }, + 'weekend': { + id: 'weekend', + workDays: [6,0], + dayNames: ['Sat','Sun'], + totalDays: 2, + firstWorkDay: 6 + }, + 'fullweek': { + id: 'fullweek', + workDays: [0,1,2,3,4,5,6], + dayNames: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'], + totalDays: 7, + firstWorkDay: 0 + } + }; + } + + /** + * Get current work week settings + */ + getWorkWeekSettings(): WorkWeekSettings { + const presets = this.getWorkWeekPresets(); + return presets[this.currentWorkWeek] || presets['standard']; + } + + /** + * Set work week preset + */ + setWorkWeek(workWeekId: string): void { + const presets = this.getWorkWeekPresets(); + if (presets[workWeekId]) { + this.currentWorkWeek = workWeekId; + + // Update dateViewSettings to match work week + this.dateViewSettings.weekDays = presets[workWeekId].totalDays; + + // Emit work week change event + eventBus.emit(EventTypes.WORKWEEK_CHANGED, { + workWeekId: workWeekId, + settings: presets[workWeekId] + }); + } + } + + /** + * Get current work week ID + */ + getCurrentWorkWeek(): string { + return this.currentWorkWeek; + } } // Create singleton instance diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts index a19481c..66c0e7e 100644 --- a/src/managers/CalendarManager.ts +++ b/src/managers/CalendarManager.ts @@ -264,6 +264,13 @@ export class CalendarManager { this.refresh(); }); + // Lyt efter workweek changes + this.eventBus.on(EventTypes.WORKWEEK_CHANGED, (event: Event) => { + const customEvent = event as CustomEvent; + console.log('CalendarManager: Workweek changed to', customEvent.detail.workWeekId); + this.handleWorkweekChange(); + }); + // Lyt efter reset requests this.eventBus.on(EventTypes.RESET_REQUESTED, () => { this.reset(); @@ -281,7 +288,8 @@ export class CalendarManager { nextDate.setDate(nextDate.getDate() + 1); break; case 'week': - nextDate.setDate(nextDate.getDate() + 7); + const workWeekSettings = this.config.getWorkWeekSettings(); + nextDate.setDate(nextDate.getDate() + 7); // Move to next calendar week regardless of work days break; case 'month': nextDate.setMonth(nextDate.getMonth() + 1); @@ -302,7 +310,8 @@ export class CalendarManager { previousDate.setDate(previousDate.getDate() - 1); break; case 'week': - previousDate.setDate(previousDate.getDate() - 7); + const workWeekSettings = this.config.getWorkWeekSettings(); + previousDate.setDate(previousDate.getDate() - 7); // Move to previous calendar week regardless of work days break; case 'month': previousDate.setMonth(previousDate.getMonth() - 1); @@ -370,4 +379,42 @@ export class CalendarManager { } } + /** + * Handle workweek configuration changes + */ + private handleWorkweekChange(): void { + console.log('CalendarManager: Handling workweek change - forcing full grid rebuild'); + + // Force a complete grid rebuild by clearing existing structure + const container = document.querySelector('swp-calendar-container'); + if (container) { + container.innerHTML = ''; // Clear everything to force full rebuild + } + + // Re-render the grid with new workweek settings (will now rebuild everything) + this.gridManager.render(); + + // Re-initialize scroll manager after grid rebuild + this.scrollManager.initialize(); + + // Re-render events in the new grid structure + this.rerenderEvents(); + } + + /** + * Re-render events after grid structure changes + */ + private rerenderEvents(): void { + console.log('CalendarManager: Re-rendering events for new workweek'); + + // Get current period data to determine date range + const periodData = this.calculateCurrentPeriod(); + + // Trigger event rendering for the current date range + this.eventRenderer.renderEventsForDateRange( + periodData.startDate, + periodData.endDate + ); + } + } \ No newline at end of file diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 9ec7b24..2208b42 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -2,6 +2,7 @@ import { IEventBus } from '../types/CalendarTypes.js'; import { DateUtils } from '../utils/DateUtils.js'; import { EventTypes } from '../constants/EventTypes.js'; import { NavigationRenderer } from '../renderers/NavigationRenderer.js'; +import { calendarConfig } from '../core/CalendarConfig.js'; /** * NavigationManager handles calendar navigation (prev/next/today buttons) @@ -17,7 +18,7 @@ export class NavigationManager { constructor(eventBus: IEventBus) { console.log('🧭 NavigationManager: Constructor called'); this.eventBus = eventBus; - this.navigationRenderer = new NavigationRenderer(eventBus); + this.navigationRenderer = new NavigationRenderer(eventBus, calendarConfig); this.currentWeek = DateUtils.getWeekStart(new Date(), 0); // Sunday start like POC this.targetWeek = new Date(this.currentWeek); this.init(); diff --git a/src/managers/ViewManager.ts b/src/managers/ViewManager.ts index a121e51..1f9c8f3 100644 --- a/src/managers/ViewManager.ts +++ b/src/managers/ViewManager.ts @@ -33,6 +33,9 @@ export class ViewManager { // Setup view button handlers this.setupViewButtonHandlers(); + + // Setup workweek preset button handlers + this.setupWorkweekButtonHandlers(); } private setupViewButtonHandlers(): void { @@ -48,8 +51,22 @@ export class ViewManager { }); } + private setupWorkweekButtonHandlers(): void { + const workweekButtons = document.querySelectorAll('swp-preset-button[data-workweek]'); + workweekButtons.forEach(button => { + button.addEventListener('click', (event) => { + event.preventDefault(); + const workweekId = button.getAttribute('data-workweek'); + if (workweekId) { + this.changeWorkweek(workweekId); + } + }); + }); + } + private initializeView(): void { this.updateViewButtons(); + this.updateWorkweekButtons(); this.eventBus.emit(EventTypes.VIEW_RENDERED, { view: this.currentView @@ -72,6 +89,18 @@ export class ViewManager { }); } + private changeWorkweek(workweekId: string): void { + console.log(`ViewManager: Changing workweek to ${workweekId}`); + + // Update the calendar config + calendarConfig.setWorkWeek(workweekId); + + // Update button states + this.updateWorkweekButtons(); + + // Trigger a calendar refresh to apply the new workweek + this.eventBus.emit(EventTypes.REFRESH_REQUESTED); + } private updateViewButtons(): void { const viewButtons = document.querySelectorAll('swp-view-button[data-view]'); @@ -85,6 +114,20 @@ export class ViewManager { }); } + private updateWorkweekButtons(): void { + const currentWorkweek = calendarConfig.getCurrentWorkWeek(); + const workweekButtons = document.querySelectorAll('swp-preset-button[data-workweek]'); + + workweekButtons.forEach(button => { + const buttonWorkweek = button.getAttribute('data-workweek'); + if (buttonWorkweek === currentWorkweek) { + button.setAttribute('data-active', 'true'); + } else { + button.removeAttribute('data-active'); + } + }); + } + private refreshCurrentView(): void { this.eventBus.emit(EventTypes.VIEW_RENDERED, { view: this.currentView diff --git a/src/renderers/ColumnRenderer.ts b/src/renderers/ColumnRenderer.ts index 4fdf151..a6e55f9 100644 --- a/src/renderers/ColumnRenderer.ts +++ b/src/renderers/ColumnRenderer.ts @@ -26,7 +26,7 @@ export class DateColumnRenderer implements ColumnRenderer { render(columnContainer: HTMLElement, context: ColumnRenderContext): void { const { currentWeek, config } = context; - const dates = this.getWeekDates(currentWeek); + const dates = this.getWeekDates(currentWeek, config); const dateSettings = config.getDateViewSettings(); const daysToShow = dates.slice(0, dateSettings.weekDays); @@ -43,13 +43,20 @@ export class DateColumnRenderer implements ColumnRenderer { }); } - private getWeekDates(weekStart: Date): Date[] { + private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { const dates: Date[] = []; - for (let i = 0; i < 7; i++) { + 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); - date.setDate(weekStart.getDate() + i); + // 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; } diff --git a/src/renderers/GridStyleManager.ts b/src/renderers/GridStyleManager.ts index c6cd417..44fb3df 100644 --- a/src/renderers/GridStyleManager.ts +++ b/src/renderers/GridStyleManager.ts @@ -60,19 +60,21 @@ export class GridStyleManager { return resourceData.resources.length; } else if (calendarType === 'date') { const dateSettings = this.config.getDateViewSettings(); + const workWeekSettings = this.config.getWorkWeekSettings(); + switch (dateSettings.period) { case 'day': return 1; case 'week': - return dateSettings.weekDays; + return workWeekSettings.totalDays; case 'month': - return 7; + return workWeekSettings.totalDays; // Use work week for month view too default: - return dateSettings.weekDays; + return workWeekSettings.totalDays; } } - return 7; // Default + return this.config.getWorkWeekSettings().totalDays; // Default to work week } /** diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index f12f06b..cf2d734 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -27,18 +27,19 @@ export class DateHeaderRenderer implements HeaderRenderer { render(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek); + const dates = this.getWeekDates(currentWeek, config); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); - daysToShow.forEach((date) => { + const workWeekSettings = config.getWorkWeekSettings(); + daysToShow.forEach((date, index) => { const header = document.createElement('swp-day-header'); if (this.isToday(date)) { (header as any).dataset.today = 'true'; } header.innerHTML = ` - ${this.getDayName(date)} + ${workWeekSettings.dayNames[index]} ${date.getDate()} `; (header as any).dataset.date = this.formatDate(date); @@ -53,7 +54,7 @@ export class DateHeaderRenderer implements HeaderRenderer { private renderAllDayEvents(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek); + const dates = this.getWeekDates(currentWeek, config); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); @@ -101,13 +102,20 @@ export class DateHeaderRenderer implements HeaderRenderer { }); } - private getWeekDates(weekStart: Date): Date[] { + private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { const dates: Date[] = []; - for (let i = 0; i < 7; i++) { + 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); - date.setDate(weekStart.getDate() + i); + // 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; } @@ -120,10 +128,6 @@ export class DateHeaderRenderer implements HeaderRenderer { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } - private getDayName(date: Date): string { - const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - return days[date.getDay()]; - } } /** diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index 82ef28a..bc22ce3 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -1,6 +1,7 @@ import { IEventBus } from '../types/CalendarTypes'; import { EventTypes } from '../constants/EventTypes'; import { DateUtils } from '../utils/DateUtils'; +import { CalendarConfig } from '../core/CalendarConfig'; /** * NavigationRenderer - Handles DOM rendering for navigation containers @@ -8,9 +9,11 @@ import { DateUtils } from '../utils/DateUtils'; */ export class NavigationRenderer { private eventBus: IEventBus; + private config: CalendarConfig; - constructor(eventBus: IEventBus) { + constructor(eventBus: IEventBus, config: CalendarConfig) { this.eventBus = eventBus; + this.config = config; this.setupEventListeners(); } @@ -97,10 +100,13 @@ export class NavigationRenderer { dayColumns.innerHTML = ''; // Render headers for target week - const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - for (let i = 0; i < 7; i++) { + const workWeekSettings = this.config.getWorkWeekSettings(); + workWeekSettings.workDays.forEach((dayOfWeek, i) => { const date = new Date(weekStart); - date.setDate(date.getDate() + i); + // 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); const headerElement = document.createElement('swp-day-header'); if (this.isToday(date)) { @@ -108,26 +114,29 @@ export class NavigationRenderer { } headerElement.innerHTML = ` - ${days[date.getDay()]} + ${workWeekSettings.dayNames[i]} ${date.getDate()} `; headerElement.dataset.date = this.formatDate(date); header.appendChild(headerElement); - } + }); // Render day columns for target week - for (let i = 0; i < 7; i++) { + workWeekSettings.workDays.forEach(dayOfWeek => { const column = document.createElement('swp-day-column'); const date = new Date(weekStart); - date.setDate(date.getDate() + i); + // 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); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); dayColumns.appendChild(column); - } + }); } /** diff --git a/wwwroot/css/calendar-components-css.css b/wwwroot/css/calendar-components-css.css index a0b6668..df8ff1c 100644 --- a/wwwroot/css/calendar-components-css.css +++ b/wwwroot/css/calendar-components-css.css @@ -87,6 +87,44 @@ swp-view-button { } } +/* Workweek Presets */ +swp-workweek-presets { + display: flex; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 4px; + overflow: hidden; + margin-left: 16px; +} + +swp-preset-button { + padding: 6px 12px; + border: none; + background: transparent; + cursor: pointer; + font-size: 0.75rem; + font-weight: 500; + transition: all var(--transition-fast); + position: relative; + color: var(--color-text-secondary); + + &:not(:last-child) { + border-right: 1px solid var(--color-border); + } + + + &[data-active="true"] { + background: var(--color-primary); + color: white; + font-weight: 600; + } + + &[disabled] { + opacity: 0.5; + cursor: not-allowed; + } +} + /* Search container */ swp-search-container { margin-left: auto; diff --git a/wwwroot/index.html b/wwwroot/index.html index ed4a102..2219578 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -48,6 +48,14 @@ Week Month + + + Mon-Fri + Mon-Thu + Wed-Fri + Sat-Sun + 7 Days +