diff --git a/src/datasources/DateColumnDataSource.ts b/src/datasources/DateColumnDataSource.ts index d4ac0dc..1e7fd51 100644 --- a/src/datasources/DateColumnDataSource.ts +++ b/src/datasources/DateColumnDataSource.ts @@ -30,7 +30,7 @@ export class DateColumnDataSource implements IColumnDataSource { /** * Get columns (dates) to display */ - public getColumns(): IColumnInfo[] { + public async getColumns(): Promise { let dates: Date[]; switch (this.currentView) { diff --git a/src/datasources/ResourceColumnDataSource.ts b/src/datasources/ResourceColumnDataSource.ts new file mode 100644 index 0000000..9e340ed --- /dev/null +++ b/src/datasources/ResourceColumnDataSource.ts @@ -0,0 +1,61 @@ +import { IColumnDataSource, IColumnInfo } from '../types/ColumnDataSource'; +import { CalendarView } from '../types/CalendarTypes'; +import { ResourceService } from '../storage/resources/ResourceService'; + +/** + * ResourceColumnDataSource - Provides resource-based columns + * + * In resource mode, columns represent resources (people, rooms, etc.) + * instead of dates. Events are still filtered by current date, + * but grouped by resourceId. + */ +export class ResourceColumnDataSource implements IColumnDataSource { + private resourceService: ResourceService; + private currentDate: Date; + private currentView: CalendarView; + + constructor(resourceService: ResourceService) { + this.resourceService = resourceService; + this.currentDate = new Date(); + this.currentView = 'day'; + } + + /** + * Get columns (resources) to display + */ + public async getColumns(): Promise { + const resources = await this.resourceService.getActive(); + return resources.map(resource => ({ + identifier: resource.id, + data: resource + })); + } + + /** + * Get type of datasource + */ + public getType(): 'date' | 'resource' { + return 'resource'; + } + + /** + * Update current date (for event filtering) + */ + public setCurrentDate(date: Date): void { + this.currentDate = date; + } + + /** + * Update current view + */ + public setCurrentView(view: CalendarView): void { + this.currentView = view; + } + + /** + * Get current date (for event filtering) + */ + public getCurrentDate(): Date { + return this.currentDate; + } +} diff --git a/src/index.ts b/src/index.ts index 71d0181..1992d83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,6 +70,9 @@ import { EventStackManager } from './managers/EventStackManager'; import { EventLayoutCoordinator } from './managers/EventLayoutCoordinator'; import { IColumnDataSource } from './types/ColumnDataSource'; import { DateColumnDataSource } from './datasources/DateColumnDataSource'; +import { ResourceColumnDataSource } from './datasources/ResourceColumnDataSource'; +import { ResourceHeaderRenderer } from './renderers/ResourceHeaderRenderer'; +import { ResourceColumnRenderer } from './renderers/ResourceColumnRenderer'; import { IBooking } from './types/BookingTypes'; import { ICustomer } from './types/CustomerTypes'; import { IResource } from './types/ResourceTypes'; @@ -137,13 +140,25 @@ async function initializeCalendar(): Promise { builder.registerType(MockResourceRepository).as>(); builder.registerType(MockAuditRepository).as>(); - builder.registerType(DateColumnDataSource).as(); + // Calendar mode: 'date' or 'resource' (default to resource) + const calendarMode: 'date' | 'resource' = 'resource'; + + // Register DataSource and HeaderRenderer based on mode + if (calendarMode === 'resource') { + builder.registerType(ResourceColumnDataSource).as(); + builder.registerType(ResourceHeaderRenderer).as(); + } else { + builder.registerType(DateColumnDataSource).as(); + builder.registerType(DateHeaderRenderer).as(); + } + // Register entity services (sync status management) // Open/Closed Principle: Adding new entity only requires adding one line here builder.registerType(EventService).as>(); builder.registerType(BookingService).as>(); builder.registerType(CustomerService).as>(); builder.registerType(ResourceService).as>(); + builder.registerType(ResourceService).as(); builder.registerType(AuditService).as(); // Register workers @@ -151,8 +166,12 @@ async function initializeCalendar(): Promise { builder.registerType(DataSeeder).as(); // Register renderers - builder.registerType(DateHeaderRenderer).as(); - builder.registerType(DateColumnRenderer).as(); + // Note: IHeaderRenderer and IColumnRenderer are registered above based on calendarMode + if (calendarMode === 'resource') { + builder.registerType(ResourceColumnRenderer).as(); + } else { + builder.registerType(DateColumnRenderer).as(); + } builder.registerType(DateEventRenderer).as(); // Register core services and utilities diff --git a/src/managers/GridManager.ts b/src/managers/GridManager.ts index 36cf352..042da5f 100644 --- a/src/managers/GridManager.ts +++ b/src/managers/GridManager.ts @@ -89,7 +89,10 @@ export class GridManager { } // Get columns from datasource - single source of truth - const columns = this.dataSource.getColumns(); + const columns = await this.dataSource.getColumns(); + + // Set grid columns CSS variable based on actual column count + document.documentElement.style.setProperty('--grid-columns', columns.length.toString()); // Extract dates for EventManager query const dates = columns.map(col => col.data as Date); diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index e12ef4f..41b0358 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -99,7 +99,7 @@ export class HeaderManager { /** * Update header content for navigation */ - private updateHeader(currentDate: Date): void { + private async updateHeader(currentDate: Date): Promise { console.log('🎯 HeaderManager.updateHeader called', { currentDate, rendererType: this.headerRenderer.constructor.name @@ -116,7 +116,7 @@ export class HeaderManager { // Update DataSource with current date and get columns this.dataSource.setCurrentDate(currentDate); - const columns = this.dataSource.getColumns(); + const columns = await this.dataSource.getColumns(); // Render new header content using injected renderer const context: IHeaderRenderContext = { diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 3aa8b8d..fbb64e6 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -173,7 +173,7 @@ export class NavigationManager { /** * Animation transition using pre-rendered containers when available */ - private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void { + private async animateTransition(direction: 'prev' | 'next', targetWeek: Date): Promise { const container = document.querySelector('swp-calendar-container') as HTMLElement; const currentGrid = document.querySelector('swp-calendar-container swp-grid-container:not([data-prerendered])') as HTMLElement; @@ -194,7 +194,7 @@ export class NavigationManager { // Update DataSource with target week and get columns this.dataSource.setCurrentDate(targetWeek); - const columns = this.dataSource.getColumns(); + const columns = await this.dataSource.getColumns(); // Always create a fresh container for consistent behavior newGrid = this.gridRenderer.createNavigationGrid(container, columns); diff --git a/src/renderers/ResourceColumnRenderer.ts b/src/renderers/ResourceColumnRenderer.ts new file mode 100644 index 0000000..93fe6b6 --- /dev/null +++ b/src/renderers/ResourceColumnRenderer.ts @@ -0,0 +1,46 @@ +import { WorkHoursManager } from '../managers/WorkHoursManager'; +import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer'; + +/** + * Resource-based column renderer + * + * In resource mode, columns represent resources (people, rooms, etc.) + * Work hours are hardcoded (09:00-18:00) for all columns. + * TODO: Each resource should have its own work hours. + */ +export class ResourceColumnRenderer implements IColumnRenderer { + private workHoursManager: WorkHoursManager; + + constructor(workHoursManager: WorkHoursManager) { + this.workHoursManager = workHoursManager; + } + + render(columnContainer: HTMLElement, context: IColumnRenderContext): void { + const { columns } = context; + + // Hardcoded work hours for all resources: 09:00 - 18:00 + const workHours = { start: 9, end: 18 }; + + columns.forEach((columnInfo) => { + const column = document.createElement('swp-day-column'); + + column.dataset.columnId = columnInfo.identifier; + + // Apply hardcoded work hours to all resource columns + this.applyWorkHoursToColumn(column, workHours); + + const eventsLayer = document.createElement('swp-events-layer'); + column.appendChild(eventsLayer); + + columnContainer.appendChild(column); + }); + } + + private applyWorkHoursToColumn(column: HTMLElement, workHours: { start: number; end: number }): void { + const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours); + if (nonWorkStyle) { + column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`); + column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`); + } + } +} diff --git a/src/renderers/ResourceHeaderRenderer.ts b/src/renderers/ResourceHeaderRenderer.ts new file mode 100644 index 0000000..296f74e --- /dev/null +++ b/src/renderers/ResourceHeaderRenderer.ts @@ -0,0 +1,58 @@ +import { IHeaderRenderer, IHeaderRenderContext } from './DateHeaderRenderer'; +import { IResource } from '../types/ResourceTypes'; + +/** + * ResourceHeaderRenderer - Renders resource-based headers + * + * Displays resource information (avatar, name) instead of dates. + * Used in resource mode where columns represent people/rooms/equipment. + */ +export class ResourceHeaderRenderer implements IHeaderRenderer { + render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void { + const { columns } = context; + + // Create all-day container (same structure as date mode) + const allDayContainer = document.createElement('swp-allday-container'); + calendarHeader.appendChild(allDayContainer); + + columns.forEach((columnInfo) => { + const resource = columnInfo.data as IResource; + const header = document.createElement('swp-day-header'); + + // Build header content + let avatarHtml = ''; + if (resource.avatarUrl) { + avatarHtml = `${resource.displayName}`; + } else { + // Fallback: initials + const initials = this.getInitials(resource.displayName); + const bgColor = resource.color || '#6366f1'; + avatarHtml = `${initials}`; + } + + header.innerHTML = ` +
+ ${avatarHtml} + ${resource.displayName} +
+ `; + + header.dataset.columnId = columnInfo.identifier; + header.dataset.resourceId = resource.id; + + calendarHeader.appendChild(header); + }); + } + + /** + * Get initials from display name + */ + private getInitials(name: string): string { + return name + .split(' ') + .map(part => part.charAt(0)) + .join('') + .toUpperCase() + .substring(0, 2); + } +} diff --git a/src/repositories/MockAuditRepository.ts b/src/repositories/MockAuditRepository.ts index 33448f7..753f4b4 100644 --- a/src/repositories/MockAuditRepository.ts +++ b/src/repositories/MockAuditRepository.ts @@ -11,7 +11,7 @@ import { EntityType } from '../types/CalendarTypes'; export class MockAuditRepository implements IApiRepository { readonly entityType: EntityType = 'Audit'; - async sendCreate(entity: IAuditEntry): Promise { + async sendCreate(entity: IAuditEntry): Promise { // Simulate API call delay await new Promise(resolve => setTimeout(resolve, 100)); @@ -22,9 +22,11 @@ export class MockAuditRepository implements IApiRepository { operation: entity.operation, timestamp: new Date(entity.timestamp).toISOString() }); + + return entity; } - async sendUpdate(_id: string, _entity: IAuditEntry): Promise { + async sendUpdate(_id: string, entity: IAuditEntry): Promise { // Audit entries are immutable - updates should not happen throw new Error('Audit entries cannot be updated'); } diff --git a/src/storage/IndexedDBContext.ts b/src/storage/IndexedDBContext.ts index be51585..da2d6fe 100644 --- a/src/storage/IndexedDBContext.ts +++ b/src/storage/IndexedDBContext.ts @@ -19,7 +19,7 @@ import { IStore } from './IStore'; */ export class IndexedDBContext { private static readonly DB_NAME = 'CalendarDB'; - private static readonly DB_VERSION = 3; // Bumped for audit store + private static readonly DB_VERSION = 5; // Bumped to add syncStatus index to resources static readonly QUEUE_STORE = 'operationQueue'; static readonly SYNC_STATE_STORE = 'syncState'; diff --git a/src/storage/resources/ResourceService.ts b/src/storage/resources/ResourceService.ts index e59cef9..8fd868e 100644 --- a/src/storage/resources/ResourceService.ts +++ b/src/storage/resources/ResourceService.ts @@ -31,68 +31,25 @@ export class ResourceService extends BaseEntityService { /** * Get resources by type - * - * @param type - Resource type (person, room, equipment, etc.) - * @returns Array of resources of this type */ async getByType(type: string): Promise { - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([this.storeName], 'readonly'); - const store = transaction.objectStore(this.storeName); - const index = store.index('type'); - const request = index.getAll(type); - - request.onsuccess = () => { - resolve(request.result as IResource[]); - }; - - request.onerror = () => { - reject(new Error(`Failed to get resources by type ${type}: ${request.error}`)); - }; - }); + const all = await this.getAll(); + return all.filter(r => r.type === type); } /** * Get active resources only - * - * @returns Array of active resources (isActive = true) */ async getActive(): Promise { - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([this.storeName], 'readonly'); - const store = transaction.objectStore(this.storeName); - const index = store.index('isActive'); - const request = index.getAll(IDBKeyRange.only(true)); - - request.onsuccess = () => { - resolve(request.result as IResource[]); - }; - - request.onerror = () => { - reject(new Error(`Failed to get active resources: ${request.error}`)); - }; - }); + const all = await this.getAll(); + return all.filter(r => r.isActive === true); } /** * Get inactive resources - * - * @returns Array of inactive resources (isActive = false) */ async getInactive(): Promise { - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([this.storeName], 'readonly'); - const store = transaction.objectStore(this.storeName); - const index = store.index('isActive'); - const request = index.getAll(IDBKeyRange.only(false)); - - request.onsuccess = () => { - resolve(request.result as IResource[]); - }; - - request.onerror = () => { - reject(new Error(`Failed to get inactive resources: ${request.error}`)); - }; - }); + const all = await this.getAll(); + return all.filter(r => r.isActive === false); } } diff --git a/src/storage/resources/ResourceStore.ts b/src/storage/resources/ResourceStore.ts index 1725777..05ed171 100644 --- a/src/storage/resources/ResourceStore.ts +++ b/src/storage/resources/ResourceStore.ts @@ -20,16 +20,7 @@ export class ResourceStore implements IStore { * @param db - IDBDatabase instance */ create(db: IDBDatabase): void { - // Create ObjectStore with 'id' as keyPath const store = db.createObjectStore(ResourceStore.STORE_NAME, { keyPath: 'id' }); - - // Index: type (for filtering by resource category) - store.createIndex('type', 'type', { unique: false }); - - // Index: isActive (for showing/hiding inactive resources) - store.createIndex('isActive', 'isActive', { unique: false }); - - // Index: syncStatus (for querying by sync status - used by SyncPlugin) store.createIndex('syncStatus', 'syncStatus', { unique: false }); } } diff --git a/src/types/ColumnDataSource.ts b/src/types/ColumnDataSource.ts index f933574..1b38a7d 100644 --- a/src/types/ColumnDataSource.ts +++ b/src/types/ColumnDataSource.ts @@ -21,7 +21,7 @@ export interface IColumnDataSource { * Get the list of columns to render * @returns Array of column information */ - getColumns(): IColumnInfo[]; + getColumns(): Promise; /** * Get the type of columns this datasource provides diff --git a/wwwroot/data/bookings.json b/wwwroot/data/bookings.json deleted file mode 100644 index a4c0eec..0000000 --- a/wwwroot/data/bookings.json +++ /dev/null @@ -1,306 +0,0 @@ -[ - { - "id": "BOOK001", - "customerId": "CUST001", - "status": "arrived", - "createdAt": "2025-08-05T08:00:00Z", - "services": [ - { - "serviceId": "SRV001", - "serviceName": "Klipning og styling", - "baseDuration": 60, - "basePrice": 500, - "customPrice": 500, - "resourceId": "EMP001" - } - ], - "totalPrice": 500, - "notes": "Kunde ønsker lidt kortere" - }, - { - "id": "BOOK002", - "customerId": "CUST002", - "status": "paid", - "createdAt": "2025-08-05T09:00:00Z", - "services": [ - { - "serviceId": "SRV002", - "serviceName": "Hårvask", - "baseDuration": 30, - "basePrice": 100, - "customPrice": 100, - "resourceId": "STUDENT001" - }, - { - "serviceId": "SRV003", - "serviceName": "Bundfarve", - "baseDuration": 90, - "basePrice": 800, - "customPrice": 800, - "resourceId": "EMP001" - } - ], - "totalPrice": 900, - "notes": "Split booking: Elev laver hårvask, master laver farve" - }, - { - "id": "BOOK003", - "customerId": "CUST003", - "status": "created", - "createdAt": "2025-08-05T07:00:00Z", - "services": [ - { - "serviceId": "SRV004A", - "serviceName": "Bryllupsfrisure - Del 1", - "baseDuration": 60, - "basePrice": 750, - "customPrice": 750, - "resourceId": "EMP001" - }, - { - "serviceId": "SRV004B", - "serviceName": "Bryllupsfrisure - Del 2", - "baseDuration": 60, - "basePrice": 750, - "customPrice": 750, - "resourceId": "EMP002" - } - ], - "totalPrice": 1500, - "notes": "Equal-split: To master stylister arbejder sammen" - }, - { - "id": "BOOK004", - "customerId": "CUST004", - "status": "arrived", - "createdAt": "2025-08-05T10:00:00Z", - "services": [ - { - "serviceId": "SRV005", - "serviceName": "Herreklipning", - "baseDuration": 30, - "basePrice": 350, - "customPrice": 350, - "resourceId": "EMP003" - } - ], - "totalPrice": 350 - }, - { - "id": "BOOK005", - "customerId": "CUST005", - "status": "paid", - "createdAt": "2025-08-05T11:00:00Z", - "services": [ - { - "serviceId": "SRV006", - "serviceName": "Balayage langt hår", - "baseDuration": 120, - "basePrice": 1200, - "customPrice": 1200, - "resourceId": "EMP002" - } - ], - "totalPrice": 1200, - "notes": "Kunde ønsker naturlig blond tone" - }, - { - "id": "BOOK006", - "customerId": "CUST006", - "status": "created", - "createdAt": "2025-08-06T08:00:00Z", - "services": [ - { - "serviceId": "SRV007", - "serviceName": "Permanent", - "baseDuration": 90, - "basePrice": 900, - "customPrice": 900, - "resourceId": "EMP004" - } - ], - "totalPrice": 900 - }, - { - "id": "BOOK007", - "customerId": "CUST007", - "status": "arrived", - "createdAt": "2025-08-06T09:00:00Z", - "services": [ - { - "serviceId": "SRV008", - "serviceName": "Highlights", - "baseDuration": 90, - "basePrice": 850, - "customPrice": 850, - "resourceId": "EMP001" - }, - { - "serviceId": "SRV009", - "serviceName": "Styling", - "baseDuration": 30, - "basePrice": 200, - "customPrice": 200, - "resourceId": "EMP001" - } - ], - "totalPrice": 1050, - "notes": "Highlights + styling samme stylist" - }, - { - "id": "BOOK008", - "customerId": "CUST008", - "status": "paid", - "createdAt": "2025-08-06T10:00:00Z", - "services": [ - { - "serviceId": "SRV010", - "serviceName": "Klipning", - "baseDuration": 45, - "basePrice": 450, - "customPrice": 450, - "resourceId": "EMP004" - } - ], - "totalPrice": 450 - }, - { - "id": "BOOK009", - "customerId": "CUST001", - "status": "created", - "createdAt": "2025-08-07T08:00:00Z", - "services": [ - { - "serviceId": "SRV011", - "serviceName": "Farve behandling", - "baseDuration": 120, - "basePrice": 950, - "customPrice": 950, - "resourceId": "EMP002" - } - ], - "totalPrice": 950 - }, - { - "id": "BOOK010", - "customerId": "CUST002", - "status": "arrived", - "createdAt": "2025-08-07T09:00:00Z", - "services": [ - { - "serviceId": "SRV012", - "serviceName": "Skæg trimning", - "baseDuration": 20, - "basePrice": 200, - "customPrice": 200, - "resourceId": "EMP003" - } - ], - "totalPrice": 200 - }, - { - "id": "BOOK011", - "customerId": "CUST003", - "status": "paid", - "createdAt": "2025-08-07T10:00:00Z", - "services": [ - { - "serviceId": "SRV002", - "serviceName": "Hårvask", - "baseDuration": 30, - "basePrice": 100, - "customPrice": 100, - "resourceId": "STUDENT002" - }, - { - "serviceId": "SRV013", - "serviceName": "Ombré", - "baseDuration": 100, - "basePrice": 1100, - "customPrice": 1100, - "resourceId": "EMP002" - } - ], - "totalPrice": 1200, - "notes": "Split booking: Student hårvask, master ombré" - }, - { - "id": "BOOK012", - "customerId": "CUST004", - "status": "created", - "createdAt": "2025-08-08T08:00:00Z", - "services": [ - { - "serviceId": "SRV014", - "serviceName": "Føntørring", - "baseDuration": 30, - "basePrice": 250, - "customPrice": 250, - "resourceId": "STUDENT001" - } - ], - "totalPrice": 250 - }, - { - "id": "BOOK013", - "customerId": "CUST005", - "status": "arrived", - "createdAt": "2025-08-08T09:00:00Z", - "services": [ - { - "serviceId": "SRV015", - "serviceName": "Opsætning", - "baseDuration": 60, - "basePrice": 700, - "customPrice": 700, - "resourceId": "EMP004" - } - ], - "totalPrice": 700, - "notes": "Fest opsætning" - }, - { - "id": "BOOK014", - "customerId": "CUST006", - "status": "created", - "createdAt": "2025-08-09T08:00:00Z", - "services": [ - { - "serviceId": "SRV016A", - "serviceName": "Ekstensions - Del 1", - "baseDuration": 90, - "basePrice": 1250, - "customPrice": 1250, - "resourceId": "EMP001" - }, - { - "serviceId": "SRV016B", - "serviceName": "Ekstensions - Del 2", - "baseDuration": 90, - "basePrice": 1250, - "customPrice": 1250, - "resourceId": "EMP004" - } - ], - "totalPrice": 2500, - "notes": "Equal-split: To stylister arbejder sammen om extensions" - }, - { - "id": "BOOK015", - "customerId": "CUST007", - "status": "noshow", - "createdAt": "2025-08-09T09:00:00Z", - "services": [ - { - "serviceId": "SRV001", - "serviceName": "Klipning og styling", - "baseDuration": 60, - "basePrice": 500, - "customPrice": 500, - "resourceId": "EMP002" - } - ], - "totalPrice": 500, - "notes": "Kunde mødte ikke op" - } -] diff --git a/wwwroot/data/customers.json b/wwwroot/data/customers.json deleted file mode 100644 index 28997bf..0000000 --- a/wwwroot/data/customers.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - { - "id": "CUST001", - "name": "Sofie Nielsen", - "phone": "+45 23 45 67 89", - "email": "sofie.nielsen@email.dk" - }, - { - "id": "CUST002", - "name": "Emma Andersen", - "phone": "+45 31 24 56 78", - "email": "emma.andersen@email.dk" - }, - { - "id": "CUST003", - "name": "Freja Christensen", - "phone": "+45 42 67 89 12", - "email": "freja.christensen@email.dk" - }, - { - "id": "CUST004", - "name": "Laura Pedersen", - "phone": "+45 51 98 76 54" - }, - { - "id": "CUST005", - "name": "Ida Larsen", - "phone": "+45 29 87 65 43", - "email": "ida.larsen@email.dk" - }, - { - "id": "CUST006", - "name": "Caroline Jensen", - "phone": "+45 38 76 54 32", - "email": "caroline.jensen@email.dk" - }, - { - "id": "CUST007", - "name": "Mathilde Hansen", - "phone": "+45 47 65 43 21", - "email": "mathilde.hansen@email.dk" - }, - { - "id": "CUST008", - "name": "Olivia Sørensen", - "phone": "+45 56 54 32 10", - "email": "olivia.sorensen@email.dk" - } -] diff --git a/wwwroot/data/events.json b/wwwroot/data/events.json deleted file mode 100644 index 498cbe5..0000000 --- a/wwwroot/data/events.json +++ /dev/null @@ -1,485 +0,0 @@ -[ - { - "id": "EVT001", - "title": "Sofie Nielsen - Klipning og styling", - "start": "2025-08-05T10:00:00Z", - "end": "2025-08-05T11:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK001", - "resourceId": "EMP001", - "customerId": "CUST001" - }, - { - "id": "EVT002", - "title": "Emma Andersen - Hårvask", - "start": "2025-08-05T11:00:00Z", - "end": "2025-08-05T11:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK002", - "resourceId": "STUDENT001", - "customerId": "CUST002" - }, - { - "id": "EVT003", - "title": "Emma Andersen - Bundfarve", - "start": "2025-08-05T11:30:00Z", - "end": "2025-08-05T13:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK002", - "resourceId": "EMP001", - "customerId": "CUST002" - }, - { - "id": "EVT004", - "title": "Freja Christensen - Bryllupsfrisure (Camilla)", - "start": "2025-08-05T08:00:00Z", - "end": "2025-08-05T10:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK003", - "resourceId": "EMP001", - "customerId": "CUST003", - "metadata": { - "note": "To stylister arbejder sammen" - } - }, - { - "id": "EVT005", - "title": "Freja Christensen - Bryllupsfrisure (Isabella)", - "start": "2025-08-05T08:00:00Z", - "end": "2025-08-05T10:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK003", - "resourceId": "EMP002", - "customerId": "CUST003", - "metadata": { - "note": "To stylister arbejder sammen" - } - }, - { - "id": "EVT006", - "title": "Laura Pedersen - Herreklipning", - "start": "2025-08-05T11:00:00Z", - "end": "2025-08-05T11:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK004", - "resourceId": "EMP003", - "customerId": "CUST004" - }, - { - "id": "EVT007", - "title": "Ida Larsen - Balayage langt hår", - "start": "2025-08-05T13:00:00Z", - "end": "2025-08-05T15:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK005", - "resourceId": "EMP002", - "customerId": "CUST005" - }, - { - "id": "EVT008", - "title": "Frokostpause", - "start": "2025-08-05T12:00:00Z", - "end": "2025-08-05T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP003" - }, - { - "id": "EVT009", - "title": "Caroline Jensen - Permanent", - "start": "2025-08-06T09:00:00Z", - "end": "2025-08-06T10:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK006", - "resourceId": "EMP004", - "customerId": "CUST006" - }, - { - "id": "EVT010", - "title": "Mathilde Hansen - Highlights", - "start": "2025-08-06T10:00:00Z", - "end": "2025-08-06T11:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK007", - "resourceId": "EMP001", - "customerId": "CUST007" - }, - { - "id": "EVT011", - "title": "Mathilde Hansen - Styling", - "start": "2025-08-06T11:30:00Z", - "end": "2025-08-06T12:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK007", - "resourceId": "EMP001", - "customerId": "CUST007" - }, - { - "id": "EVT012", - "title": "Olivia Sørensen - Klipning", - "start": "2025-08-06T13:00:00Z", - "end": "2025-08-06T13:45:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK008", - "resourceId": "EMP004", - "customerId": "CUST008" - }, - { - "id": "EVT013", - "title": "Team møde - Salgsmål", - "start": "2025-08-06T08:00:00Z", - "end": "2025-08-06T08:30:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001", - "metadata": { - "attendees": ["EMP001", "EMP002", "EMP003", "EMP004"] - } - }, - { - "id": "EVT014", - "title": "Frokostpause", - "start": "2025-08-06T12:00:00Z", - "end": "2025-08-06T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP002" - }, - { - "id": "EVT015", - "title": "Sofie Nielsen - Farve behandling", - "start": "2025-08-07T10:00:00Z", - "end": "2025-08-07T12:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK009", - "resourceId": "EMP002", - "customerId": "CUST001" - }, - { - "id": "EVT016", - "title": "Emma Andersen - Skæg trimning", - "start": "2025-08-07T09:00:00Z", - "end": "2025-08-07T09:20:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK010", - "resourceId": "EMP003", - "customerId": "CUST002" - }, - { - "id": "EVT017", - "title": "Freja Christensen - Hårvask", - "start": "2025-08-07T11:00:00Z", - "end": "2025-08-07T11:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK011", - "resourceId": "STUDENT002", - "customerId": "CUST003" - }, - { - "id": "EVT018", - "title": "Freja Christensen - Ombré", - "start": "2025-08-07T11:30:00Z", - "end": "2025-08-07T13:10:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK011", - "resourceId": "EMP002", - "customerId": "CUST003" - }, - { - "id": "EVT019", - "title": "Frokostpause", - "start": "2025-08-07T12:00:00Z", - "end": "2025-08-07T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001" - }, - { - "id": "EVT020", - "title": "Laura Pedersen - Føntørring", - "start": "2025-08-08T09:00:00Z", - "end": "2025-08-08T09:30:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK012", - "resourceId": "STUDENT001", - "customerId": "CUST004" - }, - { - "id": "EVT021", - "title": "Ida Larsen - Opsætning", - "start": "2025-08-08T10:00:00Z", - "end": "2025-08-08T11:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK013", - "resourceId": "EMP004", - "customerId": "CUST005" - }, - { - "id": "EVT022", - "title": "Produktleverance møde", - "start": "2025-08-08T08:00:00Z", - "end": "2025-08-08T08:30:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001", - "metadata": { - "attendees": ["EMP001", "EMP004"] - } - }, - { - "id": "EVT023", - "title": "Frokostpause", - "start": "2025-08-08T12:00:00Z", - "end": "2025-08-08T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP004" - }, - { - "id": "EVT024", - "title": "Caroline Jensen - Ekstensions (Camilla)", - "start": "2025-08-09T09:00:00Z", - "end": "2025-08-09T12:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK014", - "resourceId": "EMP001", - "customerId": "CUST006", - "metadata": { - "note": "To stylister arbejder sammen" - } - }, - { - "id": "EVT025", - "title": "Caroline Jensen - Ekstensions (Viktor)", - "start": "2025-08-09T09:00:00Z", - "end": "2025-08-09T12:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK014", - "resourceId": "EMP004", - "customerId": "CUST006", - "metadata": { - "note": "To stylister arbejder sammen" - } - }, - { - "id": "EVT026", - "title": "Mathilde Hansen - Klipning og styling", - "start": "2025-08-09T10:00:00Z", - "end": "2025-08-09T11:00:00Z", - "type": "customer", - "allDay": false, - "syncStatus": "synced", - "bookingId": "BOOK015", - "resourceId": "EMP002", - "customerId": "CUST007", - "metadata": { - "note": "NOSHOW - kunde mødte ikke op" - } - }, - { - "id": "EVT027", - "title": "Ferie - Spanien", - "start": "2025-08-10T00:00:00Z", - "end": "2025-08-17T23:59:59Z", - "type": "vacation", - "allDay": true, - "syncStatus": "synced", - "resourceId": "EMP003", - "metadata": { - "destination": "Mallorca" - } - }, - { - "id": "EVT028", - "title": "Frokostpause", - "start": "2025-08-09T12:00:00Z", - "end": "2025-08-09T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP002" - }, - { - "id": "EVT029", - "title": "Kaffepause", - "start": "2025-08-05T14:00:00Z", - "end": "2025-08-05T14:15:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP004" - }, - { - "id": "EVT030", - "title": "Kursus - Nye farvningsteknikker", - "start": "2025-08-11T09:00:00Z", - "end": "2025-08-11T16:00:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001", - "metadata": { - "location": "København", - "type": "external_course" - } - }, - { - "id": "EVT031", - "title": "Supervision - Elev", - "start": "2025-08-05T15:00:00Z", - "end": "2025-08-05T15:30:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001", - "metadata": { - "attendees": ["EMP001", "STUDENT001"] - } - }, - { - "id": "EVT032", - "title": "Aftensmad pause", - "start": "2025-08-06T17:00:00Z", - "end": "2025-08-06T17:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP001" - }, - { - "id": "EVT033", - "title": "Supervision - Elev", - "start": "2025-08-07T15:00:00Z", - "end": "2025-08-07T15:30:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP002", - "metadata": { - "attendees": ["EMP002", "STUDENT002"] - } - }, - { - "id": "EVT034", - "title": "Rengøring af arbejdsstation", - "start": "2025-08-08T16:00:00Z", - "end": "2025-08-08T16:30:00Z", - "type": "blocked", - "allDay": false, - "syncStatus": "synced", - "resourceId": "STUDENT001" - }, - { - "id": "EVT035", - "title": "Rengøring af arbejdsstation", - "start": "2025-08-08T16:00:00Z", - "end": "2025-08-08T16:30:00Z", - "type": "blocked", - "allDay": false, - "syncStatus": "synced", - "resourceId": "STUDENT002" - }, - { - "id": "EVT036", - "title": "Leverandør møde", - "start": "2025-08-09T14:00:00Z", - "end": "2025-08-09T15:00:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP004", - "metadata": { - "attendees": ["EMP004"] - } - }, - { - "id": "EVT037", - "title": "Sygedag", - "start": "2025-08-12T00:00:00Z", - "end": "2025-08-12T23:59:59Z", - "type": "vacation", - "allDay": true, - "syncStatus": "synced", - "resourceId": "STUDENT001", - "metadata": { - "reason": "sick_leave" - } - }, - { - "id": "EVT038", - "title": "Frokostpause", - "start": "2025-08-05T12:00:00Z", - "end": "2025-08-05T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "STUDENT001" - }, - { - "id": "EVT039", - "title": "Frokostpause", - "start": "2025-08-05T12:00:00Z", - "end": "2025-08-05T12:30:00Z", - "type": "break", - "allDay": false, - "syncStatus": "synced", - "resourceId": "STUDENT002" - }, - { - "id": "EVT040", - "title": "Morgen briefing", - "start": "2025-08-05T08:30:00Z", - "end": "2025-08-05T08:45:00Z", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "resourceId": "EMP004", - "metadata": { - "attendees": ["EMP001", "EMP002", "EMP003", "EMP004", "STUDENT001", "STUDENT002"] - } - } -] diff --git a/wwwroot/data/mock-bookings.json b/wwwroot/data/mock-bookings.json index a4c0eec..985bda0 100644 --- a/wwwroot/data/mock-bookings.json +++ b/wwwroot/data/mock-bookings.json @@ -302,5 +302,213 @@ ], "totalPrice": 500, "notes": "Kunde mødte ikke op" + }, + { + "id": "BOOK-NOV22-001", + "customerId": "CUST001", + "status": "arrived", + "createdAt": "2025-11-20T10:00:00Z", + "services": [ + { "serviceId": "SRV-WASH", "serviceName": "Hårvask", "baseDuration": 30, "basePrice": 100, "resourceId": "STUDENT001" }, + { "serviceId": "SRV-BAL", "serviceName": "Balayage", "baseDuration": 90, "basePrice": 1200, "resourceId": "EMP001" } + ], + "totalPrice": 1300, + "notes": "Split: Elev vasker, Camilla farver" + }, + { + "id": "BOOK-NOV22-002", + "customerId": "CUST002", + "status": "arrived", + "createdAt": "2025-11-20T11:00:00Z", + "services": [ + { "serviceId": "SRV-HERREKLIP", "serviceName": "Herreklipning", "baseDuration": 30, "basePrice": 350, "resourceId": "EMP003" } + ], + "totalPrice": 350 + }, + { + "id": "BOOK-NOV22-003", + "customerId": "CUST003", + "status": "created", + "createdAt": "2025-11-20T12:00:00Z", + "services": [ + { "serviceId": "SRV-FARVE", "serviceName": "Farvning", "baseDuration": 120, "basePrice": 900, "resourceId": "EMP002" } + ], + "totalPrice": 900 + }, + { + "id": "BOOK-NOV22-004", + "customerId": "CUST004", + "status": "arrived", + "createdAt": "2025-11-20T13:00:00Z", + "services": [ + { "serviceId": "SRV-KLIP", "serviceName": "Dameklipning", "baseDuration": 60, "basePrice": 450, "resourceId": "EMP004" } + ], + "totalPrice": 450 + }, + { + "id": "BOOK-NOV22-005", + "customerId": "CUST005", + "status": "created", + "createdAt": "2025-11-20T14:00:00Z", + "services": [ + { "serviceId": "SRV-STYLE", "serviceName": "Styling", "baseDuration": 60, "basePrice": 400, "resourceId": "EMP001" } + ], + "totalPrice": 400 + }, + { + "id": "BOOK-NOV23-001", + "customerId": "CUST006", + "status": "created", + "createdAt": "2025-11-21T09:00:00Z", + "services": [ + { "serviceId": "SRV-PERM", "serviceName": "Permanent", "baseDuration": 150, "basePrice": 1100, "resourceId": "EMP002" } + ], + "totalPrice": 1100 + }, + { + "id": "BOOK-NOV23-002", + "customerId": "CUST007", + "status": "created", + "createdAt": "2025-11-21T10:00:00Z", + "services": [ + { "serviceId": "SRV-SKAEG", "serviceName": "Skæg trimning", "baseDuration": 30, "basePrice": 200, "resourceId": "EMP003" } + ], + "totalPrice": 200 + }, + { + "id": "BOOK-NOV23-003", + "customerId": "CUST008", + "status": "created", + "createdAt": "2025-11-21T11:00:00Z", + "services": [ + { "serviceId": "SRV-WASH", "serviceName": "Hårvask", "baseDuration": 30, "basePrice": 100, "resourceId": "STUDENT002" }, + { "serviceId": "SRV-HIGH", "serviceName": "Highlights", "baseDuration": 120, "basePrice": 1000, "resourceId": "EMP001" } + ], + "totalPrice": 1100, + "notes": "Split: Elev vasker, Camilla laver highlights" + }, + { + "id": "BOOK-NOV24-001", + "customerId": "CUST001", + "status": "created", + "createdAt": "2025-11-22T08:00:00Z", + "services": [ + { "serviceId": "SRV-BRYLLUP1", "serviceName": "Bryllupsfrisure Del 1", "baseDuration": 60, "basePrice": 750, "resourceId": "EMP001" }, + { "serviceId": "SRV-BRYLLUP2", "serviceName": "Bryllupsfrisure Del 2", "baseDuration": 60, "basePrice": 750, "resourceId": "EMP002" } + ], + "totalPrice": 1500, + "notes": "Equal split: Camilla og Isabella arbejder sammen" + }, + { + "id": "BOOK-NOV24-002", + "customerId": "CUST002", + "status": "created", + "createdAt": "2025-11-22T09:00:00Z", + "services": [ + { "serviceId": "SRV-FADE", "serviceName": "Fade klipning", "baseDuration": 45, "basePrice": 400, "resourceId": "EMP003" } + ], + "totalPrice": 400 + }, + { + "id": "BOOK-NOV24-003", + "customerId": "CUST003", + "status": "created", + "createdAt": "2025-11-22T10:00:00Z", + "services": [ + { "serviceId": "SRV-KLIPVASK", "serviceName": "Klipning og vask", "baseDuration": 60, "basePrice": 500, "resourceId": "EMP004" } + ], + "totalPrice": 500 + }, + { + "id": "BOOK-NOV25-001", + "customerId": "CUST004", + "status": "created", + "createdAt": "2025-11-23T08:00:00Z", + "services": [ + { "serviceId": "SRV-BALKORT", "serviceName": "Balayage kort hår", "baseDuration": 90, "basePrice": 900, "resourceId": "EMP001" } + ], + "totalPrice": 900 + }, + { + "id": "BOOK-NOV25-002", + "customerId": "CUST005", + "status": "created", + "createdAt": "2025-11-23T09:00:00Z", + "services": [ + { "serviceId": "SRV-EXT", "serviceName": "Extensions", "baseDuration": 180, "basePrice": 2500, "resourceId": "EMP002" } + ], + "totalPrice": 2500 + }, + { + "id": "BOOK-NOV25-003", + "customerId": "CUST006", + "status": "created", + "createdAt": "2025-11-23T10:00:00Z", + "services": [ + { "serviceId": "SRV-HERRESKAEG", "serviceName": "Herreklipning + skæg", "baseDuration": 60, "basePrice": 500, "resourceId": "EMP003" } + ], + "totalPrice": 500 + }, + { + "id": "BOOK-NOV26-001", + "customerId": "CUST007", + "status": "created", + "createdAt": "2025-11-24T08:00:00Z", + "services": [ + { "serviceId": "SRV-FARVKOR", "serviceName": "Farvekorrektion", "baseDuration": 180, "basePrice": 1800, "resourceId": "EMP001" } + ], + "totalPrice": 1800 + }, + { + "id": "BOOK-NOV26-002", + "customerId": "CUST008", + "status": "created", + "createdAt": "2025-11-24T09:00:00Z", + "services": [ + { "serviceId": "SRV-KERATIN", "serviceName": "Keratinbehandling", "baseDuration": 150, "basePrice": 1400, "resourceId": "EMP002" } + ], + "totalPrice": 1400 + }, + { + "id": "BOOK-NOV26-003", + "customerId": "CUST001", + "status": "created", + "createdAt": "2025-11-24T10:00:00Z", + "services": [ + { "serviceId": "SRV-SKINFADE", "serviceName": "Skin fade", "baseDuration": 45, "basePrice": 450, "resourceId": "EMP003" } + ], + "totalPrice": 450 + }, + { + "id": "BOOK-NOV27-001", + "customerId": "CUST002", + "status": "created", + "createdAt": "2025-11-25T08:00:00Z", + "services": [ + { "serviceId": "SRV-FULLCOLOR", "serviceName": "Full color", "baseDuration": 120, "basePrice": 1000, "resourceId": "EMP001" } + ], + "totalPrice": 1000 + }, + { + "id": "BOOK-NOV27-002", + "customerId": "CUST003", + "status": "created", + "createdAt": "2025-11-25T09:00:00Z", + "services": [ + { "serviceId": "SRV-WASH", "serviceName": "Hårvask", "baseDuration": 30, "basePrice": 100, "resourceId": "STUDENT001" }, + { "serviceId": "SRV-BABY", "serviceName": "Babylights", "baseDuration": 180, "basePrice": 1500, "resourceId": "EMP002" } + ], + "totalPrice": 1600, + "notes": "Split: Elev vasker, Isabella laver babylights" + }, + { + "id": "BOOK-NOV27-003", + "customerId": "CUST004", + "status": "created", + "createdAt": "2025-11-25T10:00:00Z", + "services": [ + { "serviceId": "SRV-KLASSISK", "serviceName": "Klassisk herreklip", "baseDuration": 30, "basePrice": 300, "resourceId": "EMP003" } + ], + "totalPrice": 300 } ] diff --git a/wwwroot/data/mock-events.json b/wwwroot/data/mock-events.json index b20ceab..de34e27 100644 --- a/wwwroot/data/mock-events.json +++ b/wwwroot/data/mock-events.json @@ -3965,5 +3965,354 @@ "duration": 1440, "color": "#795548" } + }, + { + "id": "RES-NOV22-001", + "title": "Balayage", + "start": "2025-11-22T09:00:00Z", + "end": "2025-11-22T11:00:00Z", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-NOV22-001", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#9c27b0" } + }, + { + "id": "RES-NOV22-002", + "title": "Herreklipning", + "start": "2025-11-22T09:30:00Z", + "end": "2025-11-22T10:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#3f51b5" } + }, + { + "id": "RES-NOV22-003", + "title": "Farvning", + "start": "2025-11-22T10:00:00Z", + "end": "2025-11-22T12:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#e91e63" } + }, + { + "id": "RES-NOV22-004", + "title": "Styling", + "start": "2025-11-22T13:00:00Z", + "end": "2025-11-22T14:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP001", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#9c27b0" } + }, + { + "id": "RES-NOV22-005", + "title": "Vask og føn", + "start": "2025-11-22T11:00:00Z", + "end": "2025-11-22T11:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT001", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#8bc34a" } + }, + { + "id": "RES-NOV22-006", + "title": "Klipning dame", + "start": "2025-11-22T14:00:00Z", + "end": "2025-11-22T15:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#009688" } + }, + { + "id": "RES-NOV23-001", + "title": "Permanent", + "start": "2025-11-23T09:00:00Z", + "end": "2025-11-23T11:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 150, "color": "#e91e63" } + }, + { + "id": "RES-NOV23-002", + "title": "Skæg trimning", + "start": "2025-11-23T10:00:00Z", + "end": "2025-11-23T10:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#3f51b5" } + }, + { + "id": "RES-NOV23-003", + "title": "Highlights", + "start": "2025-11-23T12:00:00Z", + "end": "2025-11-23T14:00:00Z", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-NOV22-001", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#9c27b0" } + }, + { + "id": "RES-NOV23-004", + "title": "Assistance", + "start": "2025-11-23T13:00:00Z", + "end": "2025-11-23T14:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT002", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#ff9800" } + }, + { + "id": "RES-NOV24-001", + "title": "Bryllupsfrisure", + "start": "2025-11-24T08:00:00Z", + "end": "2025-11-24T10:00:00Z", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-NOV22-001", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#9c27b0" } + }, + { + "id": "RES-NOV24-002", + "title": "Ombre", + "start": "2025-11-24T10:00:00Z", + "end": "2025-11-24T12:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 150, "color": "#e91e63" } + }, + { + "id": "RES-NOV24-003", + "title": "Fade klipning", + "start": "2025-11-24T11:00:00Z", + "end": "2025-11-24T11:45:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 45, "color": "#3f51b5" } + }, + { + "id": "RES-NOV24-004", + "title": "Klipning og vask", + "start": "2025-11-24T14:00:00Z", + "end": "2025-11-24T15:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#009688" } + }, + { + "id": "RES-NOV24-005", + "title": "Grundklipning elev", + "start": "2025-11-24T13:00:00Z", + "end": "2025-11-24T14:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT001", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#8bc34a" } + }, + { + "id": "RES-NOV25-001", + "title": "Balayage kort hår", + "start": "2025-11-25T09:00:00Z", + "end": "2025-11-25T10:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP001", + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#9c27b0" } + }, + { + "id": "RES-NOV25-002", + "title": "Extensions", + "start": "2025-11-25T11:00:00Z", + "end": "2025-11-25T14:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 180, "color": "#e91e63" } + }, + { + "id": "RES-NOV25-003", + "title": "Herreklipning + skæg", + "start": "2025-11-25T09:00:00Z", + "end": "2025-11-25T10:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#3f51b5" } + }, + { + "id": "RES-NOV25-004", + "title": "Styling special", + "start": "2025-11-25T15:00:00Z", + "end": "2025-11-25T16:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#009688" } + }, + { + "id": "RES-NOV25-005", + "title": "Praktik vask", + "start": "2025-11-25T10:00:00Z", + "end": "2025-11-25T10:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT002", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff9800" } + }, + { + "id": "RES-NOV26-001", + "title": "Farvekorrektion", + "start": "2025-11-26T09:00:00Z", + "end": "2025-11-26T12:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP001", + "syncStatus": "synced", + "metadata": { "duration": 180, "color": "#9c27b0" } + }, + { + "id": "RES-NOV26-002", + "title": "Keratinbehandling", + "start": "2025-11-26T10:00:00Z", + "end": "2025-11-26T12:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 150, "color": "#e91e63" } + }, + { + "id": "RES-NOV26-003", + "title": "Skin fade", + "start": "2025-11-26T13:00:00Z", + "end": "2025-11-26T13:45:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 45, "color": "#3f51b5" } + }, + { + "id": "RES-NOV26-004", + "title": "Dameklipning lang", + "start": "2025-11-26T14:00:00Z", + "end": "2025-11-26T15:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#009688" } + }, + { + "id": "RES-NOV26-005", + "title": "Føntørring træning", + "start": "2025-11-26T11:00:00Z", + "end": "2025-11-26T12:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT001", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#8bc34a" } + }, + { + "id": "RES-NOV27-001", + "title": "Full color", + "start": "2025-11-27T09:00:00Z", + "end": "2025-11-27T11:00:00Z", + "type": "customer", + "allDay": false, + "bookingId": "BOOK-NOV22-001", + "resourceId": "EMP001", + "customerId": "CUST001", + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#9c27b0" } + }, + { + "id": "RES-NOV27-002", + "title": "Babylights", + "start": "2025-11-27T12:00:00Z", + "end": "2025-11-27T15:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP002", + "syncStatus": "synced", + "metadata": { "duration": 180, "color": "#e91e63" } + }, + { + "id": "RES-NOV27-003", + "title": "Klassisk herreklip", + "start": "2025-11-27T10:00:00Z", + "end": "2025-11-27T10:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP003", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#3f51b5" } + }, + { + "id": "RES-NOV27-004", + "title": "Klipning + styling", + "start": "2025-11-27T11:00:00Z", + "end": "2025-11-27T12:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "EMP004", + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#009688" } + }, + { + "id": "RES-NOV27-005", + "title": "Vask assistance", + "start": "2025-11-27T14:00:00Z", + "end": "2025-11-27T14:30:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT001", + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#8bc34a" } + }, + { + "id": "RES-NOV27-006", + "title": "Observation", + "start": "2025-11-27T15:00:00Z", + "end": "2025-11-27T16:00:00Z", + "type": "customer", + "allDay": false, + "resourceId": "STUDENT002", + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#ff9800" } } ] \ No newline at end of file diff --git a/wwwroot/data/resources.json b/wwwroot/data/resources.json deleted file mode 100644 index 3600c0a..0000000 --- a/wwwroot/data/resources.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "id": "EMP001", - "name": "camilla.jensen", - "displayName": "Camilla Jensen", - "type": "person", - "avatarUrl": "/avatars/camilla.jpg", - "color": "#9c27b0", - "isActive": true, - "metadata": { - "role": "master stylist", - "specialties": ["balayage", "color", "bridal"] - } - }, - { - "id": "EMP002", - "name": "isabella.hansen", - "displayName": "Isabella Hansen", - "type": "person", - "avatarUrl": "/avatars/isabella.jpg", - "color": "#e91e63", - "isActive": true, - "metadata": { - "role": "master stylist", - "specialties": ["highlights", "ombre", "styling"] - } - }, - { - "id": "EMP003", - "name": "alexander.nielsen", - "displayName": "Alexander Nielsen", - "type": "person", - "avatarUrl": "/avatars/alexander.jpg", - "color": "#3f51b5", - "isActive": true, - "metadata": { - "role": "master stylist", - "specialties": ["men's cuts", "beard", "fade"] - } - }, - { - "id": "EMP004", - "name": "viktor.andersen", - "displayName": "Viktor Andersen", - "type": "person", - "avatarUrl": "/avatars/viktor.jpg", - "color": "#009688", - "isActive": true, - "metadata": { - "role": "stylist", - "specialties": ["cuts", "styling", "perms"] - } - }, - { - "id": "STUDENT001", - "name": "line.pedersen", - "displayName": "Line Pedersen (Elev)", - "type": "person", - "avatarUrl": "/avatars/line.jpg", - "color": "#8bc34a", - "isActive": true, - "metadata": { - "role": "student", - "specialties": ["wash", "blow-dry", "basic cuts"] - } - }, - { - "id": "STUDENT002", - "name": "mads.larsen", - "displayName": "Mads Larsen (Elev)", - "type": "person", - "avatarUrl": "/avatars/mads.jpg", - "color": "#ff9800", - "isActive": true, - "metadata": { - "role": "student", - "specialties": ["wash", "styling assistance"] - } - } -]