diff --git a/PlanTempus.Application/.claude/settings.local.json b/PlanTempus.Application/.claude/settings.local.json index ac7fc6c..addddb1 100644 --- a/PlanTempus.Application/.claude/settings.local.json +++ b/PlanTempus.Application/.claude/settings.local.json @@ -7,7 +7,11 @@ "Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\")", "Bash(findstr:*)", "Bash(dir \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\wwwroot\\\\*.html\")", - "Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cs\" \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cshtml\")" + "Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cs\" \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cshtml\")", + "Bash(npm view:*)", + "Bash(tar:*)", + "Bash(find:*)", + "Bash(npm install:*)" ] } } diff --git a/PlanTempus.Application/.npmrc b/PlanTempus.Application/.npmrc new file mode 100644 index 0000000..863523f --- /dev/null +++ b/PlanTempus.Application/.npmrc @@ -0,0 +1,2 @@ +@novadi:registry=http://npm.jarjarbinks:4873 +registry=http://npm.jarjarbinks:4873 diff --git a/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml b/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml new file mode 100644 index 0000000..bf12ed6 --- /dev/null +++ b/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml @@ -0,0 +1,37 @@ +@page "/kalender" +@using PlanTempus.Application.Features.Calendar.Pages +@model PlanTempus.Application.Features.Calendar.Pages.IndexModel +@{ + ViewData["Title"] = "Kalender"; +} + +@section Styles { + +} + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml.cs b/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml.cs new file mode 100644 index 0000000..c33228d --- /dev/null +++ b/PlanTempus.Application/Features/Calendar/Pages/Index.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PlanTempus.Application.Features.Calendar.Pages; + +public class IndexModel : PageModel +{ + public void OnGet() + { + } +} diff --git a/PlanTempus.Application/Features/Localization/Translations/da.json b/PlanTempus.Application/Features/Localization/Translations/da.json index 2ab8fc1..1683b3a 100644 --- a/PlanTempus.Application/Features/Localization/Translations/da.json +++ b/PlanTempus.Application/Features/Localization/Translations/da.json @@ -3,6 +3,8 @@ "home": "Dashboard", "calendar": "Kalender", "pos": "Kasse", + "createBooking": "Opret booking", + "createSale": "Opret salg", "products": "Produkter & Lager", "suppliers": "Leverandører", "customers": "Kunder", @@ -16,6 +18,7 @@ }, "groups": { "overview": "Overblik", + "quickActions": "Hurtigvalg", "data": "Data", "online": "Online", "analytics": "Analyse", @@ -762,5 +765,9 @@ "modules": "Moduler", "tracking": "Tracking" } + }, + "calendar": { + "title": "Kalender", + "subtitle": "Se og administrer bookinger" } } diff --git a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs index 1ed7046..e6b2f1d 100644 --- a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs +++ b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs @@ -71,7 +71,7 @@ public class MockMenuService : IMenuService Id = "calendar", Label = "Kalender", Icon = "ph-calendar", - Url = "/poc-calendar.html", + Url = "/kalender", MinimumRole = UserRole.Staff, SortOrder = 2 }, @@ -87,12 +87,41 @@ public class MockMenuService : IMenuService } }, + // QUICK ACTIONS GROUP + new MenuGroup + { + Id = "quickActions", + Label = "Hurtigvalg", + SortOrder = 2, + Items = new List + { + new MenuItem + { + Id = "createBooking", + Label = "Opret booking", + Icon = "ph-calendar-plus", + Url = "/opret-booking", + MinimumRole = UserRole.Staff, + SortOrder = 1 + }, + new MenuItem + { + Id = "createSale", + Label = "Opret salg", + Icon = "ph-shopping-cart", + Url = "/opret-salg", + MinimumRole = UserRole.Staff, + SortOrder = 2 + } + } + }, + // DATA GROUP new MenuGroup { Id = "data", Label = "Data", - SortOrder = 2, + SortOrder = 3, Items = new List { new MenuItem @@ -148,7 +177,7 @@ public class MockMenuService : IMenuService { Id = "online", Label = "Online", - SortOrder = 3, + SortOrder = 4, Items = new List { new MenuItem @@ -177,7 +206,7 @@ public class MockMenuService : IMenuService { Id = "analytics", Label = "Analyse", - SortOrder = 4, + SortOrder = 5, Items = new List { new MenuItem @@ -197,7 +226,7 @@ public class MockMenuService : IMenuService { Id = "system", Label = "System", - SortOrder = 5, + SortOrder = 6, Items = new List { new MenuItem diff --git a/PlanTempus.Application/Features/Shared/Components/SideMenu/Default.cshtml b/PlanTempus.Application/Features/Shared/Components/SideMenu/Default.cshtml index 2514a4b..cfc2f53 100644 --- a/PlanTempus.Application/Features/Shared/Components/SideMenu/Default.cshtml +++ b/PlanTempus.Application/Features/Shared/Components/SideMenu/Default.cshtml @@ -1,4 +1,4 @@ -@using PlanTempus.Application.Features.Menu +@using PlanTempus.Application.Features.Menu @model SideMenuViewModel @@ -13,7 +13,7 @@ @foreach (var group in Model.Groups) { - + @group.Label @foreach (var item in group.Items) { diff --git a/PlanTempus.Application/Features/_Shared/Components/SideMenu/Default.cshtml b/PlanTempus.Application/Features/_Shared/Components/SideMenu/Default.cshtml index ae7de86..29af4b1 100644 --- a/PlanTempus.Application/Features/_Shared/Components/SideMenu/Default.cshtml +++ b/PlanTempus.Application/Features/_Shared/Components/SideMenu/Default.cshtml @@ -12,7 +12,7 @@ @foreach (var group in Model.Groups) { - + @group.Label @foreach (var item in group.Items) { diff --git a/PlanTempus.Application/esbuild.config.js b/PlanTempus.Application/esbuild.config.js new file mode 100644 index 0000000..cb77167 --- /dev/null +++ b/PlanTempus.Application/esbuild.config.js @@ -0,0 +1,14 @@ +import * as esbuild from 'esbuild'; +import { NovadiUnplugin } from '@novadi/core/unplugin'; + +await esbuild.build({ + entryPoints: ['wwwroot/ts/app.ts'], + bundle: true, + format: 'esm', + outfile: 'wwwroot/js/app.js', + sourcemap: 'inline', + target: 'es2020', + keepNames: true, + platform: 'browser', + plugins: [NovadiUnplugin.esbuild()] +}); diff --git a/PlanTempus.Application/package-lock.json b/PlanTempus.Application/package-lock.json index 603c53b..73a67c3 100644 --- a/PlanTempus.Application/package-lock.json +++ b/PlanTempus.Application/package-lock.json @@ -5,7 +5,9 @@ "packages": { "": { "dependencies": { + "@novadi/core": "^0.6.0", "@sevenweirdpeople/swp-charting": "^0.2.5", + "calendar": "^0.1.7", "fuse.js": "^7.1.0" }, "devDependencies": { @@ -473,6 +475,66 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "http://npm.jarjarbinks:4873/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "http://npm.jarjarbinks:4873/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "http://npm.jarjarbinks:4873/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "http://npm.jarjarbinks:4873/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "http://npm.jarjarbinks:4873/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@novadi/core": { + "version": "0.6.0", + "resolved": "http://npm.jarjarbinks:4873/@novadi/core/-/core-0.6.0.tgz", + "integrity": "sha512-CU1134Nd7ULMg9OQbID5oP+FLtrMkNiLJ17+dmy4jjmPDcPK/dVzKTFxvJmbBvEfZEc9WtmkmJjqw11ABU7Jxw==", + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.10" + }, + "optionalDependencies": { + "@rollup/rollup-win32-x64-msvc": "^4.52.5" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -484,12 +546,37 @@ "node": ">=14" } }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.0", + "resolved": "http://npm.jarjarbinks:4873/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sevenweirdpeople/swp-charting": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.2.5.tgz", "integrity": "sha512-bQa5FtAXsTjjFxsE79sD1+A74R7f9YgVp5fC1fsiHoaLXmapDEO2dWuGX/MQ8rEChDZFyN1ZlkV+OLUs6qtfZw==", "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "http://npm.jarjarbinks:4873/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -533,6 +620,17 @@ "balanced-match": "^1.0.0" } }, + "node_modules/calendar": { + "version": "0.1.7", + "resolved": "http://npm.jarjarbinks:4873/calendar/-/calendar-0.1.7.tgz", + "integrity": "sha512-9KLvHRMezHFwdpkpNSoOO0yi58D+tcXefo+2uSQznDK3hCKnUng1yBb6XG2XtUJD+SSyi5Y1/zOWRo9DxnxabA==", + "dependencies": { + "dayjs": "^1.11.0" + }, + "peerDependencies": { + "@novadi/core": "^0.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -591,6 +689,12 @@ "node": ">=4" } }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "http://npm.jarjarbinks:4873/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -820,6 +924,18 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "http://npm.jarjarbinks:4873/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -1029,6 +1145,35 @@ "node": ">=8" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "http://npm.jarjarbinks:4873/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "http://npm.jarjarbinks:4873/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1036,6 +1181,12 @@ "dev": true, "license": "MIT" }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "http://npm.jarjarbinks:4873/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/PlanTempus.Application/package.json b/PlanTempus.Application/package.json index 97b5fd0..1a827b0 100644 --- a/PlanTempus.Application/package.json +++ b/PlanTempus.Application/package.json @@ -5,11 +5,13 @@ "purgecss": "^6.0.0" }, "scripts": { - "build": "esbuild wwwroot/ts/app.ts --bundle --format=esm --outfile=wwwroot/js/app.js --sourcemap=inline --target=es2020 --keep-names --platform=browser", + "build": "node esbuild.config.js", "analyze-css": "node analyze-css.js" }, "dependencies": { + "@novadi/core": "^0.6.0", "@sevenweirdpeople/swp-charting": "^0.2.5", + "calendar": "^0.1.7", "fuse.js": "^7.1.0" } } diff --git a/PlanTempus.Application/wwwroot/css/sidebar.css b/PlanTempus.Application/wwwroot/css/sidebar.css index 58c9cfe..982b7f8 100644 --- a/PlanTempus.Application/wwwroot/css/sidebar.css +++ b/PlanTempus.Application/wwwroot/css/sidebar.css @@ -82,6 +82,7 @@ swp-side-menu-group { margin-bottom: var(--spacing-2); } + swp-side-menu-label { display: block; padding: var(--spacing-2) var(--spacing-4) 6px; @@ -125,6 +126,28 @@ a[is="swp-side-menu-item"] { } } +/* =========================================== + QUICK ACTIONS GROUP - Highlighted + =========================================== */ +swp-side-menu-group[data-group="quickActions"] { + background: rgba(255, 235, 59, 0.15); + margin: var(--spacing-2) var(--spacing-2) var(--spacing-3); + padding: var(--spacing-2) 0; + border-radius: var(--border-radius-lg); + border: 1px solid rgba(255, 193, 7, 0.5); +} + +swp-side-menu-group[data-group="quickActions"] swp-side-menu-label { + padding-left: var(--spacing-3); + padding-right: var(--spacing-3); +} + +swp-side-menu-group[data-group="quickActions"] a[is="swp-side-menu-item"] { + padding-left: var(--spacing-3); + padding-right: var(--spacing-3); + border-left: none; +} + /* =========================================== FOOTER =========================================== */ @@ -223,6 +246,17 @@ swp-app-layout.menu-collapsed swp-side-menu-footer { padding: var(--spacing-3) var(--spacing-2); } +/* Quick Actions - Collapsed State */ +swp-app-layout.menu-collapsed swp-side-menu-group[data-group="quickActions"] { + margin: var(--spacing-2) var(--spacing-1); + padding: var(--spacing-2) 0; +} + +swp-app-layout.menu-collapsed swp-side-menu-group[data-group="quickActions"] a[is="swp-side-menu-item"] { + padding: var(--spacing-3); + justify-content: center; +} + /* =========================================== TOOLTIP (Collapsed State) =========================================== */ diff --git a/PlanTempus.Application/wwwroot/ts/app.ts b/PlanTempus.Application/wwwroot/ts/app.ts index 47dbed7..f54d6e9 100644 --- a/PlanTempus.Application/wwwroot/ts/app.ts +++ b/PlanTempus.Application/wwwroot/ts/app.ts @@ -17,6 +17,7 @@ import { CustomersController } from './modules/customers'; import { SuppliersController } from './modules/suppliers'; import { TrackingController } from './modules/tracking'; import { ReportsController } from './modules/reports'; +import { CalendarController } from './modules/calendar'; /** * Main application class @@ -35,6 +36,7 @@ export class App { readonly suppliers: SuppliersController; readonly tracking: TrackingController; readonly reports: ReportsController; + readonly calendar: CalendarController; constructor() { // Initialize controllers @@ -51,6 +53,7 @@ export class App { this.suppliers = new SuppliersController(); this.tracking = new TrackingController(); this.reports = new ReportsController(); + this.calendar = new CalendarController(); } } diff --git a/PlanTempus.Application/wwwroot/ts/modules/calendar.ts b/PlanTempus.Application/wwwroot/ts/modules/calendar.ts new file mode 100644 index 0000000..6ffda38 --- /dev/null +++ b/PlanTempus.Application/wwwroot/ts/modules/calendar.ts @@ -0,0 +1,119 @@ +/** + * Calendar Controller + * + * Integrates the calendar package into PlanTempus. + * Uses NovaDI for dependency injection and IndexedDB for offline storage. + */ + +import { Container } from '@novadi/core'; +import { + registerCoreServices, + CalendarApp, + IndexedDBContext, + SettingsService, + ViewConfigService, + EventBus, + CalendarEvents +} from 'calendar'; +import { registerSchedules } from 'calendar/schedules'; + +export class CalendarController { + private app: Container | null = null; + private calendarApp: CalendarApp | null = null; + + constructor() { + const containerEl = document.querySelector('swp-calendar-container'); + if (!containerEl) return; + this.init(containerEl as HTMLElement).catch(err => { + console.error('[CalendarController] Init failed:', err); + }); + } + + private async init(containerEl: HTMLElement): Promise { + console.log('[CalendarController] Starting init...'); + + // Create DI container using the pattern from README + const container = new Container(); + const builder = container.builder(); + registerCoreServices(builder, { + dbConfig: { dbName: 'PlanTempusCalendarDB', dbVersion: 1 } + }); + registerSchedules(builder); + this.app = builder.build(); + console.log('[CalendarController] Container built'); + + // Initialize IndexedDB + const dbContext = this.app.resolveType(); + await dbContext.initialize(); + console.log('[CalendarController] IndexedDB initialized'); + + // Seed required settings + await this.seedSettings(); + console.log('[CalendarController] Settings seeded'); + + // Initialize CalendarApp + this.calendarApp = this.app.resolveType(); + await this.calendarApp.init(containerEl); + console.log('[CalendarController] CalendarApp initialized'); + + // Render default view + const eventBus = this.app.resolveType(); + eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' }); + console.log('[CalendarController] Render command emitted'); + } + + private async seedSettings(): Promise { + if (!this.app) return; + + const settingsService = this.app.resolveType(); + const viewConfigService = this.app.resolveType(); + + // Grid settings + await settingsService.save({ + id: 'grid', + dayStartHour: 8, + dayEndHour: 18, + workStartHour: 9, + workEndHour: 17, + hourHeight: 64, + snapInterval: 15, + syncStatus: 'synced' + }); + + // Workweek settings + await settingsService.save({ + id: 'workweek', + presets: { + standard: { id: 'standard', label: 'Standard', workDays: [1, 2, 3, 4, 5], periodDays: 7 } + }, + defaultPreset: 'standard', + firstDayOfWeek: 1, + syncStatus: 'synced' + }); + + // View configuration + await viewConfigService.save({ + id: 'simple', + groupings: [{ type: 'date', values: [], idProperty: 'date', derivedFrom: 'start' }], + syncStatus: 'synced' + }); + } + + navigatePrev(): void { + if (!this.app) return; + const eventBus = this.app.resolveType(); + eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV); + } + + navigateNext(): void { + if (!this.app) return; + const eventBus = this.app.resolveType(); + eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT); + } + + toggleDrawer(): void { + if (!this.app) return; + const eventBus = this.app.resolveType(); + eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE); + } +}