diff --git a/src/core/CalendarApp.ts b/src/core/CalendarApp.ts index b590250..3820e0b 100644 --- a/src/core/CalendarApp.ts +++ b/src/core/CalendarApp.ts @@ -24,7 +24,7 @@ import { export class CalendarApp { private animator!: NavigationAnimator; private container!: HTMLElement; - private weekOffset = 0; + private dayOffset = 0; private currentViewId = 'simple'; private workweekPreset: IWorkweekPreset | null = null; private groupingOverrides: Map = new Map(); @@ -126,13 +126,15 @@ export class CalendarApp { } private async handleNavigatePrev(): Promise { - this.weekOffset--; + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset -= step; await this.animator.slide('right', () => this.render()); this.emitStatus('rendered', { viewId: this.currentViewId }); } private async handleNavigateNext(): Promise { - this.weekOffset++; + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset += step; await this.animator.slide('left', () => this.render()); this.emitStatus('rendered', { viewId: this.currentViewId }); } @@ -159,11 +161,15 @@ export class CalendarApp { return; } - // Populate date values based on workweek and offset + // Populate date values based on workweek preset and day offset const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5]; - const dates = this.currentViewId === 'day' - ? this.dateService.getWeekDates(this.weekOffset, 1) - : this.dateService.getWorkWeekDates(this.weekOffset, workDays); + const periodDays = this.workweekPreset?.periodDays ?? 7; + + // For single-day navigation (periodDays=1), show consecutive days from offset + // For week navigation (periodDays=7), show workDays from the week containing offset + const dates = periodDays === 1 + ? this.dateService.getDatesFromOffset(this.dayOffset, workDays.length) + : this.dateService.getWorkDaysFromOffset(this.dayOffset, workDays); // Clone config and apply overrides const viewConfig: ViewConfig = { diff --git a/src/core/DateService.ts b/src/core/DateService.ts index 760388a..1d3c44d 100644 --- a/src/core/DateService.ts +++ b/src/core/DateService.ts @@ -41,21 +41,30 @@ export class DateService { return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); } - getWeekDates(offset = 0, days = 7): string[] { - const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week'); - return Array.from({ length: days }, (_, i) => - monday.add(i, 'day').format('YYYY-MM-DD') + /** + * Get dates starting from a day offset + * @param dayOffset - Day offset from base date + * @param count - Number of consecutive days to return + * @returns Array of date strings in YYYY-MM-DD format + */ + getDatesFromOffset(dayOffset: number, count: number): string[] { + const startDate = this.baseDate.add(dayOffset, 'day'); + return Array.from({ length: count }, (_, i) => + startDate.add(i, 'day').format('YYYY-MM-DD') ); } /** - * Get dates for specific weekdays within a week - * @param offset - Week offset from base date (0 = current week) + * Get specific weekdays from the week containing the offset date + * @param dayOffset - Day offset from base date * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) * @returns Array of date strings in YYYY-MM-DD format */ - getWorkWeekDates(offset: number, workDays: number[]): string[] { - const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week'); + getWorkDaysFromOffset(dayOffset: number, workDays: number[]): string[] { + // Get the date at offset, then find its week's Monday + const targetDate = this.baseDate.add(dayOffset, 'day'); + const monday = targetDate.startOf('week').add(1, 'day'); + return workDays.map(isoDay => { // ISO: 1=Monday, 7=Sunday → days from Monday: 0-6 const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; @@ -63,6 +72,15 @@ export class DateService { }); } + // Legacy methods for backwards compatibility + getWeekDates(weekOffset = 0, days = 7): string[] { + return this.getDatesFromOffset(weekOffset * 7, days); + } + + getWorkWeekDates(weekOffset: number, workDays: number[]): string[] { + return this.getWorkDaysFromOffset(weekOffset * 7, workDays); + } + // ============================================ // FORMATTING // ============================================ diff --git a/src/types/SettingsTypes.ts b/src/types/SettingsTypes.ts index 8f25f45..5ae47aa 100644 --- a/src/types/SettingsTypes.ts +++ b/src/types/SettingsTypes.ts @@ -17,6 +17,7 @@ export interface IWorkweekPreset { id: string; workDays: number[]; label: string; + periodDays: number; // Navigation step in days (1 = day, 7 = week) } /** diff --git a/wwwroot/data/mock-events.json b/wwwroot/data/mock-events.json index 16c70e6..82297ee 100644 --- a/wwwroot/data/mock-events.json +++ b/wwwroot/data/mock-events.json @@ -1,4 +1,262 @@ [ + { + "id": "EVT-NOV24-001", + "title": "Herreklipning", + "start": "2025-11-24T09:00:00", + "end": "2025-11-24T09:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N01", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "color": "blue" } + }, + { + "id": "EVT-NOV24-002", + "title": "Balayage", + "start": "2025-11-24T10:00:00", + "end": "2025-11-24T12:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N02", + "resourceId": "EMP002", + "customerId": "CUST002", + "syncStatus": "synced", + "metadata": { "color": "purple" } + }, + { + "id": "EVT-NOV24-003", + "title": "Klipning dame", + "start": "2025-11-24T14:00:00", + "end": "2025-11-24T15:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N03", + "resourceId": "EMP003", + "customerId": "CUST003", + "syncStatus": "synced", + "metadata": { "color": "teal" } + }, + { + "id": "EVT-NOV25-001", + "title": "Farve behandling", + "start": "2025-11-25T09:00:00", + "end": "2025-11-25T11:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N04", + "resourceId": "EMP001", + "customerId": "CUST004", + "syncStatus": "synced", + "metadata": { "color": "orange" } + }, + { + "id": "EVT-NOV25-002", + "title": "Permanent", + "start": "2025-11-25T12:00:00", + "end": "2025-11-25T14:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N05", + "resourceId": "EMP004", + "customerId": "CUST005", + "syncStatus": "synced", + "metadata": { "color": "indigo" } + }, + { + "id": "EVT-NOV26-001", + "title": "Highlights", + "start": "2025-11-26T10:00:00", + "end": "2025-11-26T12:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N06", + "resourceId": "EMP002", + "customerId": "CUST006", + "syncStatus": "synced", + "metadata": { "color": "lime" } + }, + { + "id": "EVT-NOV26-002", + "title": "Børneklip", + "start": "2025-11-26T14:00:00", + "end": "2025-11-26T14:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N07", + "resourceId": "EMP003", + "customerId": "CUST007", + "syncStatus": "synced", + "metadata": { "color": "cyan" } + }, + { + "id": "EVT-NOV27-001", + "title": "Olaplex behandling", + "start": "2025-11-27T09:00:00", + "end": "2025-11-27T10:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N08", + "resourceId": "EMP005", + "customerId": "CUST008", + "syncStatus": "synced", + "metadata": { "color": "pink" } + }, + { + "id": "EVT-NOV27-002", + "title": "Skæg trim", + "start": "2025-11-27T11:00:00", + "end": "2025-11-27T11:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N09", + "resourceId": "EMP001", + "customerId": "CUST009", + "syncStatus": "synced", + "metadata": { "color": "teal" } + }, + { + "id": "EVT-NOV28-001", + "title": "Extensions", + "start": "2025-11-28T09:00:00", + "end": "2025-11-28T12:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N10", + "resourceId": "EMP002", + "customerId": "CUST010", + "syncStatus": "synced", + "metadata": { "color": "deep-purple" } + }, + { + "id": "EVT-DEC01-001", + "title": "Klipning og føn", + "start": "2025-12-01T10:00:00", + "end": "2025-12-01T11:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N11", + "resourceId": "EMP001", + "customerId": "CUST011", + "syncStatus": "synced", + "metadata": { "color": "blue" } + }, + { + "id": "EVT-DEC01-002", + "title": "Farve korrektion", + "start": "2025-12-01T13:00:00", + "end": "2025-12-01T16:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N12", + "resourceId": "EMP004", + "customerId": "CUST012", + "syncStatus": "synced", + "metadata": { "color": "magenta" } + }, + { + "id": "EVT-DEC02-001", + "title": "Bryllupsfrisure prøve", + "start": "2025-12-02T09:00:00", + "end": "2025-12-02T11:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N13", + "resourceId": "EMP002", + "customerId": "CUST013", + "syncStatus": "synced", + "metadata": { "color": "green" } + }, + { + "id": "EVT-DEC02-002", + "title": "Herreklip express", + "start": "2025-12-02T14:00:00", + "end": "2025-12-02T14:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N14", + "resourceId": "EMP003", + "customerId": "CUST014", + "syncStatus": "synced", + "metadata": { "color": "amber" } + }, + { + "id": "EVT-DEC03-001", + "title": "Keratin behandling", + "start": "2025-12-03T10:00:00", + "end": "2025-12-03T13:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N15", + "resourceId": "EMP005", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "color": "violet" } + }, + { + "id": "EVT-DEC03-002", + "title": "Vask og styling", + "start": "2025-12-03T15:00:00", + "end": "2025-12-03T15:45:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N16", + "resourceId": "EMP001", + "customerId": "CUST002", + "syncStatus": "synced", + "metadata": { "color": "light-green" } + }, + { + "id": "EVT-DEC04-001", + "title": "Balayage kort hår", + "start": "2025-12-04T09:00:00", + "end": "2025-12-04T10:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N17", + "resourceId": "EMP002", + "customerId": "CUST003", + "syncStatus": "synced", + "metadata": { "color": "purple" } + }, + { + "id": "EVT-DEC04-002", + "title": "Ferie", + "start": "2025-12-04T00:00:00", + "end": "2025-12-05T23:59:59", + "type": "vacation", + "allDay": true, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "color": "red" } + }, + { + "id": "EVT-DEC05-001", + "title": "Dameklip langt hår", + "start": "2025-12-05T11:00:00", + "end": "2025-12-05T12:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N18", + "resourceId": "EMP003", + "customerId": "CUST004", + "syncStatus": "synced", + "metadata": { "color": "pink" } + }, + { + "id": "EVT-DEC05-002", + "title": "Highlights delvis", + "start": "2025-12-05T14:00:00", + "end": "2025-12-05T15:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-N19", + "resourceId": "EMP005", + "customerId": "CUST005", + "syncStatus": "synced", + "metadata": { "color": "lime" } + }, { "id": "EVT-DEC08-001", "title": "Balayage langt hår", @@ -11,7 +269,7 @@ "resourceId": "EMP001", "customerId": "CUST001", "syncStatus": "synced", - "metadata": { "duration": 60, "color": "purple" } + "metadata": { "color": "purple" } }, { "id": "EVT-DEC08-002", @@ -25,7 +283,7 @@ "resourceId": "EMP001", "customerId": "CUST002", "syncStatus": "synced", - "metadata": { "duration": 90, "color": "pink" } + "metadata": { "color": "pink" } }, { "id": "EVT-DEC08-003", @@ -39,7 +297,7 @@ "resourceId": "EMP002", "customerId": "CUST003", "syncStatus": "synced", - "metadata": { "duration": 120, "color": "indigo" } + "metadata": { "color": "indigo" } }, { "id": "EVT-DEC08-004", @@ -53,7 +311,7 @@ "resourceId": "EMP002", "customerId": "CUST004", "syncStatus": "synced", - "metadata": { "duration": 120, "color": "orange" } + "metadata": { "color": "orange" } }, { "id": "EVT-DEC09-001", @@ -67,21 +325,19 @@ "resourceId": "EMP003", "customerId": "CUST005", "syncStatus": "synced", - "metadata": { "duration": 30, "color": "teal" } + "metadata": { "color": "teal" } }, { "id": "EVT-DEC09-002", - "title": "Skæg trimning", - "description": "Skæg trim og styling", + "title": "Kursus", + "description": "Ekstern kursusdag", "start": "2025-12-09T00:00:00", "end": "2025-12-10T23:59:59", - "type": "customer", + "type": "blocked", "allDay": true, - "bookingId": "BOOK-006", "resourceId": "EMP003", - "customerId": "CUST006", "syncStatus": "synced", - "metadata": { "duration": 30, "color": "cyan" } + "metadata": { "color": "cyan" } }, { "id": "EVT-DEC09-003", @@ -95,7 +351,7 @@ "resourceId": "EMP004", "customerId": "CUST007", "syncStatus": "synced", - "metadata": { "duration": 120, "color": "green" } + "metadata": { "color": "green" } }, { "id": "EVT-DEC10-001", @@ -109,7 +365,7 @@ "resourceId": "EMP005", "customerId": "CUST008", "syncStatus": "synced", - "metadata": { "duration": 120, "color": "lime" } + "metadata": { "color": "lime" } }, { "id": "EVT-DEC10-002", @@ -121,7 +377,7 @@ "allDay": false, "resourceId": "EMP005", "syncStatus": "synced", - "metadata": { "duration": 30, "color": "amber" } + "metadata": { "color": "amber" } }, { "id": "EVT-DEC10-003", @@ -135,7 +391,7 @@ "resourceId": "EMP001", "customerId": "CUST009", "syncStatus": "synced", - "metadata": { "duration": 90, "color": "blue" } + "metadata": { "color": "blue" } }, { "id": "EVT-DEC11-001", @@ -149,7 +405,7 @@ "resourceId": "EMP002", "customerId": "CUST010", "syncStatus": "synced", - "metadata": { "duration": 180, "color": "deep-purple" } + "metadata": { "color": "deep-purple" } }, { "id": "EVT-DEC11-002", @@ -163,19 +419,19 @@ "resourceId": "EMP003", "customerId": "CUST011", "syncStatus": "synced", - "metadata": { "duration": 30, "color": "light-blue" } + "metadata": { "color": "light-blue" } }, { "id": "EVT-DEC11-003", - "title": "Frisør møde", - "description": "Team møde", + "title": "Team møde", + "description": "Morgen briefing", "start": "2025-12-11T08:00:00", "end": "2025-12-11T08:30:00", "type": "meeting", "allDay": false, "resourceId": "EMP001", "syncStatus": "synced", - "metadata": { "duration": 30, "color": "red" } + "metadata": { "color": "red" } }, { "id": "EVT-DEC12-001", @@ -189,7 +445,7 @@ "resourceId": "EMP004", "customerId": "CUST012", "syncStatus": "synced", - "metadata": { "duration": 180, "color": "violet" } + "metadata": { "color": "violet" } }, { "id": "EVT-DEC12-002", @@ -203,7 +459,7 @@ "resourceId": "EMP005", "customerId": "CUST013", "syncStatus": "synced", - "metadata": { "duration": 45, "color": "light-green" } + "metadata": { "color": "light-green" } }, { "id": "EVT-DEC12-003", @@ -217,6 +473,197 @@ "resourceId": "EMP001", "customerId": "CUST014", "syncStatus": "synced", - "metadata": { "duration": 180, "color": "magenta" } + "metadata": { "color": "magenta" } + }, + { + "id": "EVT-DEC15-001", + "title": "Juleklipning", + "start": "2025-12-15T09:00:00", + "end": "2025-12-15T10:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F01", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "color": "red" } + }, + { + "id": "EVT-DEC15-002", + "title": "Balayage jul", + "start": "2025-12-15T11:00:00", + "end": "2025-12-15T13:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F02", + "resourceId": "EMP002", + "customerId": "CUST002", + "syncStatus": "synced", + "metadata": { "color": "purple" } + }, + { + "id": "EVT-DEC15-003", + "title": "Herreklip", + "start": "2025-12-15T14:00:00", + "end": "2025-12-15T14:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F03", + "resourceId": "EMP003", + "customerId": "CUST003", + "syncStatus": "synced", + "metadata": { "color": "blue" } + }, + { + "id": "EVT-DEC16-001", + "title": "Farve og klip", + "start": "2025-12-16T10:00:00", + "end": "2025-12-16T12:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F04", + "resourceId": "EMP004", + "customerId": "CUST004", + "syncStatus": "synced", + "metadata": { "color": "orange" } + }, + { + "id": "EVT-DEC16-002", + "title": "Permanent", + "start": "2025-12-16T13:00:00", + "end": "2025-12-16T15:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F05", + "resourceId": "EMP005", + "customerId": "CUST005", + "syncStatus": "synced", + "metadata": { "color": "indigo" } + }, + { + "id": "EVT-DEC17-001", + "title": "Julefrokost", + "start": "2025-12-17T12:00:00", + "end": "2025-12-17T14:00:00", + "type": "meeting", + "allDay": false, + "resourceId": "EMP001", + "syncStatus": "synced", + "metadata": { "color": "green" } + }, + { + "id": "EVT-DEC17-002", + "title": "Extensions fjernelse", + "start": "2025-12-17T09:00:00", + "end": "2025-12-17T11:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F06", + "resourceId": "EMP002", + "customerId": "CUST006", + "syncStatus": "synced", + "metadata": { "color": "deep-purple" } + }, + { + "id": "EVT-DEC18-001", + "title": "Highlights jul", + "start": "2025-12-18T10:00:00", + "end": "2025-12-18T12:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F07", + "resourceId": "EMP001", + "customerId": "CUST007", + "syncStatus": "synced", + "metadata": { "color": "lime" } + }, + { + "id": "EVT-DEC18-002", + "title": "Børneklip jul", + "start": "2025-12-18T14:00:00", + "end": "2025-12-18T14:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F08", + "resourceId": "EMP003", + "customerId": "CUST008", + "syncStatus": "synced", + "metadata": { "color": "cyan" } + }, + { + "id": "EVT-DEC19-001", + "title": "Festfrisure", + "start": "2025-12-19T09:00:00", + "end": "2025-12-19T10:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F09", + "resourceId": "EMP004", + "customerId": "CUST009", + "syncStatus": "synced", + "metadata": { "color": "pink" } + }, + { + "id": "EVT-DEC19-002", + "title": "Julestyling", + "start": "2025-12-19T11:00:00", + "end": "2025-12-19T12:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F10", + "resourceId": "EMP005", + "customerId": "CUST010", + "syncStatus": "synced", + "metadata": { "color": "amber" } + }, + { + "id": "EVT-DEC19-003", + "title": "Klipning og farve", + "start": "2025-12-19T14:00:00", + "end": "2025-12-19T16:30:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F11", + "resourceId": "EMP001", + "customerId": "CUST011", + "syncStatus": "synced", + "metadata": { "color": "violet" } + }, + { + "id": "EVT-DEC22-001", + "title": "Sidste jul klip", + "start": "2025-12-22T09:00:00", + "end": "2025-12-22T10:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F12", + "resourceId": "EMP002", + "customerId": "CUST012", + "syncStatus": "synced", + "metadata": { "color": "red" } + }, + { + "id": "EVT-DEC22-002", + "title": "Juleaften forberedelse", + "start": "2025-12-22T11:00:00", + "end": "2025-12-22T13:00:00", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-F13", + "resourceId": "EMP003", + "customerId": "CUST013", + "syncStatus": "synced", + "metadata": { "color": "green" } + }, + { + "id": "EVT-DEC22-003", + "title": "Juleferie start", + "start": "2025-12-22T00:00:00", + "end": "2025-12-27T23:59:59", + "type": "vacation", + "allDay": true, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "color": "light-blue" } } ] diff --git a/wwwroot/data/tenant-settings.json b/wwwroot/data/tenant-settings.json index 0c70562..40d7782 100644 --- a/wwwroot/data/tenant-settings.json +++ b/wwwroot/data/tenant-settings.json @@ -6,27 +6,38 @@ "standard": { "id": "standard", "workDays": [1, 2, 3, 4, 5], - "label": "Man-Fre" + "label": "Man-Fre", + "periodDays": 7 }, "compressed": { "id": "compressed", "workDays": [1, 2, 3], - "label": "Man-Ons" + "label": "Man-Ons", + "periodDays": 7 }, "midweek": { "id": "midweek", "workDays": [4, 5], - "label": "Tors-Fre" + "label": "Tors-Fre", + "periodDays": 7 }, "weekend": { "id": "weekend", "workDays": [6, 7], - "label": "Weekend" + "label": "Weekend", + "periodDays": 7 }, "fullweek": { "id": "fullweek", "workDays": [1, 2, 3, 4, 5, 6, 7], - "label": "Alle dage" + "label": "Alle dage", + "periodDays": 7 + }, + "day": { + "id": "day", + "workDays": [1], + "label": "Dag", + "periodDays": 1 } }, "defaultPreset": "standard",