From 51f3b92d698bca64a26aec470f403dcbbdc2ab48 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Mon, 2 Feb 2026 22:42:18 +0100 Subject: [PATCH] Enhances application menu and adds calendar feature Adds new calendar page and module with comprehensive initialization Introduces quick actions group in side menu with create booking and sale options Updates menu service to include new menu groups and rearrange sort order Configures custom npm registry and esbuild configuration Adds localization for new menu items and calendar section Implements calendar controller with dependency injection and settings seeding --- .../.claude/settings.local.json | 6 +- PlanTempus.Application/.npmrc | 2 + .../Features/Calendar/Pages/Index.cshtml | 37 +++++ .../Features/Calendar/Pages/Index.cshtml.cs | 10 ++ .../Localization/Translations/da.json | 7 + .../Features/Menu/Services/MockMenuService.cs | 39 ++++- .../Shared/Components/SideMenu/Default.cshtml | 4 +- .../Components/SideMenu/Default.cshtml | 2 +- PlanTempus.Application/esbuild.config.js | 14 ++ PlanTempus.Application/package-lock.json | 151 ++++++++++++++++++ PlanTempus.Application/package.json | 4 +- .../wwwroot/css/sidebar.css | 34 ++++ PlanTempus.Application/wwwroot/ts/app.ts | 3 + .../wwwroot/ts/modules/calendar.ts | 119 ++++++++++++++ 14 files changed, 422 insertions(+), 10 deletions(-) create mode 100644 PlanTempus.Application/.npmrc create mode 100644 PlanTempus.Application/Features/Calendar/Pages/Index.cshtml create mode 100644 PlanTempus.Application/Features/Calendar/Pages/Index.cshtml.cs create mode 100644 PlanTempus.Application/esbuild.config.js create mode 100644 PlanTempus.Application/wwwroot/ts/modules/calendar.ts 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); + } +}