Introduces type-safe EventId with centralized normalization logic for clone and standard event IDs Refactors event ID management across multiple components to use consistent ID transformation methods Improves type safety and reduces potential ID-related bugs in drag-and-drop and event rendering
246 lines
9.7 KiB
Markdown
246 lines
9.7 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
Calendar Plantempus is a professional TypeScript calendar component with offline-first architecture, drag-and-drop functionality, and real-time synchronization capabilities. Supports both date-based (day/week/month) and resource-based (people, rooms) calendar views.
|
|
|
|
## Build & Development Commands
|
|
|
|
```bash
|
|
# Build the project (bundles to wwwroot/js/calendar.js)
|
|
npm run build
|
|
|
|
# Watch mode for development
|
|
npm run watch
|
|
|
|
# Clean build output
|
|
npm run clean
|
|
|
|
# Type check only
|
|
npx tsc --noEmit
|
|
|
|
# Run all tests (watch mode)
|
|
npm test
|
|
|
|
# Run tests once and exit
|
|
npm run test:run
|
|
|
|
# Run tests with UI
|
|
npm run test:ui
|
|
|
|
# Run single test file
|
|
npm test -- <test-file-name>
|
|
|
|
# CSS Development
|
|
npm run css:build # Build CSS
|
|
npm run css:watch # Watch and rebuild CSS
|
|
npm run css:build:prod # Build minified production CSS
|
|
npm run css:analyze # Analyze CSS metrics
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Core Design Pattern: Dependency Injection with NovaDI
|
|
|
|
The application uses **NovaDI** (@novadi/core) for dependency injection. All managers, services, and repositories are registered in `src/index.ts` and resolved through the DI container.
|
|
|
|
**Key principle**: Never instantiate managers or services directly with `new`. Always use constructor injection and register types in the container.
|
|
|
|
### Event-Driven Architecture
|
|
|
|
The application uses a **centralized EventBus** (`src/core/EventBus.ts`) built on DOM CustomEvents for all inter-component communication. This is the ONLY way components should communicate.
|
|
|
|
- All event types are defined in `src/constants/CoreEvents.ts` (reduced from 102+ to ~20 core events)
|
|
- Components emit events via `eventBus.emit(CoreEvents.EVENT_NAME, payload)`
|
|
- Components subscribe via `eventBus.on(CoreEvents.EVENT_NAME, handler)`
|
|
- Never call methods directly between managers - always use events
|
|
|
|
### Calendar Modes: Date vs Resource
|
|
|
|
The calendar supports two column modes, configured at initialization in `src/index.ts`:
|
|
|
|
**Date Mode** (`DateColumnDataSource`):
|
|
- Columns represent dates (day/week/month views)
|
|
- Uses `DateHeaderRenderer` and `DateColumnRenderer`
|
|
|
|
**Resource Mode** (`ResourceColumnDataSource`):
|
|
- Columns represent resources (people, rooms, equipment)
|
|
- Uses `ResourceHeaderRenderer` and `ResourceColumnRenderer`
|
|
- Events filtered per-resource via `IColumnInfo.events`
|
|
|
|
Both modes implement `IColumnDataSource` interface, allowing polymorphic column handling.
|
|
|
|
### Manager Hierarchy
|
|
|
|
**CalendarManager** (`src/managers/CalendarManager.ts`) - Top-level coordinator
|
|
- Manages calendar state (current view, current date)
|
|
- Orchestrates initialization sequence
|
|
- Coordinates other managers via EventBus
|
|
|
|
**Key Managers**:
|
|
- **EventManager** - Event CRUD operations, data loading from repository
|
|
- **GridManager** - Renders time grid structure
|
|
- **NavigationManager** - Date navigation and period calculations
|
|
- **DragDropManager** - Advanced drag-and-drop with smooth animations, type conversion (timed ↔ all-day), scroll compensation
|
|
- **ResizeHandleManager** - Event resizing with visual feedback
|
|
- **AllDayManager** - All-day event layout and rendering
|
|
- **HeaderManager** - Date headers and all-day event container
|
|
- **ScrollManager** - Scroll behavior and position management
|
|
- **EdgeScrollManager** - Automatic scrolling at viewport edges during drag
|
|
|
|
### Repository Pattern
|
|
|
|
Data access is abstracted through **IApiRepository<T>** interface (`src/repositories/IApiRepository.ts`):
|
|
- **MockEventRepository**, **MockBookingRepository**, **MockCustomerRepository**, **MockResourceRepository** - Development: Load from JSON files
|
|
- **ApiEventRepository**, **ApiBookingRepository**, **ApiCustomerRepository**, **ApiResourceRepository** - Production: Backend API calls
|
|
|
|
All repository methods accept an `UpdateSource` parameter ('local' | 'remote') to distinguish user actions from remote updates.
|
|
|
|
### Entity Service Pattern
|
|
|
|
**IEntityService<T>** (`src/storage/IEntityService.ts`) provides polymorphic entity handling:
|
|
- `EventService`, `BookingService`, `CustomerService`, `ResourceService` implement this interface
|
|
- Encapsulates sync status manipulation (SyncManager delegates, never manipulates directly)
|
|
- Uses `entityType` discriminator for runtime routing without switch statements
|
|
- Enables polymorphic operations: `Array<IEntityService<any>>` works across all entity types
|
|
|
|
### Offline-First Sync Architecture
|
|
|
|
**SyncManager** (`src/workers/SyncManager.ts`) provides background synchronization:
|
|
1. Local changes are written to **IndexedDB** immediately
|
|
2. Operations are queued in **OperationQueue**
|
|
3. SyncManager processes queue when online (5-second polling)
|
|
4. Failed operations retry with exponential backoff (max 5 retries)
|
|
5. Events have `syncStatus`: 'synced' | 'pending' | 'error'
|
|
|
|
### Rendering Strategy Pattern
|
|
|
|
**EventRenderingService** (`src/renderers/EventRendererManager.ts`) uses strategy pattern:
|
|
- **IEventRenderer** interface defines rendering contract
|
|
- **DateEventRenderer** - Renders timed events in day columns
|
|
- **AllDayEventRenderer** - Renders all-day events in header
|
|
- Strategies can be swapped without changing core logic
|
|
|
|
### Layout Engines
|
|
|
|
**EventStackManager** (`src/managers/EventStackManager.ts`) - Uses CSS flexbox for overlapping events:
|
|
- Groups overlapping events into stacks
|
|
- Calculates flex positioning (basis, grow, shrink)
|
|
- Handles multi-column spanning events
|
|
|
|
**AllDayLayoutEngine** (`src/utils/AllDayLayoutEngine.ts`) - Row-based layout for all-day events:
|
|
- Detects overlaps and assigns row positions
|
|
- Supports collapsed view (max 4 rows) with "+N more" indicator
|
|
- Calculates container height dynamically
|
|
|
|
### Configuration System
|
|
|
|
Configuration is loaded from `wwwroot/data/calendar-config.json` via **ConfigManager**:
|
|
- **GridSettings** - Hour height, work hours, snap interval
|
|
- **DateViewSettings** - Period type, first day of week
|
|
- **TimeFormatConfig** - Timezone, locale, 12/24-hour format
|
|
- **WorkWeekSettings** - Configurable work week presets
|
|
- **Interaction** - Enable/disable drag, resize, create
|
|
|
|
Access via injected `Configuration` instance, never load config directly.
|
|
|
|
## Important Patterns & Conventions
|
|
|
|
### Event Type Conversion (Drag & Drop)
|
|
|
|
When dragging events between timed grid and all-day area:
|
|
- **Timed → All-day**: `DragDropManager` emits `drag:mouseenter-header`, `AllDayManager` creates all-day clone
|
|
- **All-day → Timed**: `DragDropManager` emits `drag:mouseenter-column`, `EventRenderingService` creates timed clone
|
|
- Original element is marked with `data-conversion-source="true"`
|
|
- Clone is marked with `data-converted-clone="true"`
|
|
|
|
### Scroll Compensation During Drag
|
|
|
|
`DragDropManager` tracks scroll delta during edge-scrolling:
|
|
1. Listens to `edge-scroll:scrolling` events
|
|
2. Accumulates `scrollDeltaY` from scroll events
|
|
3. Compensates dragged element position: `targetY = mouseY - scrollDeltaY - mouseOffset.y`
|
|
4. Prevents visual "jumping" during scroll
|
|
|
|
### Grid Snapping
|
|
|
|
When dropping events, snap to time grid:
|
|
1. Get mouse Y position relative to column
|
|
2. Convert to time using `PositionUtils.getTimeAtPosition()`
|
|
3. Account for `mouseOffset.y` (click position within event)
|
|
4. Snap to nearest `snapInterval` (default 15 minutes)
|
|
|
|
### Testing with Vitest
|
|
|
|
Tests use **Vitest** with **jsdom** environment:
|
|
- Config: `vitest.config.ts`
|
|
- Setup file: `test/setup.ts`
|
|
- Test helpers: `test/helpers/dom-helpers.ts`, `test/helpers/config-helpers.ts`
|
|
- Run single test: `npm test -- <test-file-name>`
|
|
|
|
## Key Files to Know
|
|
|
|
- `src/index.ts` - DI container setup and initialization (also sets calendar mode: date vs resource)
|
|
- `src/core/EventBus.ts` - Central event dispatcher
|
|
- `src/constants/CoreEvents.ts` - All event type constants (~34 core events)
|
|
- `src/types/CalendarTypes.ts` - Core type definitions
|
|
- `src/types/ColumnDataSource.ts` - Column abstraction for date/resource modes
|
|
- `src/storage/IEntityService.ts` - Entity service interface for polymorphic sync
|
|
- `src/managers/CalendarManager.ts` - Main coordinator
|
|
- `src/managers/DragDropManager.ts` - Detailed drag-drop architecture docs
|
|
- `src/configurations/CalendarConfig.ts` - Configuration schema
|
|
- `wwwroot/data/calendar-config.json` - Runtime configuration
|
|
|
|
## Common Tasks
|
|
|
|
### Adding a New Event Type to CoreEvents
|
|
|
|
1. Add constant to `src/constants/CoreEvents.ts`
|
|
2. Define payload type in `src/types/EventTypes.ts`
|
|
3. Emit with `eventBus.emit(CoreEvents.NEW_EVENT, payload)`
|
|
4. Subscribe with `eventBus.on(CoreEvents.NEW_EVENT, handler)`
|
|
|
|
### Adding a New Manager
|
|
|
|
1. Create in `src/managers/`
|
|
2. Inject dependencies via constructor (EventBus, Configuration, other managers)
|
|
3. Register in DI container in `src/index.ts`: `builder.registerType(NewManager).as<NewManager>()`
|
|
4. Communicate via EventBus only, never direct method calls
|
|
5. Initialize in CalendarManager if needed
|
|
|
|
### Modifying Event Data
|
|
|
|
Always go through EventManager:
|
|
- Create: `eventManager.createEvent(eventData)`
|
|
- Update: `eventManager.updateEvent(id, updates)`
|
|
- Delete: `eventManager.deleteEvent(id)`
|
|
|
|
EventManager handles repository calls, event emission, and UI updates.
|
|
|
|
### Debugging
|
|
|
|
Debug mode is enabled in development:
|
|
```javascript
|
|
eventBus.setDebug(true); // In src/index.ts
|
|
```
|
|
|
|
Access debug interface in browser console:
|
|
```javascript
|
|
window.calendarDebug.eventBus.getEventLog()
|
|
window.calendarDebug.calendarManager
|
|
window.calendarDebug.eventManager
|
|
window.calendarDebug.auditService
|
|
window.calendarDebug.syncManager
|
|
window.calendarDebug.app // Full DI container
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- **@novadi/core** - Dependency injection framework
|
|
- **dayjs** - Date manipulation and formatting
|
|
- **fuse.js** - Fuzzy search for event filtering
|
|
- **esbuild** - Fast bundler for development
|
|
- **vitest** - Testing framework
|
|
- **postcss** - CSS processing and optimization
|