Implements strategy pattern for calendar views
Refactors the grid management to use a strategy pattern, allowing for different calendar views (week, month, day) to be rendered using separate strategy implementations. This approach improves code organization, reduces complexity within the main grid manager, and makes it easier to add new view types in the future. The strategy pattern centralizes view-specific logic, improves testability, and reduces code duplication. A month view strategy has been added and is now selectable via UI.
This commit is contained in:
parent
3ddc6352f2
commit
414ef1caaf
6 changed files with 527 additions and 252 deletions
148
src/strategies/MonthViewStrategy.ts
Normal file
148
src/strategies/MonthViewStrategy.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* MonthViewStrategy - Strategy for month view rendering
|
||||
* Completely different from week view - no time axis, cell-based events
|
||||
*/
|
||||
|
||||
import { ViewStrategy, ViewContext, ViewLayoutConfig } from './ViewStrategy';
|
||||
import { DateCalculator } from '../utils/DateCalculator';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
|
||||
export class MonthViewStrategy implements ViewStrategy {
|
||||
private dateCalculator: DateCalculator;
|
||||
|
||||
constructor() {
|
||||
this.dateCalculator = new DateCalculator(calendarConfig);
|
||||
}
|
||||
|
||||
getLayoutConfig(): ViewLayoutConfig {
|
||||
return {
|
||||
needsTimeAxis: false, // No time axis in month view!
|
||||
columnCount: 7, // Always 7 days (Mon-Sun)
|
||||
scrollable: false, // Month fits in viewport
|
||||
eventPositioning: 'cell-based' // Events go in day cells
|
||||
};
|
||||
}
|
||||
|
||||
renderGrid(context: ViewContext): void {
|
||||
console.group(`📅 MONTH VIEW: Rendering grid for ${context.currentDate.toDateString()}`);
|
||||
|
||||
// Clear existing content
|
||||
context.container.innerHTML = '';
|
||||
|
||||
// Create month grid (completely different from week!)
|
||||
this.createMonthGrid(context);
|
||||
|
||||
console.log('Month grid rendered with 7x6 layout');
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
private createMonthGrid(context: ViewContext): void {
|
||||
const monthGrid = document.createElement('div');
|
||||
monthGrid.className = 'month-grid';
|
||||
monthGrid.style.display = 'grid';
|
||||
monthGrid.style.gridTemplateColumns = 'repeat(7, 1fr)';
|
||||
monthGrid.style.gridTemplateRows = 'auto repeat(6, 1fr)';
|
||||
monthGrid.style.height = '100%';
|
||||
|
||||
// Add day headers (Mon, Tue, Wed, etc.)
|
||||
this.createDayHeaders(monthGrid);
|
||||
|
||||
// Add 6 weeks of day cells
|
||||
this.createDayCells(monthGrid, context.currentDate);
|
||||
|
||||
// Render events in day cells
|
||||
this.renderMonthEvents(monthGrid, context.allDayEvents);
|
||||
|
||||
context.container.appendChild(monthGrid);
|
||||
}
|
||||
|
||||
private createDayHeaders(container: HTMLElement): void {
|
||||
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
|
||||
dayNames.forEach(dayName => {
|
||||
const header = document.createElement('div');
|
||||
header.className = 'month-day-header';
|
||||
header.textContent = dayName;
|
||||
header.style.padding = '8px';
|
||||
header.style.fontWeight = 'bold';
|
||||
header.style.textAlign = 'center';
|
||||
header.style.borderBottom = '1px solid #e0e0e0';
|
||||
container.appendChild(header);
|
||||
});
|
||||
}
|
||||
|
||||
private createDayCells(container: HTMLElement, monthDate: Date): void {
|
||||
const dates = this.getMonthDates(monthDate);
|
||||
|
||||
dates.forEach(date => {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'month-day-cell';
|
||||
cell.dataset.date = this.dateCalculator.formatISODate(date);
|
||||
cell.style.border = '1px solid #e0e0e0';
|
||||
cell.style.minHeight = '100px';
|
||||
cell.style.padding = '4px';
|
||||
cell.style.position = 'relative';
|
||||
|
||||
// Day number
|
||||
const dayNumber = document.createElement('div');
|
||||
dayNumber.className = 'month-day-number';
|
||||
dayNumber.textContent = date.getDate().toString();
|
||||
dayNumber.style.fontWeight = 'bold';
|
||||
dayNumber.style.marginBottom = '4px';
|
||||
|
||||
// Check if today
|
||||
if (this.dateCalculator.isToday(date)) {
|
||||
dayNumber.style.color = '#1976d2';
|
||||
cell.style.backgroundColor = '#f5f5f5';
|
||||
}
|
||||
|
||||
cell.appendChild(dayNumber);
|
||||
container.appendChild(cell);
|
||||
});
|
||||
}
|
||||
|
||||
private getMonthDates(monthDate: Date): Date[] {
|
||||
// Get first day of month
|
||||
const firstOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
|
||||
|
||||
// Get Monday of the week containing first day
|
||||
const startDate = this.dateCalculator.getISOWeekStart(firstOfMonth);
|
||||
|
||||
// Generate 42 days (6 weeks)
|
||||
const dates: Date[] = [];
|
||||
for (let i = 0; i < 42; i++) {
|
||||
dates.push(this.dateCalculator.addDays(startDate, i));
|
||||
}
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
private renderMonthEvents(container: HTMLElement, events: any[]): void {
|
||||
// TODO: Implement month event rendering
|
||||
// Events will be small blocks in day cells
|
||||
console.log(`MonthViewStrategy: Would render ${events.length} events`);
|
||||
}
|
||||
|
||||
getNextPeriod(currentDate: Date): Date {
|
||||
return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
|
||||
}
|
||||
|
||||
getPreviousPeriod(currentDate: Date): Date {
|
||||
return new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
|
||||
}
|
||||
|
||||
getPeriodLabel(date: Date): string {
|
||||
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
return `${monthNames[date.getMonth()]} ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
getDisplayDates(baseDate: Date): Date[] {
|
||||
return this.getMonthDates(baseDate);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
console.log('MonthViewStrategy: Cleaning up');
|
||||
}
|
||||
}
|
||||
67
src/strategies/ViewStrategy.ts
Normal file
67
src/strategies/ViewStrategy.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* ViewStrategy - Strategy pattern for different calendar view types
|
||||
* Allows clean separation between week view, month view, day view etc.
|
||||
*/
|
||||
|
||||
import { AllDayEvent } from '../types/EventTypes';
|
||||
import { ResourceCalendarData } from '../types/CalendarTypes';
|
||||
|
||||
/**
|
||||
* Context object passed to strategy methods
|
||||
*/
|
||||
export interface ViewContext {
|
||||
currentDate: Date;
|
||||
container: HTMLElement;
|
||||
allDayEvents: AllDayEvent[];
|
||||
resourceData: ResourceCalendarData | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout configuration specific to each view type
|
||||
*/
|
||||
export interface ViewLayoutConfig {
|
||||
needsTimeAxis: boolean;
|
||||
columnCount: number;
|
||||
scrollable: boolean;
|
||||
eventPositioning: 'time-based' | 'cell-based';
|
||||
}
|
||||
|
||||
/**
|
||||
* Base strategy interface for all view types
|
||||
*/
|
||||
export interface ViewStrategy {
|
||||
/**
|
||||
* Get the layout configuration for this view
|
||||
*/
|
||||
getLayoutConfig(): ViewLayoutConfig;
|
||||
|
||||
/**
|
||||
* Render the grid structure for this view
|
||||
*/
|
||||
renderGrid(context: ViewContext): void;
|
||||
|
||||
/**
|
||||
* Calculate next period for navigation
|
||||
*/
|
||||
getNextPeriod(currentDate: Date): Date;
|
||||
|
||||
/**
|
||||
* Calculate previous period for navigation
|
||||
*/
|
||||
getPreviousPeriod(currentDate: Date): Date;
|
||||
|
||||
/**
|
||||
* Get display label for current period
|
||||
*/
|
||||
getPeriodLabel(date: Date): string;
|
||||
|
||||
/**
|
||||
* Get the dates that should be displayed in this view
|
||||
*/
|
||||
getDisplayDates(baseDate: Date): Date[];
|
||||
|
||||
/**
|
||||
* Clean up any view-specific resources
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
75
src/strategies/WeekViewStrategy.ts
Normal file
75
src/strategies/WeekViewStrategy.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* WeekViewStrategy - Strategy for week/day view rendering
|
||||
* Extracts the time-based grid logic from GridManager
|
||||
*/
|
||||
|
||||
import { ViewStrategy, ViewContext, ViewLayoutConfig } from './ViewStrategy';
|
||||
import { DateCalculator } from '../utils/DateCalculator';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { GridRenderer } from '../renderers/GridRenderer';
|
||||
import { GridStyleManager } from '../renderers/GridStyleManager';
|
||||
|
||||
export class WeekViewStrategy implements ViewStrategy {
|
||||
private dateCalculator: DateCalculator;
|
||||
private gridRenderer: GridRenderer;
|
||||
private styleManager: GridStyleManager;
|
||||
|
||||
constructor() {
|
||||
this.dateCalculator = new DateCalculator(calendarConfig);
|
||||
this.gridRenderer = new GridRenderer(calendarConfig);
|
||||
this.styleManager = new GridStyleManager(calendarConfig);
|
||||
}
|
||||
|
||||
getLayoutConfig(): ViewLayoutConfig {
|
||||
return {
|
||||
needsTimeAxis: true,
|
||||
columnCount: calendarConfig.getWorkWeekSettings().totalDays,
|
||||
scrollable: true,
|
||||
eventPositioning: 'time-based'
|
||||
};
|
||||
}
|
||||
|
||||
renderGrid(context: ViewContext): void {
|
||||
console.group(`🗓️ WEEK VIEW: Rendering grid for ${context.currentDate.toDateString()}`);
|
||||
|
||||
// Update grid styles
|
||||
this.styleManager.updateGridStyles(context.resourceData);
|
||||
|
||||
// Render the grid structure (time axis + day columns)
|
||||
this.gridRenderer.renderGrid(
|
||||
context.container,
|
||||
context.currentDate,
|
||||
context.resourceData,
|
||||
context.allDayEvents
|
||||
);
|
||||
|
||||
console.log(`Week grid rendered with ${this.getLayoutConfig().columnCount} columns`);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
getNextPeriod(currentDate: Date): Date {
|
||||
return this.dateCalculator.addWeeks(currentDate, 1);
|
||||
}
|
||||
|
||||
getPreviousPeriod(currentDate: Date): Date {
|
||||
return this.dateCalculator.addWeeks(currentDate, -1);
|
||||
}
|
||||
|
||||
getPeriodLabel(date: Date): string {
|
||||
const weekStart = this.dateCalculator.getISOWeekStart(date);
|
||||
const weekEnd = this.dateCalculator.addDays(weekStart, 6);
|
||||
const weekNumber = this.dateCalculator.getWeekNumber(date);
|
||||
|
||||
return `Week ${weekNumber}: ${this.dateCalculator.formatDateRange(weekStart, weekEnd)}`;
|
||||
}
|
||||
|
||||
getDisplayDates(baseDate: Date): Date[] {
|
||||
return this.dateCalculator.getWorkWeekDates(baseDate);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
// Clean up any week-specific resources
|
||||
// For now, just log
|
||||
console.log('WeekViewStrategy: Cleaning up');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue