diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..e41ef5c --- /dev/null +++ b/.hintrc @@ -0,0 +1,15 @@ +{ + "extends": [ + "development" + ], + "hints": { + "compat-api/css": [ + "default", + { + "ignore": [ + "grid-template-columns: subgrid" + ] + } + ] + } +} \ No newline at end of file diff --git a/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/AttentionItemViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/AttentionItemViewComponent.cs new file mode 100644 index 0000000..b9f323f --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/AttentionItemViewComponent.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +public class AttentionItemViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = AttentionItemCatalog.Get(key); + return View(model); + } +} + +public class AttentionItemViewModel +{ + public required string Key { get; init; } + public required string Icon { get; init; } + public required string Title { get; init; } + public required string Description { get; init; } + public required string ActionText { get; init; } + public required string Severity { get; init; } +} + +public static class AttentionItemCatalog +{ + private static readonly Dictionary Attentions = new() + { + ["attention-1"] = new AttentionItemViewModel + { + Key = "attention-1", + Icon = "x-circle", + Title = "Aflyst booking", + Description = "Mette Hansen aflyste kl. 15:00 – tid nu ledig", + ActionText = "Fyld tid", + Severity = "urgent" + }, + ["attention-2"] = new AttentionItemViewModel + { + Key = "attention-2", + Icon = "clock", + Title = "Ubekræftet booking", + Description = "Ida Rasmussen har ikke bekræftet kl. 11:30", + ActionText = "Send påmindelse", + Severity = "warning" + }, + ["attention-3"] = new AttentionItemViewModel + { + Key = "attention-3", + Icon = "gift", + Title = "Gavekort udløber snart", + Description = "GC-D2R4-6TY9 udløber om 3 uger (200 DKK)", + ActionText = "Se gavekort", + Severity = "info" + } + }; + + public static AttentionItemViewModel Get(string key) + { + if (Attentions.TryGetValue(key, out var attention)) + return attention; + + throw new KeyNotFoundException($"AttentionItem with key '{key}' not found"); + } + + public static IEnumerable AllKeys => Attentions.Keys; +} diff --git a/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/Default.cshtml new file mode 100644 index 0000000..2ce1fcf --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/AttentionItem/Default.cshtml @@ -0,0 +1,12 @@ +@model PlanTempus.Application.Features.Dashboard.Components.AttentionItemViewModel + + + + + + + @Model.Title + @Model.Description + + @Model.ActionText + diff --git a/PlanTempus.Application/Features/Dashboard/Components/AttentionList/AttentionListViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/AttentionList/AttentionListViewComponent.cs new file mode 100644 index 0000000..2892d22 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/AttentionList/AttentionListViewComponent.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +public class AttentionListViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = AttentionListCatalog.Get(key); + return View(model); + } +} + +public class AttentionListViewModel +{ + public required string Key { get; init; } + public required string Title { get; init; } + public required IReadOnlyList AttentionKeys { get; init; } +} + +public static class AttentionListCatalog +{ + private static readonly Dictionary Lists = new() + { + ["current-attentions"] = new AttentionListViewModel + { + Key = "current-attentions", + Title = "Opmærksomheder", + AttentionKeys = ["attention-1", "attention-2", "attention-3"] + } + }; + + public static AttentionListViewModel Get(string key) + { + if (Lists.TryGetValue(key, out var list)) + return list; + + throw new KeyNotFoundException($"AttentionList with key '{key}' not found"); + } +} diff --git a/PlanTempus.Application/Features/Dashboard/Components/AttentionList/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/AttentionList/Default.cshtml new file mode 100644 index 0000000..19b9d54 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/AttentionList/Default.cshtml @@ -0,0 +1,18 @@ +@model PlanTempus.Application.Features.Dashboard.Components.AttentionListViewModel + + + + + + @Model.Title + + + + + @foreach (var attentionKey in Model.AttentionKeys) + { + @await Component.InvokeAsync("AttentionItem", attentionKey) + } + + + diff --git a/PlanTempus.Application/Features/Dashboard/Components/BookingItemViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/BookingItem/BookingItemViewComponent.cs similarity index 100% rename from PlanTempus.Application/Features/Dashboard/Components/BookingItemViewComponent.cs rename to PlanTempus.Application/Features/Dashboard/Components/BookingItem/BookingItemViewComponent.cs diff --git a/PlanTempus.Application/Features/Dashboard/Components/BookingItem/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/BookingItem/Default.cshtml index 44857f0..7d0ab44 100644 --- a/PlanTempus.Application/Features/Dashboard/Components/BookingItem/Default.cshtml +++ b/PlanTempus.Application/Features/Dashboard/Components/BookingItem/Default.cshtml @@ -7,8 +7,8 @@ - @Model.Service - @Model.CustomerName + @Model.Service + @Model.CustomerName @Model.EmployeeInitials diff --git a/PlanTempus.Application/Features/Dashboard/Components/BookingListViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/BookingList/BookingListViewComponent.cs similarity index 100% rename from PlanTempus.Application/Features/Dashboard/Components/BookingListViewComponent.cs rename to PlanTempus.Application/Features/Dashboard/Components/BookingList/BookingListViewComponent.cs diff --git a/PlanTempus.Application/Features/Dashboard/Components/NotificationItem/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/NotificationItem/Default.cshtml index 6372dfd..9a844ad 100644 --- a/PlanTempus.Application/Features/Dashboard/Components/NotificationItem/Default.cshtml +++ b/PlanTempus.Application/Features/Dashboard/Components/NotificationItem/Default.cshtml @@ -5,9 +5,9 @@ - + @Model.Title @Model.Text - - @Model.Time + + @Model.Time diff --git a/PlanTempus.Application/Features/Dashboard/Components/NotificationItemViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/NotificationItem/NotificationItemViewComponent.cs similarity index 100% rename from PlanTempus.Application/Features/Dashboard/Components/NotificationItemViewComponent.cs rename to PlanTempus.Application/Features/Dashboard/Components/NotificationItem/NotificationItemViewComponent.cs diff --git a/PlanTempus.Application/Features/Dashboard/Components/NotificationListViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/NotificationList/NotificationListViewComponent.cs similarity index 100% rename from PlanTempus.Application/Features/Dashboard/Components/NotificationListViewComponent.cs rename to PlanTempus.Application/Features/Dashboard/Components/NotificationList/NotificationListViewComponent.cs diff --git a/PlanTempus.Application/Features/Dashboard/Components/QuickStat/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/QuickStat/Default.cshtml new file mode 100644 index 0000000..97bb2d3 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/QuickStat/Default.cshtml @@ -0,0 +1,6 @@ +@model PlanTempus.Application.Features.Dashboard.Components.QuickStatViewModel + + + @Model.Value + @Model.Label + diff --git a/PlanTempus.Application/Features/Dashboard/Components/QuickStat/QuickStatViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/QuickStat/QuickStatViewComponent.cs new file mode 100644 index 0000000..eb16392 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/QuickStat/QuickStatViewComponent.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +/// +/// ViewComponent for rendering a quick stat item in the sidebar. +/// +public class QuickStatViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = QuickStatCatalog.Get(key); + return View(model); + } +} + +/// +/// ViewModel for the QuickStat component. +/// +public class QuickStatViewModel +{ + public required string Key { get; init; } + public required string Value { get; init; } + public required string Label { get; init; } +} + +/// +/// Catalog of available quick stats with their data. +/// +public static class QuickStatCatalog +{ + private static readonly Dictionary Stats = new() + { + ["bookings-week"] = new QuickStatViewModel + { + Key = "bookings-week", + Value = "47", + Label = "Bookinger" + }, + ["revenue-week"] = new QuickStatViewModel + { + Key = "revenue-week", + Value = "38.200 kr", + Label = "Omsætning" + }, + ["new-customers"] = new QuickStatViewModel + { + Key = "new-customers", + Value = "8", + Label = "Nye kunder" + }, + ["avg-occupancy"] = new QuickStatViewModel + { + Key = "avg-occupancy", + Value = "72%", + Label = "Gns. belægning" + } + }; + + public static QuickStatViewModel Get(string key) + { + if (Stats.TryGetValue(key, out var stat)) + return stat; + + throw new KeyNotFoundException($"QuickStat with key '{key}' not found"); + } + + public static IEnumerable AllKeys => Stats.Keys; +} diff --git a/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/Default.cshtml new file mode 100644 index 0000000..62d0e35 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/Default.cshtml @@ -0,0 +1,18 @@ +@model PlanTempus.Application.Features.Dashboard.Components.QuickStatListViewModel + + + + + + @Model.Title + + + + + @foreach (var statKey in Model.StatKeys) + { + @await Component.InvokeAsync("QuickStat", statKey) + } + + + diff --git a/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/QuickStatListViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/QuickStatListViewComponent.cs new file mode 100644 index 0000000..cef1f5e --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/QuickStatList/QuickStatListViewComponent.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +public class QuickStatListViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = QuickStatListCatalog.Get(key); + return View(model); + } +} + +public class QuickStatListViewModel +{ + public required string Key { get; init; } + public required string Title { get; init; } + public required string Icon { get; init; } + public required IReadOnlyList StatKeys { get; init; } +} + +public static class QuickStatListCatalog +{ + private static readonly Dictionary Lists = new() + { + ["this-week"] = new QuickStatListViewModel + { + Key = "this-week", + Title = "Denne uge", + Icon = "chart-line-up", + StatKeys = ["bookings-week", "revenue-week", "new-customers", "avg-occupancy"] + } + }; + + public static QuickStatListViewModel Get(string key) + { + if (Lists.TryGetValue(key, out var list)) + return list; + + throw new KeyNotFoundException($"QuickStatList with key '{key}' not found"); + } +} diff --git a/PlanTempus.Application/Features/Dashboard/Components/StatCardViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/StatCard/StatCardViewComponent.cs similarity index 100% rename from PlanTempus.Application/Features/Dashboard/Components/StatCardViewComponent.cs rename to PlanTempus.Application/Features/Dashboard/Components/StatCard/StatCardViewComponent.cs diff --git a/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/Default.cshtml new file mode 100644 index 0000000..3cb5755 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/Default.cshtml @@ -0,0 +1,9 @@ +@model PlanTempus.Application.Features.Dashboard.Components.WaitlistCardViewModel + + + + + @Model.Count + + @Model.Label + diff --git a/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/WaitlistCardViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/WaitlistCardViewComponent.cs new file mode 100644 index 0000000..527be13 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/WaitlistCard/WaitlistCardViewComponent.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +/// +/// ViewComponent for rendering the waitlist mini card on the dashboard. +/// Displays a count badge and triggers the waitlist drawer when clicked. +/// +public class WaitlistCardViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = WaitlistCardCatalog.Get(key); + return View(model); + } +} + +/// +/// ViewModel for the WaitlistCard component. +/// +public class WaitlistCardViewModel +{ + public required string Key { get; init; } + public required string Label { get; init; } + public required string Icon { get; init; } + public required int Count { get; init; } + public required string DrawerTarget { get; init; } +} + +/// +/// Catalog of waitlist cards with their data. +/// +public static class WaitlistCardCatalog +{ + private static readonly Dictionary Cards = new() + { + ["waitlist"] = new WaitlistCardViewModel + { + Key = "waitlist", + Label = "På venteliste", + Icon = "users-three", + Count = 4, + DrawerTarget = "waitlist-drawer" + } + }; + + public static WaitlistCardViewModel Get(string key) + { + if (Cards.TryGetValue(key, out var card)) + return card; + + throw new KeyNotFoundException($"WaitlistCard with key '{key}' not found"); + } +} diff --git a/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/Default.cshtml b/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/Default.cshtml new file mode 100644 index 0000000..4940501 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/Default.cshtml @@ -0,0 +1,41 @@ +@model PlanTempus.Application.Features.Dashboard.Components.WaitlistItemViewModel + + + + @Model.CustomerInitials + + @Model.CustomerName + @Model.CustomerPhone + + + @Model.Service + + + Ønsker: + @foreach (var period in Model.PreferredPeriods) + { + @period + } + + + + + Tilmeldt: @Model.RegisteredDate + + + + Udløber: @Model.ExpiresDate + + + + + + + Kontakt + + + + Book + + + diff --git a/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/WaitlistItemViewComponent.cs b/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/WaitlistItemViewComponent.cs new file mode 100644 index 0000000..fd59829 --- /dev/null +++ b/PlanTempus.Application/Features/Dashboard/Components/WaitlistItem/WaitlistItemViewComponent.cs @@ -0,0 +1,99 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Dashboard.Components; + +/// +/// ViewComponent for rendering a waitlist item in the waitlist drawer. +/// +public class WaitlistItemViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(string key) + { + var model = WaitlistItemCatalog.Get(key); + return View(model); + } +} + +/// +/// ViewModel for the WaitlistItem component. +/// +public class WaitlistItemViewModel +{ + public required string Key { get; init; } + public required string CustomerName { get; init; } + public required string CustomerInitials { get; init; } + public required string CustomerPhone { get; init; } + public required string Service { get; init; } + public required IReadOnlyList PreferredPeriods { get; init; } + public required string RegisteredDate { get; init; } + public required string ExpiresDate { get; init; } + public bool ExpiresSoon { get; init; } +} + +/// +/// Catalog of waitlist items with demo data. +/// +public static class WaitlistItemCatalog +{ + private static readonly Dictionary Items = new() + { + ["waitlist-1"] = new WaitlistItemViewModel + { + Key = "waitlist-1", + CustomerName = "Emma Christensen", + CustomerInitials = "EC", + CustomerPhone = "+45 12 34 56 78", + Service = "Dameklip + Farve", + PreferredPeriods = ["Mandag-Onsdag", "Formiddag"], + RegisteredDate = "2. jan 2026", + ExpiresDate = "16. jan 2026", + ExpiresSoon = false + }, + ["waitlist-2"] = new WaitlistItemViewModel + { + Key = "waitlist-2", + CustomerName = "Mikkel Sørensen", + CustomerInitials = "MS", + CustomerPhone = "+45 23 45 67 89", + Service = "Herreklip", + PreferredPeriods = ["Weekend"], + RegisteredDate = "30. dec 2025", + ExpiresDate = "6. jan 2026", + ExpiresSoon = true + }, + ["waitlist-3"] = new WaitlistItemViewModel + { + Key = "waitlist-3", + CustomerName = "Lise Andersen", + CustomerInitials = "LA", + CustomerPhone = "+45 34 56 78 90", + Service = "Balayage", + PreferredPeriods = ["Tirsdag-Torsdag", "Eftermiddag"], + RegisteredDate = "28. dec 2025", + ExpiresDate = "11. jan 2026", + ExpiresSoon = false + }, + ["waitlist-4"] = new WaitlistItemViewModel + { + Key = "waitlist-4", + CustomerName = "Peter Hansen", + CustomerInitials = "PH", + CustomerPhone = "+45 45 67 89 01", + Service = "Herreklip + Skæg", + PreferredPeriods = ["Fleksibel"], + RegisteredDate = "27. dec 2025", + ExpiresDate = "10. jan 2026", + ExpiresSoon = false + } + }; + + public static WaitlistItemViewModel Get(string key) + { + if (Items.TryGetValue(key, out var item)) + return item; + + throw new KeyNotFoundException($"WaitlistItem with key '{key}' not found"); + } + + public static IEnumerable AllKeys => Items.Keys; +} diff --git a/PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml b/PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml index 5c78b61..cfa7088 100644 --- a/PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml +++ b/PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml @@ -33,12 +33,21 @@ @await Component.InvokeAsync("BookingList", "todays-bookings") + + + @await Component.InvokeAsync("AttentionList", "current-attentions") @await Component.InvokeAsync("NotificationList", "recent-notifications") + + @await Component.InvokeAsync("WaitlistCard", "waitlist") + + + @await Component.InvokeAsync("QuickStatList", "this-week") + diff --git a/PlanTempus.Application/Features/Shared/_WaitlistDrawer.cshtml b/PlanTempus.Application/Features/Shared/_WaitlistDrawer.cshtml new file mode 100644 index 0000000..bee4ac3 --- /dev/null +++ b/PlanTempus.Application/Features/Shared/_WaitlistDrawer.cshtml @@ -0,0 +1,21 @@ +@using PlanTempus.Application.Features.Dashboard.Components + + + + + Venteliste (@WaitlistItemCatalog.AllKeys.Count()) + + + + + + + + + @foreach (var key in WaitlistItemCatalog.AllKeys) + { + @await Component.InvokeAsync("WaitlistItem", key) + } + + + diff --git a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml index 36be511..4413b76 100644 --- a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml +++ b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml @@ -20,6 +20,9 @@ + + + @await RenderSectionAsync("Styles", required: false) @@ -47,6 +50,7 @@ + diff --git a/PlanTempus.Application/wwwroot/css/attentions.css b/PlanTempus.Application/wwwroot/css/attentions.css new file mode 100644 index 0000000..47d4738 --- /dev/null +++ b/PlanTempus.Application/wwwroot/css/attentions.css @@ -0,0 +1,114 @@ +/** + * Attentions CSS + * + * Styling for attention/alert components on dashboard + */ + +/* =========================================== + ATTENTION LIST + =========================================== */ +swp-attention-list { + display: contents; +} + +/* =========================================== + ATTENTION ITEM + =========================================== */ +swp-attention-item { + display: grid; + grid-column: 1 / -1; + grid-template-columns: subgrid; + align-items: center; + gap: var(--spacing-8); + padding: var(--spacing-5) var(--spacing-6); + background: var(--color-background-alt); + border-radius: var(--radius-xl); + border-left: 3px solid var(--color-border); + cursor: pointer; + transition: background var(--transition-fast); +} + +swp-attention-item:hover { + background: var(--color-background-hover); +} + +/* Severity: Urgent (red) */ +swp-attention-item.urgent { + border-left-color: var(--color-red); + background: color-mix(in srgb, var(--color-red) 5%, var(--color-background-alt)); +} + +swp-attention-item.urgent:hover { + background: color-mix(in srgb, var(--color-red) 8%, var(--color-background-alt)); +} + +/* Severity: Warning (amber) */ +swp-attention-item.warning { + border-left-color: var(--color-amber); + background: color-mix(in srgb, var(--color-amber) 5%, var(--color-background-alt)); +} + +swp-attention-item.warning:hover { + background: color-mix(in srgb, var(--color-amber) 8%, var(--color-background-alt)); +} + +/* Severity: Info (blue) */ +swp-attention-item.info { + border-left-color: var(--color-blue); +} + +/* =========================================== + ATTENTION ICON + =========================================== */ +swp-attention-icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--color-background-hover); + border-radius: var(--radius-xl); + color: var(--color-text-secondary); + font-size: var(--font-size-xl); +} + +/* Icon colors per severity */ +swp-attention-item.urgent swp-attention-icon { + background: color-mix(in srgb, var(--color-red) 15%, transparent); + color: var(--color-red); +} + +swp-attention-item.warning swp-attention-icon { + background: color-mix(in srgb, var(--color-amber) 15%, transparent); + color: var(--color-amber); +} + +swp-attention-item.info swp-attention-icon { + background: color-mix(in srgb, var(--color-blue) 15%, transparent); + color: var(--color-blue); +} + +/* =========================================== + ATTENTION CONTENT + =========================================== */ +swp-attention-content { + display: flex; + flex-direction: column; + min-width: 0; +} + +/* =========================================== + ATTENTION ACTION + =========================================== */ +swp-attention-action { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--color-teal); + cursor: pointer; + white-space: nowrap; + transition: text-decoration var(--transition-fast); +} + +swp-attention-action:hover { + text-decoration: underline; +} diff --git a/PlanTempus.Application/wwwroot/css/bookings.css b/PlanTempus.Application/wwwroot/css/bookings.css index 695efd9..b04e98e 100644 --- a/PlanTempus.Application/wwwroot/css/bookings.css +++ b/PlanTempus.Application/wwwroot/css/bookings.css @@ -8,10 +8,7 @@ BOOKING LIST =========================================== */ swp-booking-list { - display: grid; - grid-template-columns: 50px 4px 1fr auto auto; - gap: var(--spacing-4); - padding: 0 var(--card-body-padding); + display: contents; } /* =========================================== @@ -90,22 +87,6 @@ swp-booking-details { overflow: hidden; } -swp-booking-service { - display: block; - font-size: var(--font-size-base); - font-weight: var(--font-weight-medium); - color: var(--color-text); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -swp-booking-customer { - display: block; - font-size: var(--font-size-sm); - color: var(--color-text-secondary); -} - /* =========================================== BOOKING EMPLOYEE =========================================== */ @@ -174,7 +155,7 @@ swp-current-time { padding: var(--spacing-4) var(--spacing-6); background: color-mix(in srgb, var(--color-teal) 10%, transparent); border-radius: var(--radius-lg); - margin: 0 var(--card-body-padding) var(--spacing-4) var(--card-body-padding); + margin-bottom: var(--spacing-4); } swp-current-time i { diff --git a/PlanTempus.Application/wwwroot/css/drawers.css b/PlanTempus.Application/wwwroot/css/drawers.css index 218d3fd..1ac07b1 100644 --- a/PlanTempus.Application/wwwroot/css/drawers.css +++ b/PlanTempus.Application/wwwroot/css/drawers.css @@ -5,8 +5,36 @@ */ /* =========================================== - BASE DRAWER + BASE DRAWER (Generic) =========================================== */ +[data-drawer] { + position: fixed; + top: 0; + right: 0; + width: var(--drawer-width, 320px); + height: 100vh; + background: var(--color-surface); + border-left: 1px solid var(--color-border); + box-shadow: var(--shadow-lg); + z-index: var(--z-drawer); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform var(--transition-normal); +} + +[data-drawer].active, +[data-drawer].open { + transform: translateX(0); +} + +/* Drawer width variants */ +[data-drawer="sm"] { --drawer-width: 280px; } +[data-drawer="md"] { --drawer-width: 360px; } +[data-drawer="lg"] { --drawer-width: 420px; } +[data-drawer="xl"] { --drawer-width: 480px; } + +/* Legacy support for existing drawers */ swp-profile-drawer, swp-notification-drawer, swp-todo-drawer { @@ -38,17 +66,26 @@ swp-drawer-header { display: flex; align-items: center; justify-content: space-between; - padding: var(--spacing-4) var(--spacing-5); + padding: var(--spacing-10) var(--spacing-12); border-bottom: 1px solid var(--color-border); flex-shrink: 0; } swp-drawer-title { + display: flex; + align-items: center; + gap: var(--spacing-2); font-size: var(--font-size-lg); - font-weight: 600; + font-weight: var(--font-weight-semibold); color: var(--color-text); } +swp-drawer-title swp-count { + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--color-text-secondary); +} + swp-drawer-close { width: 32px; height: 32px; @@ -56,8 +93,8 @@ swp-drawer-close { align-items: center; justify-content: center; border: none; - background: transparent; - border-radius: var(--border-radius); + background: var(--color-background-alt); + border-radius: var(--radius-md); cursor: pointer; color: var(--color-text-secondary); transition: all var(--transition-fast); @@ -73,12 +110,13 @@ swp-drawer-close i { } /* =========================================== - DRAWER CONTENT + DRAWER CONTENT / BODY =========================================== */ -swp-drawer-content { +swp-drawer-content, +swp-drawer-body { flex: 1; overflow-y: auto; - padding: var(--spacing-5); + padding: var(--spacing-8); } swp-drawer-divider { diff --git a/PlanTempus.Application/wwwroot/css/notifications.css b/PlanTempus.Application/wwwroot/css/notifications.css index f16bb76..1f589f9 100644 --- a/PlanTempus.Application/wwwroot/css/notifications.css +++ b/PlanTempus.Application/wwwroot/css/notifications.css @@ -8,10 +8,7 @@ NOTIFICATION LIST =========================================== */ swp-notification-list { - display: grid; - grid-template-columns: 50px 1fr; - gap: var(--spacing-4) var(--spacing-6); - padding: 0 var(--card-body-padding); + display: contents; } /* =========================================== @@ -70,23 +67,3 @@ swp-notification-content { min-width: 0; } -swp-notification-text { - display: block; - font-size: var(--font-size-md); - color: var(--color-text); - line-height: var(--line-height-snug); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -swp-notification-text strong { - font-weight: var(--font-weight-semibold); -} - -swp-notification-time { - display: block; - font-size: var(--font-size-xs); - color: var(--color-text-secondary); - margin-top: var(--spacing-1); -} diff --git a/PlanTempus.Application/wwwroot/css/page.css b/PlanTempus.Application/wwwroot/css/page.css index 4372d4f..c8263aa 100644 --- a/PlanTempus.Application/wwwroot/css/page.css +++ b/PlanTempus.Application/wwwroot/css/page.css @@ -49,6 +49,7 @@ swp-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--border-radius-lg); + padding: var(--spacing-5); margin-bottom: var(--spacing-5); } @@ -56,6 +57,7 @@ swp-card-header { display: flex; align-items: center; justify-content: space-between; + margin: calc(-1 * var(--spacing-5)) calc(-1 * var(--spacing-5)) var(--spacing-4) calc(-1 * var(--spacing-5)); padding: var(--spacing-4) var(--spacing-5); border-bottom: 1px solid var(--color-border); } @@ -86,7 +88,44 @@ swp-card-action:hover { } swp-card-content { - padding: var(--spacing-5); + display: block; +} + +/* Card content with grid lists - auto-detect via :has() */ +swp-card-content:has(> swp-booking-list), +swp-card-content:has(> swp-notification-list), +swp-card-content:has(> swp-attention-list) { + display: grid; + gap: var(--spacing-4); +} + +swp-card-content:has(> swp-booking-list) { + grid-template-columns: 50px 4px 1fr auto auto; +} + +swp-card-content:has(> swp-notification-list) { + grid-template-columns: 50px 1fr; +} + +swp-card-content:has(> swp-attention-list) { + grid-template-columns: 50px 1fr auto; +} + +/* Generic list item title & description */ +swp-item-title { + display: block; + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +swp-item-desc { + display: block; + font-size: var(--font-size-xs); + color: var(--color-text-secondary); } /* =========================================== @@ -98,14 +137,11 @@ swp-dashboard-grid { gap: var(--spacing-5); } -swp-main-column { - display: flex; - flex-direction: column; -} - +swp-main-column, swp-side-column { - display: flex; - flex-direction: column; + display: grid; + gap: var(--spacing-5); + align-content: start; } /* =========================================== diff --git a/PlanTempus.Application/wwwroot/css/quick-stats.css b/PlanTempus.Application/wwwroot/css/quick-stats.css new file mode 100644 index 0000000..ec1f626 --- /dev/null +++ b/PlanTempus.Application/wwwroot/css/quick-stats.css @@ -0,0 +1,38 @@ +/** + * Quick Stats CSS + * + * Styling for quick stats components in sidebar + */ + +/* =========================================== + QUICK STATS CONTAINER + =========================================== */ +swp-quick-stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--spacing-3); +} + +/* =========================================== + QUICK STAT ITEM + =========================================== */ +swp-quick-stat { + display: flex; + flex-direction: column; + gap: var(--spacing-1); + padding: var(--spacing-3); + background: var(--color-background-alt); + border-radius: var(--radius-md); +} + +swp-quick-stat swp-stat-value { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + font-family: var(--font-mono); + color: var(--color-text); +} + +swp-quick-stat swp-stat-label { + font-size: var(--font-size-xs); + color: var(--color-text-secondary); +} diff --git a/PlanTempus.Application/wwwroot/css/stats.css b/PlanTempus.Application/wwwroot/css/stats.css index eb4ed7a..dd49a10 100644 --- a/PlanTempus.Application/wwwroot/css/stats.css +++ b/PlanTempus.Application/wwwroot/css/stats.css @@ -182,33 +182,6 @@ swp-stat-card.highlight.filled swp-stat-change { color: rgba(255, 255, 255, 0.9); } -/* =========================================== - QUICK STATS (Compact Variant) - =========================================== */ -swp-quick-stats { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: var(--spacing-3); -} - -swp-quick-stat { - display: flex; - flex-direction: column; - text-align: center; - padding: var(--spacing-3); - background: var(--color-background); - border-radius: var(--border-radius); -} - -swp-quick-stat swp-stat-value { - font-size: var(--font-size-xl); -} - -swp-quick-stat swp-stat-label { - font-size: var(--font-size-xs); - margin-top: var(--spacing-1); -} - /* =========================================== STAT ITEM (Inline Variant) =========================================== */ diff --git a/PlanTempus.Application/wwwroot/css/waitlist.css b/PlanTempus.Application/wwwroot/css/waitlist.css new file mode 100644 index 0000000..8500654 --- /dev/null +++ b/PlanTempus.Application/wwwroot/css/waitlist.css @@ -0,0 +1,250 @@ +/** + * Waitlist CSS + * + * Styling for waitlist mini card and drawer items + */ + +/* =========================================== + WAITLIST MINI CARD + =========================================== */ +swp-waitlist-card { + display: flex; + align-items: center; + gap: var(--spacing-3); + padding: var(--spacing-4); + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-fast); +} + +swp-waitlist-card:hover { + border-color: var(--color-teal); + box-shadow: var(--shadow-md); +} + +swp-waitlist-icon { + position: relative; + font-size: var(--font-size-2xl); + color: var(--color-text-secondary); +} + +swp-waitlist-badge { + position: absolute; + top: -8px; + right: -8px; + min-width: 20px; + height: 20px; + padding: 0 var(--spacing-2); + background: var(--color-teal); + color: white; + font-size: var(--font-size-xs); + font-weight: var(--font-weight-semibold); + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; +} + +swp-waitlist-label { + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +/* =========================================== + WAITLIST LIST + =========================================== */ +swp-waitlist-list { + display: flex; + flex-direction: column; + gap: var(--spacing-6); +} + +/* =========================================== + WAITLIST ITEM + =========================================== */ +swp-waitlist-item { + display: flex; + flex-direction: column; + gap: var(--spacing-6); + padding: var(--spacing-8); + background: var(--color-background-alt); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); +} + +/* =========================================== + CUSTOMER SECTION + =========================================== */ +swp-waitlist-customer { + display: flex; + align-items: center; + gap: var(--spacing-6); +} + +swp-waitlist-customer swp-avatar { + width: 40px; + height: 40px; + border-radius: var(--radius-full); + background: var(--color-teal); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-size-base); + font-weight: var(--font-weight-semibold); + flex-shrink: 0; +} + +swp-waitlist-customer-info { + flex: 1; + min-width: 0; +} + +swp-waitlist-name { + display: block; + font-size: var(--font-size-base); + font-weight: var(--font-weight-semibold); + color: var(--color-text); +} + +swp-waitlist-phone { + display: block; + font-size: var(--font-size-sm); + color: var(--color-text-secondary); +} + +/* =========================================== + SERVICE TAG + =========================================== */ +swp-waitlist-service { + display: inline-block; + font-size: var(--font-size-md); + font-weight: var(--font-weight-medium); + color: var(--color-teal); + padding: var(--spacing-2) var(--spacing-3); + background: color-mix(in srgb, var(--color-teal) 10%, transparent); + border-radius: var(--radius-sm); +} + +/* =========================================== + META SECTION (Preferences & Dates) + =========================================== */ +swp-waitlist-meta { + display: flex; + flex-direction: column; + gap: var(--spacing-2); +} + +swp-waitlist-periods { + display: flex; + align-items: center; + gap: var(--spacing-2); + flex-wrap: wrap; +} + +swp-waitlist-periods swp-label { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); +} + +swp-waitlist-period-tag { + font-size: var(--font-size-xs); + padding: var(--spacing-1) var(--spacing-2); + background: var(--color-background); + border-radius: var(--radius-sm); + color: var(--color-text); +} + +swp-waitlist-dates { + display: flex; + align-items: center; + gap: var(--spacing-4); +} + +swp-waitlist-date { + font-size: var(--font-size-xs); + color: var(--color-text-secondary); + display: flex; + align-items: center; + gap: var(--spacing-1); +} + +swp-waitlist-date i { + font-size: var(--font-size-sm); +} + +swp-waitlist-date.expires.soon { + color: var(--color-amber); + font-weight: var(--font-weight-medium); +} + +/* =========================================== + ACTION BUTTONS + =========================================== */ +swp-waitlist-actions { + display: flex; + gap: var(--spacing-4); + padding-top: var(--spacing-4); + border-top: 1px solid var(--color-border); +} + +swp-waitlist-actions swp-btn { + flex: 1; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-2); + padding: var(--spacing-4) var(--spacing-6); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + font-family: var(--font-family); + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast); + border: none; +} + +swp-waitlist-actions swp-btn.secondary { + background: var(--color-surface); + border: 1px solid var(--color-border); + color: var(--color-text); +} + +swp-waitlist-actions swp-btn.secondary:hover { + background: var(--color-background-hover); +} + +swp-waitlist-actions swp-btn.primary { + background: var(--color-teal); + color: white; +} + +swp-waitlist-actions swp-btn.primary:hover { + opacity: 0.9; +} + +/* =========================================== + EMPTY STATE + =========================================== */ +swp-waitlist-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--spacing-10) var(--spacing-6); + text-align: center; +} + +swp-waitlist-empty i { + font-size: 48px; + color: var(--color-border); + margin-bottom: var(--spacing-4); +} + +swp-waitlist-empty span { + font-size: var(--font-size-base); + color: var(--color-text-secondary); +} diff --git a/PlanTempus.Application/wwwroot/js/app.js b/PlanTempus.Application/wwwroot/js/app.js index 701f2c5..7aed0c8 100644 --- a/PlanTempus.Application/wwwroot/js/app.js +++ b/PlanTempus.Application/wwwroot/js/app.js @@ -1,8 +1,5 @@ -var __defProp = Object.defineProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); - -// wwwroot/ts/modules/sidebar.ts -var _SidebarController = class _SidebarController { +// modules/sidebar.ts +var SidebarController = class { constructor() { this.menuToggle = null; this.appLayout = null; @@ -24,8 +21,7 @@ var _SidebarController = class _SidebarController { * Toggle sidebar collapsed state */ toggle() { - if (!this.appLayout) - return; + if (!this.appLayout) return; this.appLayout.classList.toggle("menu-collapsed"); localStorage.setItem("sidebar-collapsed", String(this.isCollapsed)); } @@ -47,8 +43,7 @@ var _SidebarController = class _SidebarController { this.menuToggle?.addEventListener("click", () => this.toggle()); } setupTooltips() { - if (!this.menuTooltip) - return; + if (!this.menuTooltip) return; const menuItems = document.querySelectorAll("swp-side-menu-item[data-tooltip]"); menuItems.forEach((item) => { item.addEventListener("mouseenter", () => this.showTooltip(item)); @@ -56,12 +51,10 @@ var _SidebarController = class _SidebarController { }); } showTooltip(item) { - if (!this.isCollapsed || !this.menuTooltip) - return; + if (!this.isCollapsed || !this.menuTooltip) return; const rect = item.getBoundingClientRect(); const tooltipText = item.dataset.tooltip; - if (!tooltipText) - return; + if (!tooltipText) return; this.menuTooltip.textContent = tooltipText; this.menuTooltip.style.left = `${rect.right + 8}px`; this.menuTooltip.style.top = `${rect.top + rect.height / 2}px`; @@ -72,18 +65,15 @@ var _SidebarController = class _SidebarController { this.menuTooltip?.hidePopover(); } restoreState() { - if (!this.appLayout) - return; + if (!this.appLayout) return; if (localStorage.getItem("sidebar-collapsed") === "true") { this.appLayout.classList.add("menu-collapsed"); } } }; -__name(_SidebarController, "SidebarController"); -var SidebarController = _SidebarController; -// wwwroot/ts/modules/drawers.ts -var _DrawerController = class _DrawerController { +// modules/drawers.ts +var DrawerController = class { constructor() { this.profileDrawer = null; this.notificationDrawer = null; @@ -91,12 +81,14 @@ var _DrawerController = class _DrawerController { this.newTodoDrawer = null; this.overlay = null; this.activeDrawer = null; + this.activeGenericDrawer = null; this.profileDrawer = document.getElementById("profileDrawer"); this.notificationDrawer = document.getElementById("notificationDrawer"); this.todoDrawer = document.getElementById("todoDrawer"); this.newTodoDrawer = document.getElementById("newTodoDrawer"); this.overlay = document.getElementById("drawerOverlay"); this.setupListeners(); + this.setupGenericDrawers(); } /** * Get currently active drawer name @@ -136,10 +128,31 @@ var _DrawerController = class _DrawerController { */ closeAll() { [this.profileDrawer, this.notificationDrawer, this.todoDrawer, this.newTodoDrawer].forEach((drawer) => drawer?.classList.remove("active")); + this.closeGenericDrawer(); this.overlay?.classList.remove("active"); document.body.style.overflow = ""; this.activeDrawer = null; } + /** + * Open a generic drawer by ID + */ + openGenericDrawer(drawerId) { + this.closeAll(); + const drawer = document.getElementById(drawerId); + if (drawer && this.overlay) { + drawer.classList.add("open"); + this.overlay.classList.add("active"); + document.body.style.overflow = "hidden"; + this.activeGenericDrawer = drawer; + } + } + /** + * Close the currently open generic drawer + */ + closeGenericDrawer() { + this.activeGenericDrawer?.classList.remove("open"); + this.activeGenericDrawer = null; + } /** * Open profile drawer */ @@ -181,8 +194,7 @@ var _DrawerController = class _DrawerController { * Mark all notifications as read */ markAllNotificationsRead() { - if (!this.notificationDrawer) - return; + if (!this.notificationDrawer) return; const unreadItems = this.notificationDrawer.querySelectorAll( 'swp-notification-item[data-unread="true"]' ); @@ -218,8 +230,7 @@ var _DrawerController = class _DrawerController { document.getElementById("saveNewTodo")?.addEventListener("click", () => this.closeNewTodo()); this.overlay?.addEventListener("click", () => this.closeAll()); document.addEventListener("keydown", (e) => { - if (e.key === "Escape") - this.closeAll(); + if (e.key === "Escape") this.closeAll(); }); this.todoDrawer?.addEventListener("click", (e) => this.handleTodoClick(e)); document.addEventListener("click", (e) => this.handleVisibilityClick(e)); @@ -250,12 +261,44 @@ var _DrawerController = class _DrawerController { option.classList.add("active"); } } + /** + * Setup generic drawer triggers and close buttons + * Uses data-drawer-trigger="drawer-id" and data-drawer-close attributes + */ + setupGenericDrawers() { + document.addEventListener("click", (e) => { + const target = e.target; + const trigger = target.closest("[data-drawer-trigger]"); + if (trigger) { + const drawerId = trigger.dataset.drawerTrigger; + if (drawerId) { + this.openGenericDrawer(drawerId); + } + } + }); + document.addEventListener("click", (e) => { + const target = e.target; + const closeBtn = target.closest("[data-drawer-close]"); + if (closeBtn) { + this.closeGenericDrawer(); + this.overlay?.classList.remove("active"); + document.body.style.overflow = ""; + } + }); + } }; -__name(_DrawerController, "DrawerController"); -var DrawerController = _DrawerController; -// wwwroot/ts/modules/theme.ts -var _ThemeController = class _ThemeController { +// modules/theme.ts +var ThemeController = class _ThemeController { + static { + this.STORAGE_KEY = "theme-preference"; + } + static { + this.DARK_CLASS = "dark-mode"; + } + static { + this.LIGHT_CLASS = "light-mode"; + } constructor() { this.root = document.documentElement; this.themeOptions = document.querySelectorAll("swp-theme-option"); @@ -308,8 +351,7 @@ var _ThemeController = class _ThemeController { } } updateUI() { - if (!this.themeOptions) - return; + if (!this.themeOptions) return; const darkActive = this.isDark; this.themeOptions.forEach((option) => { const theme = option.dataset.theme; @@ -339,14 +381,9 @@ var _ThemeController = class _ThemeController { } } }; -__name(_ThemeController, "ThemeController"); -_ThemeController.STORAGE_KEY = "theme-preference"; -_ThemeController.DARK_CLASS = "dark-mode"; -_ThemeController.LIGHT_CLASS = "light-mode"; -var ThemeController = _ThemeController; -// wwwroot/ts/modules/search.ts -var _SearchController = class _SearchController { +// modules/search.ts +var SearchController = class { constructor() { this.input = null; this.container = null; @@ -415,19 +452,16 @@ var _SearchController = class _SearchController { handleSubmit(e) { e.preventDefault(); const query = this.value.trim(); - if (!query) - return; + if (!query) return; document.dispatchEvent(new CustomEvent("app:search-submit", { detail: { query }, bubbles: true })); } }; -__name(_SearchController, "SearchController"); -var SearchController = _SearchController; -// wwwroot/ts/modules/lockscreen.ts -var _LockScreenController = class _LockScreenController { +// modules/lockscreen.ts +var LockScreenController = class _LockScreenController { constructor(drawers) { // Demo PIN this.lockScreen = null; @@ -445,6 +479,9 @@ var _LockScreenController = class _LockScreenController { this.pinDigits = this.pinInput?.querySelectorAll("swp-pin-digit") ?? null; this.setupListeners(); } + static { + this.CORRECT_PIN = "1234"; + } /** * Check if lock screen is active */ @@ -484,8 +521,7 @@ var _LockScreenController = class _LockScreenController { return `${hours}:${minutes}`; } updateDisplay() { - if (!this.pinDigits) - return; + if (!this.pinDigits) return; this.pinDigits.forEach((digit, index) => { digit.classList.remove("filled", "error"); if (index < this.currentPin.length) { @@ -497,8 +533,7 @@ var _LockScreenController = class _LockScreenController { }); } showError() { - if (!this.pinDigits) - return; + if (!this.pinDigits) return; this.pinDigits.forEach((digit) => digit.classList.add("error")); this.pinInput?.classList.add("shake"); setTimeout(() => { @@ -515,8 +550,7 @@ var _LockScreenController = class _LockScreenController { } } addDigit(digit) { - if (this.currentPin.length >= 4) - return; + if (this.currentPin.length >= 4) return; this.currentPin += digit; this.updateDisplay(); if (this.currentPin.length === 4) { @@ -524,8 +558,7 @@ var _LockScreenController = class _LockScreenController { } } removeDigit() { - if (this.currentPin.length === 0) - return; + if (this.currentPin.length === 0) return; this.currentPin = this.currentPin.slice(0, -1); this.updateDisplay(); } @@ -541,8 +574,7 @@ var _LockScreenController = class _LockScreenController { handleKeypadClick(e) { const target = e.target; const key = target.closest("swp-pin-key"); - if (!key) - return; + if (!key) return; const digit = key.dataset.digit; const action = key.dataset.action; if (digit) { @@ -554,8 +586,7 @@ var _LockScreenController = class _LockScreenController { } } handleKeyboard(e) { - if (!this.isActive) - return; + if (!this.isActive) return; e.preventDefault(); if (e.key >= "0" && e.key <= "9") { this.addDigit(e.key); @@ -566,12 +597,9 @@ var _LockScreenController = class _LockScreenController { } } }; -__name(_LockScreenController, "LockScreenController"); -_LockScreenController.CORRECT_PIN = "1234"; -var LockScreenController = _LockScreenController; -// wwwroot/ts/app.ts -var _App = class _App { +// app.ts +var App = class { constructor() { this.sidebar = new SidebarController(); this.drawers = new DrawerController(); @@ -580,8 +608,6 @@ var _App = class _App { this.lockScreen = new LockScreenController(this.drawers); } }; -__name(_App, "App"); -var App = _App; var app; function init() { app = new App(); @@ -589,7 +615,6 @@ function init() { window.app = app; } } -__name(init, "init"); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { @@ -601,4 +626,4 @@ export { app, app_default as default }; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vdHMvbW9kdWxlcy9zaWRlYmFyLnRzIiwgIi4uL3RzL21vZHVsZXMvZHJhd2Vycy50cyIsICIuLi90cy9tb2R1bGVzL3RoZW1lLnRzIiwgIi4uL3RzL21vZHVsZXMvc2VhcmNoLnRzIiwgIi4uL3RzL21vZHVsZXMvbG9ja3NjcmVlbi50cyIsICIuLi90cy9hcHAudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogU2lkZWJhciBDb250cm9sbGVyXG4gKlxuICogSGFuZGxlcyBzaWRlYmFyIGNvbGxhcHNlL2V4cGFuZCBhbmQgdG9vbHRpcCBmdW5jdGlvbmFsaXR5XG4gKi9cblxuZXhwb3J0IGNsYXNzIFNpZGViYXJDb250cm9sbGVyIHtcbiAgcHJpdmF0ZSBtZW51VG9nZ2xlOiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGFwcExheW91dDogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBtZW51VG9vbHRpcDogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcblxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLm1lbnVUb2dnbGUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbWVudVRvZ2dsZScpO1xuICAgIHRoaXMuYXBwTGF5b3V0ID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWFwcC1sYXlvdXQnKTtcbiAgICB0aGlzLm1lbnVUb29sdGlwID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ21lbnVUb29sdGlwJyk7XG5cbiAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5zZXR1cFRvb2x0aXBzKCk7XG4gICAgdGhpcy5yZXN0b3JlU3RhdGUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBzaWRlYmFyIGlzIGNvbGxhcHNlZFxuICAgKi9cbiAgZ2V0IGlzQ29sbGFwc2VkKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmFwcExheW91dD8uY2xhc3NMaXN0LmNvbnRhaW5zKCdtZW51LWNvbGxhcHNlZCcpID8/IGZhbHNlO1xuICB9XG5cbiAgLyoqXG4gICAqIFRvZ2dsZSBzaWRlYmFyIGNvbGxhcHNlZCBzdGF0ZVxuICAgKi9cbiAgdG9nZ2xlKCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5hcHBMYXlvdXQpIHJldHVybjtcblxuICAgIHRoaXMuYXBwTGF5b3V0LmNsYXNzTGlzdC50b2dnbGUoJ21lbnUtY29sbGFwc2VkJyk7XG4gICAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oJ3NpZGViYXItY29sbGFwc2VkJywgU3RyaW5nKHRoaXMuaXNDb2xsYXBzZWQpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb2xsYXBzZSB0aGUgc2lkZWJhclxuICAgKi9cbiAgY29sbGFwc2UoKTogdm9pZCB7XG4gICAgdGhpcy5hcHBMYXlvdXQ/LmNsYXNzTGlzdC5hZGQoJ21lbnUtY29sbGFwc2VkJyk7XG4gICAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oJ3NpZGViYXItY29sbGFwc2VkJywgJ3RydWUnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFeHBhbmQgdGhlIHNpZGViYXJcbiAgICovXG4gIGV4cGFuZCgpOiB2b2lkIHtcbiAgICB0aGlzLmFwcExheW91dD8uY2xhc3NMaXN0LnJlbW92ZSgnbWVudS1jb2xsYXBzZWQnKTtcbiAgICBsb2NhbFN0b3JhZ2Uuc2V0SXRlbSgnc2lkZWJhci1jb2xsYXBzZWQnLCAnZmFsc2UnKTtcbiAgfVxuXG4gIHByaXZhdGUgc2V0dXBMaXN0ZW5lcnMoKTogdm9pZCB7XG4gICAgdGhpcy5tZW51VG9nZ2xlPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMudG9nZ2xlKCkpO1xuICB9XG5cbiAgcHJpdmF0ZSBzZXR1cFRvb2x0aXBzKCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5tZW51VG9vbHRpcCkgcmV0dXJuO1xuXG4gICAgY29uc3QgbWVudUl0ZW1zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbDxIVE1MRWxlbWVudD4oJ3N3cC1zaWRlLW1lbnUtaXRlbVtkYXRhLXRvb2x0aXBdJyk7XG5cbiAgICBtZW51SXRlbXMuZm9yRWFjaChpdGVtID0+IHtcbiAgICAgIGl0ZW0uYWRkRXZlbnRMaXN0ZW5lcignbW91c2VlbnRlcicsICgpID0+IHRoaXMuc2hvd1Rvb2x0aXAoaXRlbSkpO1xuICAgICAgaXRlbS5hZGRFdmVudExpc3RlbmVyKCdtb3VzZWxlYXZlJywgKCkgPT4gdGhpcy5oaWRlVG9vbHRpcCgpKTtcbiAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgc2hvd1Rvb2x0aXAoaXRlbTogSFRNTEVsZW1lbnQpOiB2b2lkIHtcbiAgICBpZiAoIXRoaXMuaXNDb2xsYXBzZWQgfHwgIXRoaXMubWVudVRvb2x0aXApIHJldHVybjtcblxuICAgIGNvbnN0IHJlY3QgPSBpdGVtLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGNvbnN0IHRvb2x0aXBUZXh0ID0gaXRlbS5kYXRhc2V0LnRvb2x0aXA7XG5cbiAgICBpZiAoIXRvb2x0aXBUZXh0KSByZXR1cm47XG5cbiAgICB0aGlzLm1lbnVUb29sdGlwLnRleHRDb250ZW50ID0gdG9vbHRpcFRleHQ7XG4gICAgdGhpcy5tZW51VG9vbHRpcC5zdHlsZS5sZWZ0ID0gYCR7cmVjdC5yaWdodCArIDh9cHhgO1xuICAgIHRoaXMubWVudVRvb2x0aXAuc3R5bGUudG9wID0gYCR7cmVjdC50b3AgKyByZWN0LmhlaWdodCAvIDJ9cHhgO1xuICAgIHRoaXMubWVudVRvb2x0aXAuc3R5bGUudHJhbnNmb3JtID0gJ3RyYW5zbGF0ZVkoLTUwJSknO1xuICAgIHRoaXMubWVudVRvb2x0aXAuc2hvd1BvcG92ZXIoKTtcbiAgfVxuXG4gIHByaXZhdGUgaGlkZVRvb2x0aXAoKTogdm9pZCB7XG4gICAgdGhpcy5tZW51VG9vbHRpcD8uaGlkZVBvcG92ZXIoKTtcbiAgfVxuXG4gIHByaXZhdGUgcmVzdG9yZVN0YXRlKCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5hcHBMYXlvdXQpIHJldHVybjtcblxuICAgIGlmIChsb2NhbFN0b3JhZ2UuZ2V0SXRlbSgnc2lkZWJhci1jb2xsYXBzZWQnKSA9PT0gJ3RydWUnKSB7XG4gICAgICB0aGlzLmFwcExheW91dC5jbGFzc0xpc3QuYWRkKCdtZW51LWNvbGxhcHNlZCcpO1xuICAgIH1cbiAgfVxufVxuIiwgIi8qKlxuICogRHJhd2VyIENvbnRyb2xsZXJcbiAqXG4gKiBIYW5kbGVzIGFsbCBkcmF3ZXIgZnVuY3Rpb25hbGl0eSBpbmNsdWRpbmcgcHJvZmlsZSwgbm90aWZpY2F0aW9ucywgYW5kIHRvZG8gZHJhd2Vyc1xuICovXG5cbmV4cG9ydCB0eXBlIERyYXdlck5hbWUgPSAncHJvZmlsZScgfCAnbm90aWZpY2F0aW9uJyB8ICd0b2RvJyB8ICduZXdUb2RvJztcblxuZXhwb3J0IGNsYXNzIERyYXdlckNvbnRyb2xsZXIge1xuICBwcml2YXRlIHByb2ZpbGVEcmF3ZXI6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgbm90aWZpY2F0aW9uRHJhd2VyOiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHRvZG9EcmF3ZXI6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgbmV3VG9kb0RyYXdlcjogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBvdmVybGF5OiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGFjdGl2ZURyYXdlcjogRHJhd2VyTmFtZSB8IG51bGwgPSBudWxsO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMucHJvZmlsZURyYXdlciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwcm9maWxlRHJhd2VyJyk7XG4gICAgdGhpcy5ub3RpZmljYXRpb25EcmF3ZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbm90aWZpY2F0aW9uRHJhd2VyJyk7XG4gICAgdGhpcy50b2RvRHJhd2VyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3RvZG9EcmF3ZXInKTtcbiAgICB0aGlzLm5ld1RvZG9EcmF3ZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbmV3VG9kb0RyYXdlcicpO1xuICAgIHRoaXMub3ZlcmxheSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmF3ZXJPdmVybGF5Jyk7XG5cbiAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGN1cnJlbnRseSBhY3RpdmUgZHJhd2VyIG5hbWVcbiAgICovXG4gIGdldCBhY3RpdmUoKTogRHJhd2VyTmFtZSB8IG51bGwge1xuICAgIHJldHVybiB0aGlzLmFjdGl2ZURyYXdlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuIGEgZHJhd2VyIGJ5IG5hbWVcbiAgICovXG4gIG9wZW4obmFtZTogRHJhd2VyTmFtZSk6IHZvaWQge1xuICAgIHRoaXMuY2xvc2VBbGwoKTtcblxuICAgIGNvbnN0IGRyYXdlciA9IHRoaXMuZ2V0RHJhd2VyKG5hbWUpO1xuICAgIGlmIChkcmF3ZXIgJiYgdGhpcy5vdmVybGF5KSB7XG4gICAgICBkcmF3ZXIuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7XG4gICAgICB0aGlzLm92ZXJsYXkuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7XG4gICAgICBkb2N1bWVudC5ib2R5LnN0eWxlLm92ZXJmbG93ID0gJ2hpZGRlbic7XG4gICAgICB0aGlzLmFjdGl2ZURyYXdlciA9IG5hbWU7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlIGEgc3BlY2lmaWMgZHJhd2VyXG4gICAqL1xuICBjbG9zZShuYW1lOiBEcmF3ZXJOYW1lKTogdm9pZCB7XG4gICAgY29uc3QgZHJhd2VyID0gdGhpcy5nZXREcmF3ZXIobmFtZSk7XG4gICAgZHJhd2VyPy5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTtcblxuICAgIC8vIE9ubHkgaGlkZSBvdmVybGF5IGlmIG5vIGRyYXdlcnMgYXJlIGFjdGl2ZVxuICAgIGlmICh0aGlzLm92ZXJsYXkgJiYgIWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5hY3RpdmVbY2xhc3MqPVwiZHJhd2VyXCJdJykpIHtcbiAgICAgIHRoaXMub3ZlcmxheS5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTtcbiAgICAgIGRvY3VtZW50LmJvZHkuc3R5bGUub3ZlcmZsb3cgPSAnJztcbiAgICB9XG5cbiAgICBpZiAodGhpcy5hY3RpdmVEcmF3ZXIgPT09IG5hbWUpIHtcbiAgICAgIHRoaXMuYWN0aXZlRHJhd2VyID0gbnVsbDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2xvc2UgYWxsIGRyYXdlcnNcbiAgICovXG4gIGNsb3NlQWxsKCk6IHZvaWQge1xuICAgIFt0aGlzLnByb2ZpbGVEcmF3ZXIsIHRoaXMubm90aWZpY2F0aW9uRHJhd2VyLCB0aGlzLnRvZG9EcmF3ZXIsIHRoaXMubmV3VG9kb0RyYXdlcl1cbiAgICAgIC5mb3JFYWNoKGRyYXdlciA9PiBkcmF3ZXI/LmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpKTtcblxuICAgIHRoaXMub3ZlcmxheT8uY2xhc3NMaXN0LnJlbW92ZSgnYWN0aXZlJyk7XG4gICAgZG9jdW1lbnQuYm9keS5zdHlsZS5vdmVyZmxvdyA9ICcnO1xuICAgIHRoaXMuYWN0aXZlRHJhd2VyID0gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuIHByb2ZpbGUgZHJhd2VyXG4gICAqL1xuICBvcGVuUHJvZmlsZSgpOiB2b2lkIHtcbiAgICB0aGlzLm9wZW4oJ3Byb2ZpbGUnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuIG5vdGlmaWNhdGlvbiBkcmF3ZXJcbiAgICovXG4gIG9wZW5Ob3RpZmljYXRpb24oKTogdm9pZCB7XG4gICAgdGhpcy5vcGVuKCdub3RpZmljYXRpb24nKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuIHRvZG8gZHJhd2VyIChzbGlkZXMgb24gdG9wIG9mIHByb2ZpbGUpXG4gICAqL1xuICBvcGVuVG9kbygpOiB2b2lkIHtcbiAgICB0aGlzLnRvZG9EcmF3ZXI/LmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlIHRvZG8gZHJhd2VyXG4gICAqL1xuICBjbG9zZVRvZG8oKTogdm9pZCB7XG4gICAgdGhpcy50b2RvRHJhd2VyPy5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTtcbiAgICB0aGlzLmNsb3NlTmV3VG9kbygpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW4gbmV3IHRvZG8gZHJhd2VyXG4gICAqL1xuICBvcGVuTmV3VG9kbygpOiB2b2lkIHtcbiAgICB0aGlzLm5ld1RvZG9EcmF3ZXI/LmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlIG5ldyB0b2RvIGRyYXdlclxuICAgKi9cbiAgY2xvc2VOZXdUb2RvKCk6IHZvaWQge1xuICAgIHRoaXMubmV3VG9kb0RyYXdlcj8uY2xhc3NMaXN0LnJlbW92ZSgnYWN0aXZlJyk7XG4gIH1cblxuICAvKipcbiAgICogTWFyayBhbGwgbm90aWZpY2F0aW9ucyBhcyByZWFkXG4gICAqL1xuICBtYXJrQWxsTm90aWZpY2F0aW9uc1JlYWQoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLm5vdGlmaWNhdGlvbkRyYXdlcikgcmV0dXJuO1xuXG4gICAgY29uc3QgdW5yZWFkSXRlbXMgPSB0aGlzLm5vdGlmaWNhdGlvbkRyYXdlci5xdWVyeVNlbGVjdG9yQWxsPEhUTUxFbGVtZW50PihcbiAgICAgICdzd3Atbm90aWZpY2F0aW9uLWl0ZW1bZGF0YS11bnJlYWQ9XCJ0cnVlXCJdJ1xuICAgICk7XG4gICAgdW5yZWFkSXRlbXMuZm9yRWFjaChpdGVtID0+IGl0ZW0ucmVtb3ZlQXR0cmlidXRlKCdkYXRhLXVucmVhZCcpKTtcblxuICAgIGNvbnN0IGJhZGdlID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcjxIVE1MRWxlbWVudD4oJ3N3cC1ub3RpZmljYXRpb24tYmFkZ2UnKTtcbiAgICBpZiAoYmFkZ2UpIHtcbiAgICAgIGJhZGdlLnN0eWxlLmRpc3BsYXkgPSAnbm9uZSc7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBnZXREcmF3ZXIobmFtZTogRHJhd2VyTmFtZSk6IEhUTUxFbGVtZW50IHwgbnVsbCB7XG4gICAgc3dpdGNoIChuYW1lKSB7XG4gICAgICBjYXNlICdwcm9maWxlJzogcmV0dXJuIHRoaXMucHJvZmlsZURyYXdlcjtcbiAgICAgIGNhc2UgJ25vdGlmaWNhdGlvbic6IHJldHVybiB0aGlzLm5vdGlmaWNhdGlvbkRyYXdlcjtcbiAgICAgIGNhc2UgJ3RvZG8nOiByZXR1cm4gdGhpcy50b2RvRHJhd2VyO1xuICAgICAgY2FzZSAnbmV3VG9kbyc6IHJldHVybiB0aGlzLm5ld1RvZG9EcmF3ZXI7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzZXR1cExpc3RlbmVycygpOiB2b2lkIHtcbiAgICAvLyBQcm9maWxlIGRyYXdlciB0cmlnZ2Vyc1xuICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwcm9maWxlVHJpZ2dlcicpXG4gICAgICA/LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdGhpcy5vcGVuUHJvZmlsZSgpKTtcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZHJhd2VyQ2xvc2UnKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMuY2xvc2UoJ3Byb2ZpbGUnKSk7XG5cbiAgICAvLyBOb3RpZmljYXRpb24gZHJhd2VyIHRyaWdnZXJzXG4gICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ25vdGlmaWNhdGlvbnNCdG4nKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMub3Blbk5vdGlmaWNhdGlvbigpKTtcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbm90aWZpY2F0aW9uRHJhd2VyQ2xvc2UnKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMuY2xvc2UoJ25vdGlmaWNhdGlvbicpKTtcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbWFya0FsbFJlYWQnKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMubWFya0FsbE5vdGlmaWNhdGlvbnNSZWFkKCkpO1xuXG4gICAgLy8gVG9kbyBkcmF3ZXIgdHJpZ2dlcnNcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnb3BlblRvZG9EcmF3ZXInKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMub3BlblRvZG8oKSk7XG4gICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3RvZG9EcmF3ZXJCYWNrJylcbiAgICAgID8uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB0aGlzLmNsb3NlVG9kbygpKTtcblxuICAgIC8vIE5ldyB0b2RvIGRyYXdlciB0cmlnZ2Vyc1xuICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhZGRUb2RvQnRuJylcbiAgICAgID8uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB0aGlzLm9wZW5OZXdUb2RvKCkpO1xuICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCduZXdUb2RvRHJhd2VyQmFjaycpXG4gICAgICA/LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdGhpcy5jbG9zZU5ld1RvZG8oKSk7XG4gICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2NhbmNlbE5ld1RvZG8nKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMuY2xvc2VOZXdUb2RvKCkpO1xuICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzYXZlTmV3VG9kbycpXG4gICAgICA/LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdGhpcy5jbG9zZU5ld1RvZG8oKSk7XG5cbiAgICAvLyBPdmVybGF5IGNsaWNrIGNsb3NlcyBhbGxcbiAgICB0aGlzLm92ZXJsYXk/LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdGhpcy5jbG9zZUFsbCgpKTtcblxuICAgIC8vIEVzY2FwZSBrZXkgY2xvc2VzIGFsbFxuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLCAoZTogS2V5Ym9hcmRFdmVudCkgPT4ge1xuICAgICAgaWYgKGUua2V5ID09PSAnRXNjYXBlJykgdGhpcy5jbG9zZUFsbCgpO1xuICAgIH0pO1xuXG4gICAgLy8gVG9kbyBpbnRlcmFjdGlvbnNcbiAgICB0aGlzLnRvZG9EcmF3ZXI/LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKGUpID0+IHRoaXMuaGFuZGxlVG9kb0NsaWNrKGUpKTtcblxuICAgIC8vIFZpc2liaWxpdHkgb3B0aW9uc1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKGUpID0+IHRoaXMuaGFuZGxlVmlzaWJpbGl0eUNsaWNrKGUpKTtcbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlVG9kb0NsaWNrKGU6IEV2ZW50KTogdm9pZCB7XG4gICAgY29uc3QgdGFyZ2V0ID0gZS50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7XG4gICAgY29uc3QgdG9kb0l0ZW0gPSB0YXJnZXQuY2xvc2VzdDxIVE1MRWxlbWVudD4oJ3N3cC10b2RvLWl0ZW0nKTtcbiAgICBjb25zdCBjaGVja2JveCA9IHRhcmdldC5jbG9zZXN0PEhUTUxFbGVtZW50Pignc3dwLXRvZG8tY2hlY2tib3gnKTtcblxuICAgIGlmIChjaGVja2JveCAmJiB0b2RvSXRlbSkge1xuICAgICAgY29uc3QgaXNDb21wbGV0ZWQgPSB0b2RvSXRlbS5kYXRhc2V0LmNvbXBsZXRlZCA9PT0gJ3RydWUnO1xuICAgICAgaWYgKGlzQ29tcGxldGVkKSB7XG4gICAgICAgIHRvZG9JdGVtLnJlbW92ZUF0dHJpYnV0ZSgnZGF0YS1jb21wbGV0ZWQnKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRvZG9JdGVtLmRhdGFzZXQuY29tcGxldGVkID0gJ3RydWUnO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFRvZ2dsZSBzZWN0aW9uIGNvbGxhcHNlXG4gICAgY29uc3Qgc2VjdGlvbkhlYWRlciA9IHRhcmdldC5jbG9zZXN0PEhUTUxFbGVtZW50Pignc3dwLXRvZG8tc2VjdGlvbi1oZWFkZXInKTtcbiAgICBpZiAoc2VjdGlvbkhlYWRlcikge1xuICAgICAgY29uc3Qgc2VjdGlvbiA9IHNlY3Rpb25IZWFkZXIuY2xvc2VzdDxIVE1MRWxlbWVudD4oJ3N3cC10b2RvLXNlY3Rpb24nKTtcbiAgICAgIHNlY3Rpb24/LmNsYXNzTGlzdC50b2dnbGUoJ2NvbGxhcHNlZCcpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlVmlzaWJpbGl0eUNsaWNrKGU6IEV2ZW50KTogdm9pZCB7XG4gICAgY29uc3QgdGFyZ2V0ID0gZS50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7XG4gICAgY29uc3Qgb3B0aW9uID0gdGFyZ2V0LmNsb3Nlc3Q8SFRNTEVsZW1lbnQ+KCdzd3AtdmlzaWJpbGl0eS1vcHRpb24nKTtcblxuICAgIGlmIChvcHRpb24pIHtcbiAgICAgIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGw8SFRNTEVsZW1lbnQ+KCdzd3AtdmlzaWJpbGl0eS1vcHRpb24nKVxuICAgICAgICAuZm9yRWFjaChvID0+IG8uY2xhc3NMaXN0LnJlbW92ZSgnYWN0aXZlJykpO1xuICAgICAgb3B0aW9uLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpO1xuICAgIH1cbiAgfVxufVxuIiwgIi8qKlxuICogVGhlbWUgQ29udHJvbGxlclxuICpcbiAqIEhhbmRsZXMgZGFyay9saWdodCBtb2RlIHN3aXRjaGluZyBhbmQgc3lzdGVtIHByZWZlcmVuY2UgZGV0ZWN0aW9uXG4gKi9cblxuZXhwb3J0IHR5cGUgVGhlbWUgPSAnbGlnaHQnIHwgJ2RhcmsnIHwgJ3N5c3RlbSc7XG5cbmV4cG9ydCBjbGFzcyBUaGVtZUNvbnRyb2xsZXIge1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBTVE9SQUdFX0tFWSA9ICd0aGVtZS1wcmVmZXJlbmNlJztcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgREFSS19DTEFTUyA9ICdkYXJrLW1vZGUnO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBMSUdIVF9DTEFTUyA9ICdsaWdodC1tb2RlJztcblxuICBwcml2YXRlIHJvb3Q6IEhUTUxFbGVtZW50O1xuICBwcml2YXRlIHRoZW1lT3B0aW9uczogTm9kZUxpc3RPZjxIVE1MRWxlbWVudD47XG5cbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5yb290ID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50O1xuICAgIHRoaXMudGhlbWVPcHRpb25zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbDxIVE1MRWxlbWVudD4oJ3N3cC10aGVtZS1vcHRpb24nKTtcblxuICAgIHRoaXMuYXBwbHlUaGVtZSh0aGlzLmN1cnJlbnQpO1xuICAgIHRoaXMudXBkYXRlVUkoKTtcbiAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBjdXJyZW50IHRoZW1lIHNldHRpbmdcbiAgICovXG4gIGdldCBjdXJyZW50KCk6IFRoZW1lIHtcbiAgICBjb25zdCBzdG9yZWQgPSBsb2NhbFN0b3JhZ2UuZ2V0SXRlbShUaGVtZUNvbnRyb2xsZXIuU1RPUkFHRV9LRVkpIGFzIFRoZW1lIHwgbnVsbDtcbiAgICBpZiAoc3RvcmVkID09PSAnZGFyaycgfHwgc3RvcmVkID09PSAnbGlnaHQnIHx8IHN0b3JlZCA9PT0gJ3N5c3RlbScpIHtcbiAgICAgIHJldHVybiBzdG9yZWQ7XG4gICAgfVxuICAgIHJldHVybiAnc3lzdGVtJztcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBkYXJrIG1vZGUgaXMgY3VycmVudGx5IGFjdGl2ZVxuICAgKi9cbiAgZ2V0IGlzRGFyaygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5yb290LmNsYXNzTGlzdC5jb250YWlucyhUaGVtZUNvbnRyb2xsZXIuREFSS19DTEFTUykgfHxcbiAgICAgICh0aGlzLnN5c3RlbVByZWZlcnNEYXJrICYmICF0aGlzLnJvb3QuY2xhc3NMaXN0LmNvbnRhaW5zKFRoZW1lQ29udHJvbGxlci5MSUdIVF9DTEFTUykpO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIHN5c3RlbSBwcmVmZXJzIGRhcmsgbW9kZVxuICAgKi9cbiAgZ2V0IHN5c3RlbVByZWZlcnNEYXJrKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB3aW5kb3cubWF0Y2hNZWRpYSgnKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKScpLm1hdGNoZXM7XG4gIH1cblxuICAvKipcbiAgICogU2V0IHRoZW1lIGFuZCBwZXJzaXN0IHByZWZlcmVuY2VcbiAgICovXG4gIHNldCh0aGVtZTogVGhlbWUpOiB2b2lkIHtcbiAgICBsb2NhbFN0b3JhZ2Uuc2V0SXRlbShUaGVtZUNvbnRyb2xsZXIuU1RPUkFHRV9LRVksIHRoZW1lKTtcbiAgICB0aGlzLmFwcGx5VGhlbWUodGhlbWUpO1xuICAgIHRoaXMudXBkYXRlVUkoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUb2dnbGUgYmV0d2VlbiBsaWdodCBhbmQgZGFyayB0aGVtZXNcbiAgICovXG4gIHRvZ2dsZSgpOiB2b2lkIHtcbiAgICB0aGlzLnNldCh0aGlzLmlzRGFyayA/ICdsaWdodCcgOiAnZGFyaycpO1xuICB9XG5cbiAgcHJpdmF0ZSBhcHBseVRoZW1lKHRoZW1lOiBUaGVtZSk6IHZvaWQge1xuICAgIHRoaXMucm9vdC5jbGFzc0xpc3QucmVtb3ZlKFRoZW1lQ29udHJvbGxlci5EQVJLX0NMQVNTLCBUaGVtZUNvbnRyb2xsZXIuTElHSFRfQ0xBU1MpO1xuXG4gICAgaWYgKHRoZW1lID09PSAnZGFyaycpIHtcbiAgICAgIHRoaXMucm9vdC5jbGFzc0xpc3QuYWRkKFRoZW1lQ29udHJvbGxlci5EQVJLX0NMQVNTKTtcbiAgICB9IGVsc2UgaWYgKHRoZW1lID09PSAnbGlnaHQnKSB7XG4gICAgICB0aGlzLnJvb3QuY2xhc3NMaXN0LmFkZChUaGVtZUNvbnRyb2xsZXIuTElHSFRfQ0xBU1MpO1xuICAgIH1cbiAgICAvLyAnc3lzdGVtJyBsZWF2ZXMgYm90aCBjbGFzc2VzIG9mZiwgbGV0dGluZyBDU1MgbWVkaWEgcXVlcnkgaGFuZGxlIGl0XG4gIH1cblxuICBwcml2YXRlIHVwZGF0ZVVJKCk6IHZvaWQge1xuICAgIGlmICghdGhpcy50aGVtZU9wdGlvbnMpIHJldHVybjtcblxuICAgIGNvbnN0IGRhcmtBY3RpdmUgPSB0aGlzLmlzRGFyaztcblxuICAgIHRoaXMudGhlbWVPcHRpb25zLmZvckVhY2gob3B0aW9uID0+IHtcbiAgICAgIGNvbnN0IHRoZW1lID0gb3B0aW9uLmRhdGFzZXQudGhlbWUgYXMgVGhlbWU7XG4gICAgICBjb25zdCBpc0FjdGl2ZSA9ICh0aGVtZSA9PT0gJ2RhcmsnICYmIGRhcmtBY3RpdmUpIHx8ICh0aGVtZSA9PT0gJ2xpZ2h0JyAmJiAhZGFya0FjdGl2ZSk7XG4gICAgICBvcHRpb24uY2xhc3NMaXN0LnRvZ2dsZSgnYWN0aXZlJywgaXNBY3RpdmUpO1xuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBzZXR1cExpc3RlbmVycygpOiB2b2lkIHtcbiAgICAvLyBUaGVtZSBvcHRpb24gY2xpY2tzXG4gICAgdGhpcy50aGVtZU9wdGlvbnMuZm9yRWFjaChvcHRpb24gPT4ge1xuICAgICAgb3B0aW9uLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKGUpID0+IHRoaXMuaGFuZGxlT3B0aW9uQ2xpY2soZSkpO1xuICAgIH0pO1xuXG4gICAgLy8gU3lzdGVtIHRoZW1lIGNoYW5nZXNcbiAgICB3aW5kb3cubWF0Y2hNZWRpYSgnKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKScpXG4gICAgICAuYWRkRXZlbnRMaXN0ZW5lcignY2hhbmdlJywgKCkgPT4gdGhpcy5oYW5kbGVTeXN0ZW1DaGFuZ2UoKSk7XG4gIH1cblxuICBwcml2YXRlIGhhbmRsZU9wdGlvbkNsaWNrKGU6IEV2ZW50KTogdm9pZCB7XG4gICAgY29uc3QgdGFyZ2V0ID0gZS50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7XG4gICAgY29uc3Qgb3B0aW9uID0gdGFyZ2V0LmNsb3Nlc3Q8SFRNTEVsZW1lbnQ+KCdzd3AtdGhlbWUtb3B0aW9uJyk7XG5cbiAgICBpZiAob3B0aW9uKSB7XG4gICAgICBjb25zdCB0aGVtZSA9IG9wdGlvbi5kYXRhc2V0LnRoZW1lIGFzIFRoZW1lO1xuICAgICAgaWYgKHRoZW1lKSB7XG4gICAgICAgIHRoaXMuc2V0KHRoZW1lKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGhhbmRsZVN5c3RlbUNoYW5nZSgpOiB2b2lkIHtcbiAgICAvLyBPbmx5IHJlYWN0IHRvIHN5c3RlbSBjaGFuZ2VzIGlmIHdlJ3JlIHVzaW5nIHN5c3RlbSBwcmVmZXJlbmNlXG4gICAgaWYgKHRoaXMuY3VycmVudCA9PT0gJ3N5c3RlbScpIHtcbiAgICAgIHRoaXMudXBkYXRlVUkoKTtcbiAgICB9XG4gIH1cbn1cbiIsICIvKipcbiAqIFNlYXJjaCBDb250cm9sbGVyXG4gKlxuICogSGFuZGxlcyBnbG9iYWwgc2VhcmNoIGZ1bmN0aW9uYWxpdHkgYW5kIGtleWJvYXJkIHNob3J0Y3V0c1xuICovXG5cbmV4cG9ydCBjbGFzcyBTZWFyY2hDb250cm9sbGVyIHtcbiAgcHJpdmF0ZSBpbnB1dDogSFRNTElucHV0RWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGNvbnRhaW5lcjogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcblxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLmlucHV0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2dsb2JhbFNlYXJjaCcpIGFzIEhUTUxJbnB1dEVsZW1lbnQgfCBudWxsO1xuICAgIHRoaXMuY29udGFpbmVyID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcjxIVE1MRWxlbWVudD4oJ3N3cC10b3BiYXItc2VhcmNoJyk7XG5cbiAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGN1cnJlbnQgc2VhcmNoIHZhbHVlXG4gICAqL1xuICBnZXQgdmFsdWUoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gdGhpcy5pbnB1dD8udmFsdWUgPz8gJyc7XG4gIH1cblxuICAvKipcbiAgICogU2V0IHNlYXJjaCB2YWx1ZVxuICAgKi9cbiAgc2V0IHZhbHVlKHZhbDogc3RyaW5nKSB7XG4gICAgaWYgKHRoaXMuaW5wdXQpIHtcbiAgICAgIHRoaXMuaW5wdXQudmFsdWUgPSB2YWw7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEZvY3VzIHRoZSBzZWFyY2ggaW5wdXRcbiAgICovXG4gIGZvY3VzKCk6IHZvaWQge1xuICAgIHRoaXMuaW5wdXQ/LmZvY3VzKCk7XG4gIH1cblxuICAvKipcbiAgICogQmx1ciB0aGUgc2VhcmNoIGlucHV0XG4gICAqL1xuICBibHVyKCk6IHZvaWQge1xuICAgIHRoaXMuaW5wdXQ/LmJsdXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhciB0aGUgc2VhcmNoIGlucHV0XG4gICAqL1xuICBjbGVhcigpOiB2b2lkIHtcbiAgICB0aGlzLnZhbHVlID0gJyc7XG4gIH1cblxuICBwcml2YXRlIHNldHVwTGlzdGVuZXJzKCk6IHZvaWQge1xuICAgIC8vIEtleWJvYXJkIHNob3J0Y3V0c1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLCAoZSkgPT4gdGhpcy5oYW5kbGVLZXlib2FyZChlKSk7XG5cbiAgICAvLyBJbnB1dCBoYW5kbGVyc1xuICAgIGlmICh0aGlzLmlucHV0KSB7XG4gICAgICB0aGlzLmlucHV0LmFkZEV2ZW50TGlzdGVuZXIoJ2lucHV0JywgKGUpID0+IHRoaXMuaGFuZGxlSW5wdXQoZSkpO1xuXG4gICAgICAvLyBQcmV2ZW50IGZvcm0gc3VibWlzc2lvbiBpZiB3cmFwcGVkIGluIGZvcm1cbiAgICAgIGNvbnN0IGZvcm0gPSB0aGlzLmlucHV0LmNsb3Nlc3QoJ2Zvcm0nKTtcbiAgICAgIGZvcm0/LmFkZEV2ZW50TGlzdGVuZXIoJ3N1Ym1pdCcsIChlKSA9PiB0aGlzLmhhbmRsZVN1Ym1pdChlKSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBoYW5kbGVLZXlib2FyZChlOiBLZXlib2FyZEV2ZW50KTogdm9pZCB7XG4gICAgLy8gQ21kL0N0cmwgKyBLIHRvIGZvY3VzIHNlYXJjaFxuICAgIGlmICgoZS5tZXRhS2V5IHx8IGUuY3RybEtleSkgJiYgZS5rZXkgPT09ICdrJykge1xuICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgdGhpcy5mb2N1cygpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEVzY2FwZSB0byBibHVyIHNlYXJjaCB3aGVuIGZvY3VzZWRcbiAgICBpZiAoZS5rZXkgPT09ICdFc2NhcGUnICYmIGRvY3VtZW50LmFjdGl2ZUVsZW1lbnQgPT09IHRoaXMuaW5wdXQpIHtcbiAgICAgIHRoaXMuYmx1cigpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlSW5wdXQoZTogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCB0YXJnZXQgPSBlLnRhcmdldCBhcyBIVE1MSW5wdXRFbGVtZW50O1xuICAgIGNvbnN0IHF1ZXJ5ID0gdGFyZ2V0LnZhbHVlLnRyaW0oKTtcblxuICAgIC8vIEVtaXQgY3VzdG9tIGV2ZW50IGZvciBzZWFyY2hcbiAgICBkb2N1bWVudC5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgnYXBwOnNlYXJjaCcsIHtcbiAgICAgIGRldGFpbDogeyBxdWVyeSB9LFxuICAgICAgYnViYmxlczogdHJ1ZVxuICAgIH0pKTtcbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlU3VibWl0KGU6IEV2ZW50KTogdm9pZCB7XG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuXG4gICAgY29uc3QgcXVlcnkgPSB0aGlzLnZhbHVlLnRyaW0oKTtcbiAgICBpZiAoIXF1ZXJ5KSByZXR1cm47XG5cbiAgICAvLyBFbWl0IGN1c3RvbSBldmVudCBmb3Igc2VhcmNoIHN1Ym1pdFxuICAgIGRvY3VtZW50LmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCdhcHA6c2VhcmNoLXN1Ym1pdCcsIHtcbiAgICAgIGRldGFpbDogeyBxdWVyeSB9LFxuICAgICAgYnViYmxlczogdHJ1ZVxuICAgIH0pKTtcbiAgfVxufVxuIiwgIi8qKlxuICogTG9jayBTY3JlZW4gQ29udHJvbGxlclxuICpcbiAqIEhhbmRsZXMgUElOLWJhc2VkIGxvY2sgc2NyZWVuIGZ1bmN0aW9uYWxpdHlcbiAqL1xuXG5pbXBvcnQgeyBEcmF3ZXJDb250cm9sbGVyIH0gZnJvbSAnLi9kcmF3ZXJzJztcblxuZXhwb3J0IGNsYXNzIExvY2tTY3JlZW5Db250cm9sbGVyIHtcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgQ09SUkVDVF9QSU4gPSAnMTIzNCc7IC8vIERlbW8gUElOXG5cbiAgcHJpdmF0ZSBsb2NrU2NyZWVuOiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHBpbklucHV0OiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHBpbktleXBhZDogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBsb2NrVGltZUVsOiBIVE1MRWxlbWVudCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHBpbkRpZ2l0czogTm9kZUxpc3RPZjxIVE1MRWxlbWVudD4gfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBjdXJyZW50UGluID0gJyc7XG4gIHByaXZhdGUgZHJhd2VyczogRHJhd2VyQ29udHJvbGxlciB8IG51bGwgPSBudWxsO1xuXG4gIGNvbnN0cnVjdG9yKGRyYXdlcnM/OiBEcmF3ZXJDb250cm9sbGVyKSB7XG4gICAgdGhpcy5kcmF3ZXJzID0gZHJhd2VycyA/PyBudWxsO1xuICAgIHRoaXMubG9ja1NjcmVlbiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2NrU2NyZWVuJyk7XG4gICAgdGhpcy5waW5JbnB1dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwaW5JbnB1dCcpO1xuICAgIHRoaXMucGluS2V5cGFkID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3BpbktleXBhZCcpO1xuICAgIHRoaXMubG9ja1RpbWVFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2NrVGltZScpO1xuICAgIHRoaXMucGluRGlnaXRzID0gdGhpcy5waW5JbnB1dD8ucXVlcnlTZWxlY3RvckFsbDxIVE1MRWxlbWVudD4oJ3N3cC1waW4tZGlnaXQnKSA/PyBudWxsO1xuXG4gICAgdGhpcy5zZXR1cExpc3RlbmVycygpO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIGxvY2sgc2NyZWVuIGlzIGFjdGl2ZVxuICAgKi9cbiAgZ2V0IGlzQWN0aXZlKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmxvY2tTY3JlZW4/LmNsYXNzTGlzdC5jb250YWlucygnYWN0aXZlJykgPz8gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogU2hvdyB0aGUgbG9jayBzY3JlZW5cbiAgICovXG4gIHNob3coKTogdm9pZCB7XG4gICAgdGhpcy5kcmF3ZXJzPy5jbG9zZUFsbCgpO1xuXG4gICAgaWYgKHRoaXMubG9ja1NjcmVlbikge1xuICAgICAgdGhpcy5sb2NrU2NyZWVuLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpO1xuICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5vdmVyZmxvdyA9ICdoaWRkZW4nO1xuICAgIH1cblxuICAgIHRoaXMuY3VycmVudFBpbiA9ICcnO1xuICAgIHRoaXMudXBkYXRlRGlzcGxheSgpO1xuXG4gICAgLy8gVXBkYXRlIGxvY2sgdGltZVxuICAgIGlmICh0aGlzLmxvY2tUaW1lRWwpIHtcbiAgICAgIHRoaXMubG9ja1RpbWVFbC50ZXh0Q29udGVudCA9IGBMXHUwMEU1c3Qga2wuICR7dGhpcy5mb3JtYXRUaW1lKCl9YDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGlkZSB0aGUgbG9jayBzY3JlZW5cbiAgICovXG4gIGhpZGUoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMubG9ja1NjcmVlbikge1xuICAgICAgdGhpcy5sb2NrU2NyZWVuLmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpO1xuICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5vdmVyZmxvdyA9ICcnO1xuICAgIH1cblxuICAgIHRoaXMuY3VycmVudFBpbiA9ICcnO1xuICAgIHRoaXMudXBkYXRlRGlzcGxheSgpO1xuICB9XG5cbiAgcHJpdmF0ZSBmb3JtYXRUaW1lKCk6IHN0cmluZyB7XG4gICAgY29uc3Qgbm93ID0gbmV3IERhdGUoKTtcbiAgICBjb25zdCBob3VycyA9IG5vdy5nZXRIb3VycygpLnRvU3RyaW5nKCkucGFkU3RhcnQoMiwgJzAnKTtcbiAgICBjb25zdCBtaW51dGVzID0gbm93LmdldE1pbnV0ZXMoKS50b1N0cmluZygpLnBhZFN0YXJ0KDIsICcwJyk7XG4gICAgcmV0dXJuIGAke2hvdXJzfToke21pbnV0ZXN9YDtcbiAgfVxuXG4gIHByaXZhdGUgdXBkYXRlRGlzcGxheSgpOiB2b2lkIHtcbiAgICBpZiAoIXRoaXMucGluRGlnaXRzKSByZXR1cm47XG5cbiAgICB0aGlzLnBpbkRpZ2l0cy5mb3JFYWNoKChkaWdpdCwgaW5kZXgpID0+IHtcbiAgICAgIGRpZ2l0LmNsYXNzTGlzdC5yZW1vdmUoJ2ZpbGxlZCcsICdlcnJvcicpO1xuICAgICAgaWYgKGluZGV4IDwgdGhpcy5jdXJyZW50UGluLmxlbmd0aCkge1xuICAgICAgICBkaWdpdC50ZXh0Q29udGVudCA9ICdcdTIwMjInO1xuICAgICAgICBkaWdpdC5jbGFzc0xpc3QuYWRkKCdmaWxsZWQnKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGRpZ2l0LnRleHRDb250ZW50ID0gJyc7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIHNob3dFcnJvcigpOiB2b2lkIHtcbiAgICBpZiAoIXRoaXMucGluRGlnaXRzKSByZXR1cm47XG5cbiAgICB0aGlzLnBpbkRpZ2l0cy5mb3JFYWNoKGRpZ2l0ID0+IGRpZ2l0LmNsYXNzTGlzdC5hZGQoJ2Vycm9yJykpO1xuXG4gICAgLy8gU2hha2UgYW5pbWF0aW9uXG4gICAgdGhpcy5waW5JbnB1dD8uY2xhc3NMaXN0LmFkZCgnc2hha2UnKTtcblxuICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgdGhpcy5jdXJyZW50UGluID0gJyc7XG4gICAgICB0aGlzLnVwZGF0ZURpc3BsYXkoKTtcbiAgICAgIHRoaXMucGluSW5wdXQ/LmNsYXNzTGlzdC5yZW1vdmUoJ3NoYWtlJyk7XG4gICAgfSwgNTAwKTtcbiAgfVxuXG4gIHByaXZhdGUgdmVyaWZ5KCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmN1cnJlbnRQaW4gPT09IExvY2tTY3JlZW5Db250cm9sbGVyLkNPUlJFQ1RfUElOKSB7XG4gICAgICB0aGlzLmhpZGUoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zaG93RXJyb3IoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFkZERpZ2l0KGRpZ2l0OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5jdXJyZW50UGluLmxlbmd0aCA+PSA0KSByZXR1cm47XG5cbiAgICB0aGlzLmN1cnJlbnRQaW4gKz0gZGlnaXQ7XG4gICAgdGhpcy51cGRhdGVEaXNwbGF5KCk7XG5cbiAgICAvLyBBdXRvLXZlcmlmeSB3aGVuIDQgZGlnaXRzIGVudGVyZWRcbiAgICBpZiAodGhpcy5jdXJyZW50UGluLmxlbmd0aCA9PT0gNCkge1xuICAgICAgc2V0VGltZW91dCgoKSA9PiB0aGlzLnZlcmlmeSgpLCAyMDApO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgcmVtb3ZlRGlnaXQoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY3VycmVudFBpbi5sZW5ndGggPT09IDApIHJldHVybjtcbiAgICB0aGlzLmN1cnJlbnRQaW4gPSB0aGlzLmN1cnJlbnRQaW4uc2xpY2UoMCwgLTEpO1xuICAgIHRoaXMudXBkYXRlRGlzcGxheSgpO1xuICB9XG5cbiAgcHJpdmF0ZSBjbGVhclBpbigpOiB2b2lkIHtcbiAgICB0aGlzLmN1cnJlbnRQaW4gPSAnJztcbiAgICB0aGlzLnVwZGF0ZURpc3BsYXkoKTtcbiAgfVxuXG4gIHByaXZhdGUgc2V0dXBMaXN0ZW5lcnMoKTogdm9pZCB7XG4gICAgLy8gS2V5cGFkIGNsaWNrIGhhbmRsZXJcbiAgICB0aGlzLnBpbktleXBhZD8uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4gdGhpcy5oYW5kbGVLZXlwYWRDbGljayhlKSk7XG5cbiAgICAvLyBLZXlib2FyZCBpbnB1dFxuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLCAoZSkgPT4gdGhpcy5oYW5kbGVLZXlib2FyZChlKSk7XG5cbiAgICAvLyBMb2NrIGJ1dHRvbiBpbiBzaWRlYmFyXG4gICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcjxIVE1MRWxlbWVudD4oJ3N3cC1zaWRlLW1lbnUtYWN0aW9uLmxvY2snKVxuICAgICAgPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHRoaXMuc2hvdygpKTtcbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlS2V5cGFkQ2xpY2soZTogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCB0YXJnZXQgPSBlLnRhcmdldCBhcyBIVE1MRWxlbWVudDtcbiAgICBjb25zdCBrZXkgPSB0YXJnZXQuY2xvc2VzdDxIVE1MRWxlbWVudD4oJ3N3cC1waW4ta2V5Jyk7XG5cbiAgICBpZiAoIWtleSkgcmV0dXJuO1xuXG4gICAgY29uc3QgZGlnaXQgPSBrZXkuZGF0YXNldC5kaWdpdDtcbiAgICBjb25zdCBhY3Rpb24gPSBrZXkuZGF0YXNldC5hY3Rpb247XG5cbiAgICBpZiAoZGlnaXQpIHtcbiAgICAgIHRoaXMuYWRkRGlnaXQoZGlnaXQpO1xuICAgIH0gZWxzZSBpZiAoYWN0aW9uID09PSAnYmFja3NwYWNlJykge1xuICAgICAgdGhpcy5yZW1vdmVEaWdpdCgpO1xuICAgIH0gZWxzZSBpZiAoYWN0aW9uID09PSAnY2xlYXInKSB7XG4gICAgICB0aGlzLmNsZWFyUGluKCk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBoYW5kbGVLZXlib2FyZChlOiBLZXlib2FyZEV2ZW50KTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmlzQWN0aXZlKSByZXR1cm47XG5cbiAgICAvLyBQcmV2ZW50IGRlZmF1bHQgdG8gYXZvaWQgb3RoZXIgaW50ZXJhY3Rpb25zXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuXG4gICAgaWYgKGUua2V5ID49ICcwJyAmJiBlLmtleSA8PSAnOScpIHtcbiAgICAgIHRoaXMuYWRkRGlnaXQoZS5rZXkpO1xuICAgIH0gZWxzZSBpZiAoZS5rZXkgPT09ICdCYWNrc3BhY2UnKSB7XG4gICAgICB0aGlzLnJlbW92ZURpZ2l0KCk7XG4gICAgfSBlbHNlIGlmIChlLmtleSA9PT0gJ0VzY2FwZScpIHtcbiAgICAgIHRoaXMuY2xlYXJQaW4oKTtcbiAgICB9XG4gIH1cbn1cbiIsICIvKipcbiAqIFNhbG9uIE9TIEFwcFxuICpcbiAqIE1haW4gYXBwbGljYXRpb24gY2xhc3MgdGhhdCBvcmNoZXN0cmF0ZXMgYWxsIFVJIGNvbnRyb2xsZXJzXG4gKi9cblxuaW1wb3J0IHsgU2lkZWJhckNvbnRyb2xsZXIgfSBmcm9tICcuL21vZHVsZXMvc2lkZWJhcic7XG5pbXBvcnQgeyBEcmF3ZXJDb250cm9sbGVyIH0gZnJvbSAnLi9tb2R1bGVzL2RyYXdlcnMnO1xuaW1wb3J0IHsgVGhlbWVDb250cm9sbGVyIH0gZnJvbSAnLi9tb2R1bGVzL3RoZW1lJztcbmltcG9ydCB7IFNlYXJjaENvbnRyb2xsZXIgfSBmcm9tICcuL21vZHVsZXMvc2VhcmNoJztcbmltcG9ydCB7IExvY2tTY3JlZW5Db250cm9sbGVyIH0gZnJvbSAnLi9tb2R1bGVzL2xvY2tzY3JlZW4nO1xuXG4vKipcbiAqIE1haW4gYXBwbGljYXRpb24gY2xhc3NcbiAqL1xuZXhwb3J0IGNsYXNzIEFwcCB7XG4gIHJlYWRvbmx5IHNpZGViYXI6IFNpZGViYXJDb250cm9sbGVyO1xuICByZWFkb25seSBkcmF3ZXJzOiBEcmF3ZXJDb250cm9sbGVyO1xuICByZWFkb25seSB0aGVtZTogVGhlbWVDb250cm9sbGVyO1xuICByZWFkb25seSBzZWFyY2g6IFNlYXJjaENvbnRyb2xsZXI7XG4gIHJlYWRvbmx5IGxvY2tTY3JlZW46IExvY2tTY3JlZW5Db250cm9sbGVyO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIC8vIEluaXRpYWxpemUgY29udHJvbGxlcnNcbiAgICB0aGlzLnNpZGViYXIgPSBuZXcgU2lkZWJhckNvbnRyb2xsZXIoKTtcbiAgICB0aGlzLmRyYXdlcnMgPSBuZXcgRHJhd2VyQ29udHJvbGxlcigpO1xuICAgIHRoaXMudGhlbWUgPSBuZXcgVGhlbWVDb250cm9sbGVyKCk7XG4gICAgdGhpcy5zZWFyY2ggPSBuZXcgU2VhcmNoQ29udHJvbGxlcigpO1xuICAgIHRoaXMubG9ja1NjcmVlbiA9IG5ldyBMb2NrU2NyZWVuQ29udHJvbGxlcih0aGlzLmRyYXdlcnMpO1xuICB9XG59XG5cbi8qKlxuICogR2xvYmFsIGFwcCBpbnN0YW5jZVxuICovXG5sZXQgYXBwOiBBcHA7XG5cbi8qKlxuICogSW5pdGlhbGl6ZSB0aGUgYXBwbGljYXRpb25cbiAqL1xuZnVuY3Rpb24gaW5pdCgpOiB2b2lkIHtcbiAgYXBwID0gbmV3IEFwcCgpO1xuXG4gIC8vIEV4cG9zZSB0byB3aW5kb3cgZm9yIGRlYnVnZ2luZ1xuICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAod2luZG93IGFzIHVua25vd24gYXMgeyBhcHA6IEFwcCB9KS5hcHAgPSBhcHA7XG4gIH1cbn1cblxuLy8gV2FpdCBmb3IgRE9NIHJlYWR5XG5pZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnKSB7XG4gIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCBpbml0KTtcbn0gZWxzZSB7XG4gIGluaXQoKTtcbn1cblxuZXhwb3J0IHsgYXBw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 +//# sourceMappingURL=app.js.map diff --git a/PlanTempus.Application/wwwroot/js/app.js.map b/PlanTempus.Application/wwwroot/js/app.js.map new file mode 100644 index 0000000..eeba422 --- /dev/null +++ b/PlanTempus.Application/wwwroot/js/app.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../ts/modules/sidebar.ts", "../ts/modules/drawers.ts", "../ts/modules/theme.ts", "../ts/modules/search.ts", "../ts/modules/lockscreen.ts", "../ts/app.ts"], + "sourcesContent": ["/**\n * Sidebar Controller\n *\n * Handles sidebar collapse/expand and tooltip functionality\n */\n\nexport class SidebarController {\n private menuToggle: HTMLElement | null = null;\n private appLayout: HTMLElement | null = null;\n private menuTooltip: HTMLElement | null = null;\n\n constructor() {\n this.menuToggle = document.getElementById('menuToggle');\n this.appLayout = document.querySelector('swp-app-layout');\n this.menuTooltip = document.getElementById('menuTooltip');\n\n this.setupListeners();\n this.setupTooltips();\n this.restoreState();\n }\n\n /**\n * Check if sidebar is collapsed\n */\n get isCollapsed(): boolean {\n return this.appLayout?.classList.contains('menu-collapsed') ?? false;\n }\n\n /**\n * Toggle sidebar collapsed state\n */\n toggle(): void {\n if (!this.appLayout) return;\n\n this.appLayout.classList.toggle('menu-collapsed');\n localStorage.setItem('sidebar-collapsed', String(this.isCollapsed));\n }\n\n /**\n * Collapse the sidebar\n */\n collapse(): void {\n this.appLayout?.classList.add('menu-collapsed');\n localStorage.setItem('sidebar-collapsed', 'true');\n }\n\n /**\n * Expand the sidebar\n */\n expand(): void {\n this.appLayout?.classList.remove('menu-collapsed');\n localStorage.setItem('sidebar-collapsed', 'false');\n }\n\n private setupListeners(): void {\n this.menuToggle?.addEventListener('click', () => this.toggle());\n }\n\n private setupTooltips(): void {\n if (!this.menuTooltip) return;\n\n const menuItems = document.querySelectorAll('swp-side-menu-item[data-tooltip]');\n\n menuItems.forEach(item => {\n item.addEventListener('mouseenter', () => this.showTooltip(item));\n item.addEventListener('mouseleave', () => this.hideTooltip());\n });\n }\n\n private showTooltip(item: HTMLElement): void {\n if (!this.isCollapsed || !this.menuTooltip) return;\n\n const rect = item.getBoundingClientRect();\n const tooltipText = item.dataset.tooltip;\n\n if (!tooltipText) return;\n\n this.menuTooltip.textContent = tooltipText;\n this.menuTooltip.style.left = `${rect.right + 8}px`;\n this.menuTooltip.style.top = `${rect.top + rect.height / 2}px`;\n this.menuTooltip.style.transform = 'translateY(-50%)';\n this.menuTooltip.showPopover();\n }\n\n private hideTooltip(): void {\n this.menuTooltip?.hidePopover();\n }\n\n private restoreState(): void {\n if (!this.appLayout) return;\n\n if (localStorage.getItem('sidebar-collapsed') === 'true') {\n this.appLayout.classList.add('menu-collapsed');\n }\n }\n}\n", "/**\n * Drawer Controller\n *\n * Handles all drawer functionality including profile, notifications, and todo drawers\n */\n\nexport type DrawerName = 'profile' | 'notification' | 'todo' | 'newTodo';\n\nexport class DrawerController {\n private profileDrawer: HTMLElement | null = null;\n private notificationDrawer: HTMLElement | null = null;\n private todoDrawer: HTMLElement | null = null;\n private newTodoDrawer: HTMLElement | null = null;\n private overlay: HTMLElement | null = null;\n private activeDrawer: DrawerName | null = null;\n private activeGenericDrawer: HTMLElement | null = null;\n\n constructor() {\n this.profileDrawer = document.getElementById('profileDrawer');\n this.notificationDrawer = document.getElementById('notificationDrawer');\n this.todoDrawer = document.getElementById('todoDrawer');\n this.newTodoDrawer = document.getElementById('newTodoDrawer');\n this.overlay = document.getElementById('drawerOverlay');\n\n this.setupListeners();\n this.setupGenericDrawers();\n }\n\n /**\n * Get currently active drawer name\n */\n get active(): DrawerName | null {\n return this.activeDrawer;\n }\n\n /**\n * Open a drawer by name\n */\n open(name: DrawerName): void {\n this.closeAll();\n\n const drawer = this.getDrawer(name);\n if (drawer && this.overlay) {\n drawer.classList.add('active');\n this.overlay.classList.add('active');\n document.body.style.overflow = 'hidden';\n this.activeDrawer = name;\n }\n }\n\n /**\n * Close a specific drawer\n */\n close(name: DrawerName): void {\n const drawer = this.getDrawer(name);\n drawer?.classList.remove('active');\n\n // Only hide overlay if no drawers are active\n if (this.overlay && !document.querySelector('.active[class*=\"drawer\"]')) {\n this.overlay.classList.remove('active');\n document.body.style.overflow = '';\n }\n\n if (this.activeDrawer === name) {\n this.activeDrawer = null;\n }\n }\n\n /**\n * Close all drawers\n */\n closeAll(): void {\n [this.profileDrawer, this.notificationDrawer, this.todoDrawer, this.newTodoDrawer]\n .forEach(drawer => drawer?.classList.remove('active'));\n\n // Close any generic drawers\n this.closeGenericDrawer();\n\n this.overlay?.classList.remove('active');\n document.body.style.overflow = '';\n this.activeDrawer = null;\n }\n\n /**\n * Open a generic drawer by ID\n */\n openGenericDrawer(drawerId: string): void {\n this.closeAll();\n\n const drawer = document.getElementById(drawerId);\n if (drawer && this.overlay) {\n drawer.classList.add('open');\n this.overlay.classList.add('active');\n document.body.style.overflow = 'hidden';\n this.activeGenericDrawer = drawer;\n }\n }\n\n /**\n * Close the currently open generic drawer\n */\n closeGenericDrawer(): void {\n this.activeGenericDrawer?.classList.remove('open');\n this.activeGenericDrawer = null;\n }\n\n /**\n * Open profile drawer\n */\n openProfile(): void {\n this.open('profile');\n }\n\n /**\n * Open notification drawer\n */\n openNotification(): void {\n this.open('notification');\n }\n\n /**\n * Open todo drawer (slides on top of profile)\n */\n openTodo(): void {\n this.todoDrawer?.classList.add('active');\n }\n\n /**\n * Close todo drawer\n */\n closeTodo(): void {\n this.todoDrawer?.classList.remove('active');\n this.closeNewTodo();\n }\n\n /**\n * Open new todo drawer\n */\n openNewTodo(): void {\n this.newTodoDrawer?.classList.add('active');\n }\n\n /**\n * Close new todo drawer\n */\n closeNewTodo(): void {\n this.newTodoDrawer?.classList.remove('active');\n }\n\n /**\n * Mark all notifications as read\n */\n markAllNotificationsRead(): void {\n if (!this.notificationDrawer) return;\n\n const unreadItems = this.notificationDrawer.querySelectorAll(\n 'swp-notification-item[data-unread=\"true\"]'\n );\n unreadItems.forEach(item => item.removeAttribute('data-unread'));\n\n const badge = document.querySelector('swp-notification-badge');\n if (badge) {\n badge.style.display = 'none';\n }\n }\n\n private getDrawer(name: DrawerName): HTMLElement | null {\n switch (name) {\n case 'profile': return this.profileDrawer;\n case 'notification': return this.notificationDrawer;\n case 'todo': return this.todoDrawer;\n case 'newTodo': return this.newTodoDrawer;\n }\n }\n\n private setupListeners(): void {\n // Profile drawer triggers\n document.getElementById('profileTrigger')\n ?.addEventListener('click', () => this.openProfile());\n document.getElementById('drawerClose')\n ?.addEventListener('click', () => this.close('profile'));\n\n // Notification drawer triggers\n document.getElementById('notificationsBtn')\n ?.addEventListener('click', () => this.openNotification());\n document.getElementById('notificationDrawerClose')\n ?.addEventListener('click', () => this.close('notification'));\n document.getElementById('markAllRead')\n ?.addEventListener('click', () => this.markAllNotificationsRead());\n\n // Todo drawer triggers\n document.getElementById('openTodoDrawer')\n ?.addEventListener('click', () => this.openTodo());\n document.getElementById('todoDrawerBack')\n ?.addEventListener('click', () => this.closeTodo());\n\n // New todo drawer triggers\n document.getElementById('addTodoBtn')\n ?.addEventListener('click', () => this.openNewTodo());\n document.getElementById('newTodoDrawerBack')\n ?.addEventListener('click', () => this.closeNewTodo());\n document.getElementById('cancelNewTodo')\n ?.addEventListener('click', () => this.closeNewTodo());\n document.getElementById('saveNewTodo')\n ?.addEventListener('click', () => this.closeNewTodo());\n\n // Overlay click closes all\n this.overlay?.addEventListener('click', () => this.closeAll());\n\n // Escape key closes all\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') this.closeAll();\n });\n\n // Todo interactions\n this.todoDrawer?.addEventListener('click', (e) => this.handleTodoClick(e));\n\n // Visibility options\n document.addEventListener('click', (e) => this.handleVisibilityClick(e));\n }\n\n private handleTodoClick(e: Event): void {\n const target = e.target as HTMLElement;\n const todoItem = target.closest('swp-todo-item');\n const checkbox = target.closest('swp-todo-checkbox');\n\n if (checkbox && todoItem) {\n const isCompleted = todoItem.dataset.completed === 'true';\n if (isCompleted) {\n todoItem.removeAttribute('data-completed');\n } else {\n todoItem.dataset.completed = 'true';\n }\n }\n\n // Toggle section collapse\n const sectionHeader = target.closest('swp-todo-section-header');\n if (sectionHeader) {\n const section = sectionHeader.closest('swp-todo-section');\n section?.classList.toggle('collapsed');\n }\n }\n\n private handleVisibilityClick(e: Event): void {\n const target = e.target as HTMLElement;\n const option = target.closest('swp-visibility-option');\n\n if (option) {\n document.querySelectorAll('swp-visibility-option')\n .forEach(o => o.classList.remove('active'));\n option.classList.add('active');\n }\n }\n\n /**\n * Setup generic drawer triggers and close buttons\n * Uses data-drawer-trigger=\"drawer-id\" and data-drawer-close attributes\n */\n private setupGenericDrawers(): void {\n // Handle drawer triggers\n document.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement;\n const trigger = target.closest('[data-drawer-trigger]');\n\n if (trigger) {\n const drawerId = trigger.dataset.drawerTrigger;\n if (drawerId) {\n this.openGenericDrawer(drawerId);\n }\n }\n });\n\n // Handle drawer close buttons\n document.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement;\n const closeBtn = target.closest('[data-drawer-close]');\n\n if (closeBtn) {\n this.closeGenericDrawer();\n this.overlay?.classList.remove('active');\n document.body.style.overflow = '';\n }\n });\n }\n}\n", "/**\n * Theme Controller\n *\n * Handles dark/light mode switching and system preference detection\n */\n\nexport type Theme = 'light' | 'dark' | 'system';\n\nexport class ThemeController {\n private static readonly STORAGE_KEY = 'theme-preference';\n private static readonly DARK_CLASS = 'dark-mode';\n private static readonly LIGHT_CLASS = 'light-mode';\n\n private root: HTMLElement;\n private themeOptions: NodeListOf;\n\n constructor() {\n this.root = document.documentElement;\n this.themeOptions = document.querySelectorAll('swp-theme-option');\n\n this.applyTheme(this.current);\n this.updateUI();\n this.setupListeners();\n }\n\n /**\n * Get the current theme setting\n */\n get current(): Theme {\n const stored = localStorage.getItem(ThemeController.STORAGE_KEY) as Theme | null;\n if (stored === 'dark' || stored === 'light' || stored === 'system') {\n return stored;\n }\n return 'system';\n }\n\n /**\n * Check if dark mode is currently active\n */\n get isDark(): boolean {\n return this.root.classList.contains(ThemeController.DARK_CLASS) ||\n (this.systemPrefersDark && !this.root.classList.contains(ThemeController.LIGHT_CLASS));\n }\n\n /**\n * Check if system prefers dark mode\n */\n get systemPrefersDark(): boolean {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n\n /**\n * Set theme and persist preference\n */\n set(theme: Theme): void {\n localStorage.setItem(ThemeController.STORAGE_KEY, theme);\n this.applyTheme(theme);\n this.updateUI();\n }\n\n /**\n * Toggle between light and dark themes\n */\n toggle(): void {\n this.set(this.isDark ? 'light' : 'dark');\n }\n\n private applyTheme(theme: Theme): void {\n this.root.classList.remove(ThemeController.DARK_CLASS, ThemeController.LIGHT_CLASS);\n\n if (theme === 'dark') {\n this.root.classList.add(ThemeController.DARK_CLASS);\n } else if (theme === 'light') {\n this.root.classList.add(ThemeController.LIGHT_CLASS);\n }\n // 'system' leaves both classes off, letting CSS media query handle it\n }\n\n private updateUI(): void {\n if (!this.themeOptions) return;\n\n const darkActive = this.isDark;\n\n this.themeOptions.forEach(option => {\n const theme = option.dataset.theme as Theme;\n const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);\n option.classList.toggle('active', isActive);\n });\n }\n\n private setupListeners(): void {\n // Theme option clicks\n this.themeOptions.forEach(option => {\n option.addEventListener('click', (e) => this.handleOptionClick(e));\n });\n\n // System theme changes\n window.matchMedia('(prefers-color-scheme: dark)')\n .addEventListener('change', () => this.handleSystemChange());\n }\n\n private handleOptionClick(e: Event): void {\n const target = e.target as HTMLElement;\n const option = target.closest('swp-theme-option');\n\n if (option) {\n const theme = option.dataset.theme as Theme;\n if (theme) {\n this.set(theme);\n }\n }\n }\n\n private handleSystemChange(): void {\n // Only react to system changes if we're using system preference\n if (this.current === 'system') {\n this.updateUI();\n }\n }\n}\n", "/**\n * Search Controller\n *\n * Handles global search functionality and keyboard shortcuts\n */\n\nexport class SearchController {\n private input: HTMLInputElement | null = null;\n private container: HTMLElement | null = null;\n\n constructor() {\n this.input = document.getElementById('globalSearch') as HTMLInputElement | null;\n this.container = document.querySelector('swp-topbar-search');\n\n this.setupListeners();\n }\n\n /**\n * Get current search value\n */\n get value(): string {\n return this.input?.value ?? '';\n }\n\n /**\n * Set search value\n */\n set value(val: string) {\n if (this.input) {\n this.input.value = val;\n }\n }\n\n /**\n * Focus the search input\n */\n focus(): void {\n this.input?.focus();\n }\n\n /**\n * Blur the search input\n */\n blur(): void {\n this.input?.blur();\n }\n\n /**\n * Clear the search input\n */\n clear(): void {\n this.value = '';\n }\n\n private setupListeners(): void {\n // Keyboard shortcuts\n document.addEventListener('keydown', (e) => this.handleKeyboard(e));\n\n // Input handlers\n if (this.input) {\n this.input.addEventListener('input', (e) => this.handleInput(e));\n\n // Prevent form submission if wrapped in form\n const form = this.input.closest('form');\n form?.addEventListener('submit', (e) => this.handleSubmit(e));\n }\n }\n\n private handleKeyboard(e: KeyboardEvent): void {\n // Cmd/Ctrl + K to focus search\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n this.focus();\n return;\n }\n\n // Escape to blur search when focused\n if (e.key === 'Escape' && document.activeElement === this.input) {\n this.blur();\n }\n }\n\n private handleInput(e: Event): void {\n const target = e.target as HTMLInputElement;\n const query = target.value.trim();\n\n // Emit custom event for search\n document.dispatchEvent(new CustomEvent('app:search', {\n detail: { query },\n bubbles: true\n }));\n }\n\n private handleSubmit(e: Event): void {\n e.preventDefault();\n\n const query = this.value.trim();\n if (!query) return;\n\n // Emit custom event for search submit\n document.dispatchEvent(new CustomEvent('app:search-submit', {\n detail: { query },\n bubbles: true\n }));\n }\n}\n", "/**\n * Lock Screen Controller\n *\n * Handles PIN-based lock screen functionality\n */\n\nimport { DrawerController } from './drawers';\n\nexport class LockScreenController {\n private static readonly CORRECT_PIN = '1234'; // Demo PIN\n\n private lockScreen: HTMLElement | null = null;\n private pinInput: HTMLElement | null = null;\n private pinKeypad: HTMLElement | null = null;\n private lockTimeEl: HTMLElement | null = null;\n private pinDigits: NodeListOf | null = null;\n private currentPin = '';\n private drawers: DrawerController | null = null;\n\n constructor(drawers?: DrawerController) {\n this.drawers = drawers ?? null;\n this.lockScreen = document.getElementById('lockScreen');\n this.pinInput = document.getElementById('pinInput');\n this.pinKeypad = document.getElementById('pinKeypad');\n this.lockTimeEl = document.getElementById('lockTime');\n this.pinDigits = this.pinInput?.querySelectorAll('swp-pin-digit') ?? null;\n\n this.setupListeners();\n }\n\n /**\n * Check if lock screen is active\n */\n get isActive(): boolean {\n return this.lockScreen?.classList.contains('active') ?? false;\n }\n\n /**\n * Show the lock screen\n */\n show(): void {\n this.drawers?.closeAll();\n\n if (this.lockScreen) {\n this.lockScreen.classList.add('active');\n document.body.style.overflow = 'hidden';\n }\n\n this.currentPin = '';\n this.updateDisplay();\n\n // Update lock time\n if (this.lockTimeEl) {\n this.lockTimeEl.textContent = `L\u00E5st kl. ${this.formatTime()}`;\n }\n }\n\n /**\n * Hide the lock screen\n */\n hide(): void {\n if (this.lockScreen) {\n this.lockScreen.classList.remove('active');\n document.body.style.overflow = '';\n }\n\n this.currentPin = '';\n this.updateDisplay();\n }\n\n private formatTime(): string {\n const now = new Date();\n const hours = now.getHours().toString().padStart(2, '0');\n const minutes = now.getMinutes().toString().padStart(2, '0');\n return `${hours}:${minutes}`;\n }\n\n private updateDisplay(): void {\n if (!this.pinDigits) return;\n\n this.pinDigits.forEach((digit, index) => {\n digit.classList.remove('filled', 'error');\n if (index < this.currentPin.length) {\n digit.textContent = '\u2022';\n digit.classList.add('filled');\n } else {\n digit.textContent = '';\n }\n });\n }\n\n private showError(): void {\n if (!this.pinDigits) return;\n\n this.pinDigits.forEach(digit => digit.classList.add('error'));\n\n // Shake animation\n this.pinInput?.classList.add('shake');\n\n setTimeout(() => {\n this.currentPin = '';\n this.updateDisplay();\n this.pinInput?.classList.remove('shake');\n }, 500);\n }\n\n private verify(): void {\n if (this.currentPin === LockScreenController.CORRECT_PIN) {\n this.hide();\n } else {\n this.showError();\n }\n }\n\n private addDigit(digit: string): void {\n if (this.currentPin.length >= 4) return;\n\n this.currentPin += digit;\n this.updateDisplay();\n\n // Auto-verify when 4 digits entered\n if (this.currentPin.length === 4) {\n setTimeout(() => this.verify(), 200);\n }\n }\n\n private removeDigit(): void {\n if (this.currentPin.length === 0) return;\n this.currentPin = this.currentPin.slice(0, -1);\n this.updateDisplay();\n }\n\n private clearPin(): void {\n this.currentPin = '';\n this.updateDisplay();\n }\n\n private setupListeners(): void {\n // Keypad click handler\n this.pinKeypad?.addEventListener('click', (e) => this.handleKeypadClick(e));\n\n // Keyboard input\n document.addEventListener('keydown', (e) => this.handleKeyboard(e));\n\n // Lock button in sidebar\n document.querySelector('swp-side-menu-action.lock')\n ?.addEventListener('click', () => this.show());\n }\n\n private handleKeypadClick(e: Event): void {\n const target = e.target as HTMLElement;\n const key = target.closest('swp-pin-key');\n\n if (!key) return;\n\n const digit = key.dataset.digit;\n const action = key.dataset.action;\n\n if (digit) {\n this.addDigit(digit);\n } else if (action === 'backspace') {\n this.removeDigit();\n } else if (action === 'clear') {\n this.clearPin();\n }\n }\n\n private handleKeyboard(e: KeyboardEvent): void {\n if (!this.isActive) return;\n\n // Prevent default to avoid other interactions\n e.preventDefault();\n\n if (e.key >= '0' && e.key <= '9') {\n this.addDigit(e.key);\n } else if (e.key === 'Backspace') {\n this.removeDigit();\n } else if (e.key === 'Escape') {\n this.clearPin();\n }\n }\n}\n", "/**\n * Salon OS App\n *\n * Main application class that orchestrates all UI controllers\n */\n\nimport { SidebarController } from './modules/sidebar';\nimport { DrawerController } from './modules/drawers';\nimport { ThemeController } from './modules/theme';\nimport { SearchController } from './modules/search';\nimport { LockScreenController } from './modules/lockscreen';\n\n/**\n * Main application class\n */\nexport class App {\n readonly sidebar: SidebarController;\n readonly drawers: DrawerController;\n readonly theme: ThemeController;\n readonly search: SearchController;\n readonly lockScreen: LockScreenController;\n\n constructor() {\n // Initialize controllers\n this.sidebar = new SidebarController();\n this.drawers = new DrawerController();\n this.theme = new ThemeController();\n this.search = new SearchController();\n this.lockScreen = new LockScreenController(this.drawers);\n }\n}\n\n/**\n * Global app instance\n */\nlet app: App;\n\n/**\n * Initialize the application\n */\nfunction init(): void {\n app = new App();\n\n // Expose to window for debugging\n if (typeof window !== 'undefined') {\n (window as unknown as { app: App }).app = app;\n }\n}\n\n// Wait for DOM ready\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n} else {\n init();\n}\n\nexport { app };\nexport default App;\n"], + "mappings": "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", + "names": [] +} diff --git a/PlanTempus.Application/wwwroot/ts/modules/drawers.ts b/PlanTempus.Application/wwwroot/ts/modules/drawers.ts index 7ee36ad..fdc2d57 100644 --- a/PlanTempus.Application/wwwroot/ts/modules/drawers.ts +++ b/PlanTempus.Application/wwwroot/ts/modules/drawers.ts @@ -13,6 +13,7 @@ export class DrawerController { private newTodoDrawer: HTMLElement | null = null; private overlay: HTMLElement | null = null; private activeDrawer: DrawerName | null = null; + private activeGenericDrawer: HTMLElement | null = null; constructor() { this.profileDrawer = document.getElementById('profileDrawer'); @@ -22,6 +23,7 @@ export class DrawerController { this.overlay = document.getElementById('drawerOverlay'); this.setupListeners(); + this.setupGenericDrawers(); } /** @@ -71,11 +73,37 @@ export class DrawerController { [this.profileDrawer, this.notificationDrawer, this.todoDrawer, this.newTodoDrawer] .forEach(drawer => drawer?.classList.remove('active')); + // Close any generic drawers + this.closeGenericDrawer(); + this.overlay?.classList.remove('active'); document.body.style.overflow = ''; this.activeDrawer = null; } + /** + * Open a generic drawer by ID + */ + openGenericDrawer(drawerId: string): void { + this.closeAll(); + + const drawer = document.getElementById(drawerId); + if (drawer && this.overlay) { + drawer.classList.add('open'); + this.overlay.classList.add('active'); + document.body.style.overflow = 'hidden'; + this.activeGenericDrawer = drawer; + } + } + + /** + * Close the currently open generic drawer + */ + closeGenericDrawer(): void { + this.activeGenericDrawer?.classList.remove('open'); + this.activeGenericDrawer = null; + } + /** * Open profile drawer */ @@ -223,4 +251,35 @@ export class DrawerController { option.classList.add('active'); } } + + /** + * Setup generic drawer triggers and close buttons + * Uses data-drawer-trigger="drawer-id" and data-drawer-close attributes + */ + private setupGenericDrawers(): void { + // Handle drawer triggers + document.addEventListener('click', (e: Event) => { + const target = e.target as HTMLElement; + const trigger = target.closest('[data-drawer-trigger]'); + + if (trigger) { + const drawerId = trigger.dataset.drawerTrigger; + if (drawerId) { + this.openGenericDrawer(drawerId); + } + } + }); + + // Handle drawer close buttons + document.addEventListener('click', (e: Event) => { + const target = e.target as HTMLElement; + const closeBtn = target.closest('[data-drawer-close]'); + + if (closeBtn) { + this.closeGenericDrawer(); + this.overlay?.classList.remove('active'); + document.body.style.overflow = ''; + } + }); + } }