From 33c338345ee005364edc5536dc413985c4cc577e Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Sun, 18 Jan 2026 22:50:33 +0100 Subject: [PATCH] Adds comprehensive Settings page with multiple configuration tabs Introduces a new Settings page with configurable modules: - General company information - Calendar and booking settings - Billing and payment configurations - Tracking and analytics integrations Implements modular ViewComponents for each settings section Enhances user experience with toggle switches and detailed configuration options --- .../Features/Menu/Services/MockMenuService.cs | 2 +- .../Components/ServiceTable/Default.cshtml | 4 +- .../Components/SettingsBilling/Default.cshtml | 141 ++++ .../SettingsBillingViewComponent.cs | 15 + .../Components/SettingsBooking/Default.cshtml | 97 +++ .../SettingsBookingViewComponent.cs | 15 + .../SettingsCalendar/Default.cshtml | 227 ++++++ .../SettingsCalendarViewComponent.cs | 15 + .../Components/SettingsGeneral/Default.cshtml | 141 ++++ .../SettingsGeneralViewComponent.cs | 15 + .../Components/SettingsHours/Default.cshtml | 92 +++ .../SettingsHoursViewComponent.cs | 15 + .../Components/SettingsModules/Default.cshtml | 604 +++++++++++++++ .../SettingsModulesViewComponent.cs | 15 + .../SettingsPayments/Default.cshtml | 141 ++++ .../SettingsPaymentsViewComponent.cs | 15 + .../SettingsReminders/Default.cshtml | 114 +++ .../SettingsRemindersViewComponent.cs | 15 + .../SettingsTracking/Default.cshtml | 215 ++++++ .../SettingsTrackingViewComponent.cs | 15 + .../Features/Settings/Pages/Index.cshtml | 110 +++ .../Features/Settings/Pages/Index.cshtml.cs | 10 + .../Features/_Shared/Pages/_Layout.cshtml | 1 + PlanTempus.Application/wwwroot/css/cash.css | 1 - .../wwwroot/css/components.css | 149 +++- .../wwwroot/css/controls.css | 1 + PlanTempus.Application/wwwroot/css/page.css | 3 +- .../wwwroot/css/services.css | 16 +- .../wwwroot/css/settings.css | 692 ++++++++++++++++++ PlanTempus.Application/wwwroot/ts/app.ts | 3 + .../wwwroot/ts/modules/tracking.ts | 289 ++++++++ 31 files changed, 3167 insertions(+), 21 deletions(-) create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsBilling/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsBilling/SettingsBillingViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsBooking/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsBooking/SettingsBookingViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsCalendar/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsCalendar/SettingsCalendarViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsGeneral/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsGeneral/SettingsGeneralViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsHours/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsHours/SettingsHoursViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsModules/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsModules/SettingsModulesViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsPayments/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsPayments/SettingsPaymentsViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsReminders/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsReminders/SettingsRemindersViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsTracking/Default.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Components/SettingsTracking/SettingsTrackingViewComponent.cs create mode 100644 PlanTempus.Application/Features/Settings/Pages/Index.cshtml create mode 100644 PlanTempus.Application/Features/Settings/Pages/Index.cshtml.cs create mode 100644 PlanTempus.Application/wwwroot/css/settings.css create mode 100644 PlanTempus.Application/wwwroot/ts/modules/tracking.ts diff --git a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs index edce578..4c52443 100644 --- a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs +++ b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs @@ -176,7 +176,7 @@ public class MockMenuService : IMenuService Id = "settings", Label = "Indstillinger", Icon = "ph-gear", - Url = "/poc-indstillinger.html", + Url = "/indstillinger", MinimumRole = UserRole.Admin, SortOrder = 1 }, diff --git a/PlanTempus.Application/Features/Services/Components/ServiceTable/Default.cshtml b/PlanTempus.Application/Features/Services/Components/ServiceTable/Default.cshtml index bcfaa30..6c354f0 100644 --- a/PlanTempus.Application/Features/Services/Components/ServiceTable/Default.cshtml +++ b/PlanTempus.Application/Features/Services/Components/ServiceTable/Default.cshtml @@ -1,6 +1,6 @@ @model PlanTempus.Application.Features.Services.Components.ServiceTableViewModel - + @@ -15,7 +15,7 @@ @Model.CreateButtonText - + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsBilling/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsBilling/Default.cshtml new file mode 100644 index 0000000..59afa6d --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsBilling/Default.cshtml @@ -0,0 +1,141 @@ + + +

Kvittering printes efter betaling i butikken. Faktura sendes til kunder der betaler senere.

+
+ + + + + + + + Virksomhedsoplysninger + + + + + + Firmanavn (juridisk) + KARINA KNUDSEN® ApS + + + CVR-nummer + 12345678 + + + Adresse + Amager Strandvej 22f, 2300 København S + + + + + + + + + + + Bankoplysninger (kun faktura) + + + + + + Registreringsnr. + 1234 + + + Kontonummer + 12345678 + + + IBAN (valgfri) + DK00 0000 0000 0000 00 + + + MobilePay (valgfri) + 12345678 + + + Betalingsbetingelser + + + + + + + + + + + + + + Vis på print + + + + + Vis CVR-nummer + + Ja + Nej + + + + Vis telefonnummer + + Ja + Nej + + + + Vis website + + Ja + Nej + + + + Vis logo + + Ja + Nej + + + + + + + + + + + Tekster + + + + + + Header-tekst + Tak for dit besøg! + + +
+ Footer-tekst + +
+
+ Faktura-tekst + +
+
+
+
diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsBilling/SettingsBillingViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsBilling/SettingsBillingViewComponent.cs new file mode 100644 index 0000000..aa46f7c --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsBilling/SettingsBillingViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the invoice and receipt settings tab. +/// Handles invoice templates, receipt settings, and numbering. +/// +public class SettingsBillingViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsBooking/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsBooking/Default.cshtml new file mode 100644 index 0000000..678cd62 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsBooking/Default.cshtml @@ -0,0 +1,97 @@ + + +

Din booking-side er tilgængelig for kunder. Del linket på din hjemmeside, sociale medier eller i din email-signatur.

+
+ + + + + + + + Booking-indstillinger + + + + + + Aktivér online booking + Tillad kunder at booke tider online + + + Ja + Nej + + + + + Tillad online aflysning + Kunder kan selv aflyse deres booking + + + Ja + Nej + + + + + + + + Book frem i tiden + + + + + + Minimum tid før booking + + + + + + Aflysningsfrist + + + + + + + + + + + + + + Booking URL + + + + + + + + + + + + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsBooking/SettingsBookingViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsBooking/SettingsBookingViewComponent.cs new file mode 100644 index 0000000..7aebe8d --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsBooking/SettingsBookingViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the online booking settings tab. +/// Handles booking rules, customer restrictions, and checkout options. +/// +public class SettingsBookingViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/Default.cshtml new file mode 100644 index 0000000..0e64c45 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/Default.cshtml @@ -0,0 +1,227 @@ + + + + + + + Kalenderindstillinger + + + + + + Standard visning + + + + + + Første dag i ugen + + + + + + Tidsinterval + + + + + + Vis weekender + + Ja + Nej + + + + Arbejdstid fra + + + + + + Arbejdstid til + + + + + + + + + + + + + + Helligdage & lukkedage + + + + Tilføj lukkedag + + + + + + Luk automatisk på helligdage + Salonen lukkes automatisk på danske helligdage + + + Ja + Nej + + + + + + + + + 5. april 2026 + Påskedag + + Helligdag + + + + + + + 1. maj 2026 + Store bededag + + Helligdag + + + + + + + 14. maj 2026 + Kristi Himmelfart + + Helligdag + + + + + + + 24. maj 2026 + Pinsedag + + Helligdag + + + + + + + 25. maj 2026 + 2. pinsedag + + Helligdag + + + + + + + 5. juni 2026 + Grundlovsdag + + Helligdag + + + + + + + 24. december 2026 + Juleaften + + Helligdag + + + + + + + 25. december 2026 + 1. juledag + + Helligdag + + + + + + + 26. december 2026 + 2. juledag + + Helligdag + + + + + + + 1. januar 2027 + Nytårsdag + + Helligdag + + + + + + + 14. - 28. juli 2026 + Sommerferie + + Lukkedag + + + + + + + 24. - 26. december 2026 + Julelukning + + Lukkedag + + + + + + + + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/SettingsCalendarViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/SettingsCalendarViewComponent.cs new file mode 100644 index 0000000..a449e26 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsCalendar/SettingsCalendarViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the calendar settings tab. +/// Handles calendar view options, resources, and intervals. +/// +public class SettingsCalendarViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/Default.cshtml new file mode 100644 index 0000000..0e9f032 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/Default.cshtml @@ -0,0 +1,141 @@ + + + + + + + Grundlæggende oplysninger + + + + + + Virksomhedsnavn + KARINA KNUDSEN® + + + CVR-nummer + 12345678 + + + Adresse + Hovedgaden 123 + + + Postnummer + 2100 + + + By + København Ø + + + Telefon + 70 20 30 40 + + + Email + info@salonbeauty.dk + + + Website (valgfri) + https://salonbeauty.dk + + + + + + + + + + + Åbningstider + + + + + + Mandag + + Ja + Nej + + + + til + + + + + Tirsdag + + Ja + Nej + + + + til + + + + + Onsdag + + Ja + Nej + + + + til + + + + + Torsdag + + Ja + Nej + + + + til + + + + + Fredag + + Ja + Nej + + + + til + + + + + Lørdag + + Ja + Nej + + + + til + + + + + Søndag + + Ja + Nej + + Lukket + + + + + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/SettingsGeneralViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/SettingsGeneralViewComponent.cs new file mode 100644 index 0000000..5cb8db0 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsGeneral/SettingsGeneralViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the general company settings tab. +/// Handles company name, CVR, address, contact info. +/// +public class SettingsGeneralViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsHours/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsHours/Default.cshtml new file mode 100644 index 0000000..b5c4b07 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsHours/Default.cshtml @@ -0,0 +1,92 @@ + + + + + Åbningstider + + + + + + Mandag + + Ja + Nej + + + + til + + + + + Tirsdag + + Ja + Nej + + + + til + + + + + Onsdag + + Ja + Nej + + + + til + + + + + Torsdag + + Ja + Nej + + + + til + + + + + Fredag + + Ja + Nej + + + + til + + + + + Lørdag + + Ja + Nej + + + + til + + + + + Søndag + + Ja + Nej + + Lukket + + + + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsHours/SettingsHoursViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsHours/SettingsHoursViewComponent.cs new file mode 100644 index 0000000..a09a857 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsHours/SettingsHoursViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the opening hours settings tab. +/// Handles weekly opening hours with toggles. +/// +public class SettingsHoursViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsModules/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsModules/Default.cshtml new file mode 100644 index 0000000..9cdc418 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsModules/Default.cshtml @@ -0,0 +1,604 @@ + + + +
+ Tilpas dit system +

Her kan du slå moduler til og fra efter behov. Nogle moduler er inkluderet i dit abonnement, mens andre kan tilkøbes. Moduler der er slået fra, vises ikke i menuen og påvirker ikke dit system.

+
+
+ + + + + + + Løn & Økonomi + + + + + + + + + + + + Lønberegning + Beregn løn, overtid, provision og ferie automatisk. Grundmodul for løneksport til eksterne systemer. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Intect + Eksporter direkte til Intect lønsystem i StandardMapping-format. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Proløn + Eksporter direkte til Proløn lønsystem. + + + + Til + Fra + + + + + + Kommer + + + + + + + + + + + + Danløn + Eksporter direkte til Danløn lønsystem. + + + + Til + Fra + + + + + + Kommer + + + + + + + + + + + + Salary.dk + Eksporter direkte til Salary.dk lønsystem. + + + + Til + Fra + + + + + + Kommer + + + + + + + + + + + + Zenegy + Eksporter direkte til Zenegy lønsystem. Automatisk overførsel af timer og provision. + + + + Til + Fra + + + + + + Kommer + + + + + + + + + + + + AI & Analyse + + Nyt + + + + + + + + + + + AI Dashboard + Din personlige AI-assistent på dashboardet. Få daglige indsigter, anbefalinger og svar på spørgsmål om din forretning. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + AI Virksomhedsanalyse + Dybdegående AI-analyse af timer vs. omsætning, belægningsgrad og identificer mønstre og vækstmuligheder. + + + + Til + Fra + + + + + + +49 kr/md + Beta + + Læs mere + + + + + + + + + + + AI Kundeanalyse + Forstå dine kunders adfærd og forbliv proaktiv. AI'en analyserer bookingmønstre, forudser hvornår kunder har brug for en tid, og identificerer kunder der er ved at falde fra. + + + + Til + Fra + + + + + + + Booking-prediktion baseret på historik + + + + Churn-detektion – se hvem der er ved at falde fra + + + + Service-præference analyse pr. kunde + + + + Automatisk personlig beskedgenerering + + + + + -34% + Færre tabte kunder + + + +18% + Genbookinger + + + 3.8x + ROI på kampagner + + + + + +79 kr/md + Beta + + Prøv gratis i 14 dage + + + + + + + + + + + AI Kalenderoptimering + Maksimer din kalenderudnyttelse og reducer tabt omsætning. AI'en foreslår optimale tider til kunder og identificerer huller der kan fyldes. + + + + Til + Fra + + + + + + + Smart tidsforslag ved booking + + + + Automatisk hul-identifikation + + + + SMS-tilbud til flytning af tider + + + + Dashboard med optimeringsscore + + + + + 52x + ROI i gennemsnit + + + 15% + Færre tomme slots + + + 312k + Ekstra oms./år* + + + + + +99 kr/md + Ny + + Prøv gratis i 14 dage + + + + + + + + + + + Tillægsmoduler + + + + + + + + + + + + Online Booking + Lad kunder booke tider online via din egen bookingside. Integreres med kalender og påmindelser. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Gavekort + Sælg og administrer digitale gavekort. Kunderne kan købe online eller i butikken, og indløse ved betaling. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Kasseafstemning + Daglig kasseopgørelse og afstemning. Hold styr på kontanter, kort og andre betalingsmetoder. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Stregkodescanner + Scan EAN-koder og få AI-genererede produktbeskrivelser automatisk. Opret nye produkter på sekunder. + + + + Til + Fra + + + + + + Inkluderet + AI + + Indstillinger + + + + + + + + + + + Website Builder + Byg din salons hjemmeside med drag-and-drop blokke. Vælg mellem færdige designs, tilpas farver og fonte, og integrer din booking. + + + + Til + Fra + + + + + + +149 kr/md + Ny + + Åbn Builder + + + + + + + + + + + HR & Dokumenter + Komplet medarbejderstyring: Kontrakter, certificeringer, kurser, ferie-saldo, sygefravær og barsel. Upload dokumenter og få påmindelser om udløbsdatoer. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + Integrationer + + + + + + + + + + + + Sygeforsikring "danmark" + Gør det nemt for dine kunder at få tilskud. Send automatisk kvitteringer til "danmark" så kunderne får deres penge tilbage uden selv at løfte en finger. + + + + Til + Fra + + + + + + + Automatisk indsendelse af kvitteringer + + + + Direkte integration via API + + + + Kunden får tilskud uden besvær + + + + Øget kundetilfredshed + + + + + 2.1M + Medlemmer i DK + + + 100% + Automatiseret + + + 0 kr + Ekstra gebyr + + + + + Inkluderet + + Opsæt integration + + + + + + + + + + + Kalenderintegration + Få dine bookinger synkroniseret til din private kalender automatisk. Se alle aftaler samlet ét sted. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsModules/SettingsModulesViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsModules/SettingsModulesViewComponent.cs new file mode 100644 index 0000000..2f5aa66 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsModules/SettingsModulesViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the modules settings tab. +/// Handles feature module toggles and add-on management. +/// +public class SettingsModulesViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsPayments/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsPayments/Default.cshtml new file mode 100644 index 0000000..6694493 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsPayments/Default.cshtml @@ -0,0 +1,141 @@ + + + + + + + Betalingsmetoder i butik + + + + Vælg hvilke betalingsmetoder dine kunder kan bruge ved checkout i butikken. + + + Kontant + Modtag kontant betaling + + + Ja + Nej + + + + + Dankort / Visa / Mastercard + Betalingskort via terminal + + + Ja + Nej + + + + + MobilePay + Betaling via MobilePay + + + Ja + Nej + + + + + Gavekort + Indløs gavekort som betaling + + + Ja + Nej + + + + + Faktura + Send faktura til kunden + + + Ja + Nej + + + + + + +
+ + + + + + Online betaling + + + + + + Modtag betaling ved online booking + Kunder betaler når de booker online + + + Ja + Nej + + + + + + + + + + + + + Gebyr & tillæg + + + + + + Vis kortgebyr til kunden + Vis kortgebyr som separat linje på kvittering + + + Ja + Nej + + + + +
+
diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsPayments/SettingsPaymentsViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsPayments/SettingsPaymentsViewComponent.cs new file mode 100644 index 0000000..066b64b --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsPayments/SettingsPaymentsViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the payments settings tab. +/// Handles in-store payment methods, online payments, and fees. +/// +public class SettingsPaymentsViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsReminders/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsReminders/Default.cshtml new file mode 100644 index 0000000..23f2804 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsReminders/Default.cshtml @@ -0,0 +1,114 @@ + + + + + + Email + + +
+ Bekræftelse + Sendes når en aftale er booket +
+ + Ja + Nej + +
+ +
+ Påmindelse + Sendes inden aftalen +
+ + Ja + Nej + +
+ +
+ Aflysning + Sendes når en aftale er aflyst +
+ + Ja + Nej + +
+ +
+ Flytning + Sendes når en aftale er flyttet +
+ + Ja + Nej + +
+ + Email-påmindelser er gratis. +
+ + + + + + SMS + + +
+ Bekræftelse online (0,30 kr.) + Sendes ved online booking +
+ + Ja + Nej + +
+ +
+ Bekræftelse manuelt (0,30 kr.) + Ved oprettelse i kalender +
+ + Ja + Nej + +
+ +
+ Påmindelse (Premium) + Sendes inden aftalen +
+ + Ja + Nej + +
+ + + + + + Påmindelse sendes + + + + + + Sendes mellem + + + + + + + +
+
diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsReminders/SettingsRemindersViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsReminders/SettingsRemindersViewComponent.cs new file mode 100644 index 0000000..05919ac --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsReminders/SettingsRemindersViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the reminders settings tab. +/// Handles SMS and email reminder configuration. +/// +public class SettingsRemindersViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsTracking/Default.cshtml b/PlanTempus.Application/Features/Settings/Components/SettingsTracking/Default.cshtml new file mode 100644 index 0000000..7fe9bda --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsTracking/Default.cshtml @@ -0,0 +1,215 @@ + + + +
+ Tracking & Analytics +

Tilslut dine foretrukne analyse- og marketingværktøjer for at måle trafik og konverteringer på din online booking. Aktiver kun de tjenester du bruger.

+
+
+ + + + + + + Meta Pixel (Facebook) + + + Ja + Nej + + + + + + Pixel ID + 123456789012345 + + + + + Bruges til Facebook og Instagram annoncering og remarketing + + + + + + + + + + Google Analytics (GA4) + + + Ja + Nej + + + + + + Measurement ID + G-ABC123XYZ + + + + + Google Analytics 4 til website trafik og brugeradfærd + + + + + + + + + + Google Tag Manager + + + Ja + Nej + + + + + + Container ID + GTM-XXXXXXX + + + + + Central styring af alle tracking-tags + + + + + + + + + + Plausible Analytics + + + Ja + Nej + + + + + + Domæne + minside.dk + + + + + Privacy-venlig analytics uden cookies - GDPR compliant + + + + + + + + + + Fathom Analytics + + + Ja + Nej + + + + + + Site ID + ABCDEFGH + + + + + Privacy-venlig analytics uden cookies - GDPR compliant + + + + + + + + + + Matomo + + + Ja + Nej + + + + + + Server URL + https://matomo.minside.dk + + + Site ID + 1 + + + + + Self-hosted analytics - fuld kontrol over dine data + + + + + + + + + + Brugerdefinerede Scripts + + + + + + + Scripts i <head> + + + + + + + Scripts før </body> + + + + + + + + + + + + Genereret Kode + + + + Kopier + + + + + + Denne kode indsættes automatisk i <head> på din online booking side + +
<!-- Ingen aktive tracking-koder -->
+
+
diff --git a/PlanTempus.Application/Features/Settings/Components/SettingsTracking/SettingsTrackingViewComponent.cs b/PlanTempus.Application/Features/Settings/Components/SettingsTracking/SettingsTrackingViewComponent.cs new file mode 100644 index 0000000..2911ec6 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Components/SettingsTracking/SettingsTrackingViewComponent.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Settings.Components; + +/// +/// ViewComponent for the tracking settings tab. +/// Handles analytics and tracking code configuration. +/// +public class SettingsTrackingViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + return View(); + } +} diff --git a/PlanTempus.Application/Features/Settings/Pages/Index.cshtml b/PlanTempus.Application/Features/Settings/Pages/Index.cshtml new file mode 100644 index 0000000..1ddb516 --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Pages/Index.cshtml @@ -0,0 +1,110 @@ +@page "/indstillinger" +@using PlanTempus.Application.Features.Settings.Pages +@model PlanTempus.Application.Features.Settings.Pages.IndexModel +@{ + ViewData["Title"] = "Indstillinger"; +} + + + + + + + + Indstillinger + + + + + + + + + Virksomhed + + + + Kalender + + + + Online Booking + + + + Faktura & Kvittering + + + + Påmindelser + + + + Betalinger + + + + Moduler + + + + Tracking + + + + + + + + @await Component.InvokeAsync("SettingsGeneral") + + + + + + + @await Component.InvokeAsync("SettingsCalendar") + + + + + + + @await Component.InvokeAsync("SettingsBooking") + + + + + + + @await Component.InvokeAsync("SettingsBilling") + + + + + + + @await Component.InvokeAsync("SettingsReminders") + + + + + + + @await Component.InvokeAsync("SettingsPayments") + + + + + + + @await Component.InvokeAsync("SettingsModules") + + + + + + + @await Component.InvokeAsync("SettingsTracking") + + diff --git a/PlanTempus.Application/Features/Settings/Pages/Index.cshtml.cs b/PlanTempus.Application/Features/Settings/Pages/Index.cshtml.cs new file mode 100644 index 0000000..f8c0b8f --- /dev/null +++ b/PlanTempus.Application/Features/Settings/Pages/Index.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PlanTempus.Application.Features.Settings.Pages; + +public class IndexModel : PageModel +{ + public void OnGet() + { + } +} diff --git a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml index 137d7d6..ce9974f 100644 --- a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml +++ b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml @@ -33,6 +33,7 @@ + @await RenderSectionAsync("Styles", required: false) diff --git a/PlanTempus.Application/wwwroot/css/cash.css b/PlanTempus.Application/wwwroot/css/cash.css index b425c71..67ce439 100644 --- a/PlanTempus.Application/wwwroot/css/cash.css +++ b/PlanTempus.Application/wwwroot/css/cash.css @@ -114,7 +114,6 @@ swp-cash-table { justify-content: space-between; padding: var(--spacing-7) var(--spacing-10); background: var(--color-background-alt); - border-top: 1px solid var(--color-border); font-size: var(--font-size-md); color: var(--color-text-secondary); } diff --git a/PlanTempus.Application/wwwroot/css/components.css b/PlanTempus.Application/wwwroot/css/components.css index 62c7b89..01e40ec 100644 --- a/PlanTempus.Application/wwwroot/css/components.css +++ b/PlanTempus.Application/wwwroot/css/components.css @@ -10,11 +10,17 @@ * - swp-plan-card (subscription plan cards) * - swp-avatar (user avatars with size variants) * - swp-icon-btn (icon-only buttons) - * - swp-card (content cards with section-label/footer) + * - swp-card (content cards with header/title/section-label/footer) + * - swp-card-header (card header with title and actions) + * - swp-card-title (icon + text title) * - swp-section-label (card section headers) * - swp-section-header (section header with action link) * - swp-section-action (action link in section header) * - swp-add-button (dashed border add button) + * - swp-info-box (callout box with icon) + * - swp-section-intro (description text) + * - swp-section-divider (horizontal line) + * - swp-two-column-grid (responsive 2-column layout) * - BASE PATTERNS: table, list-item, icon-container (Grid+Subgrid reusable patterns) */ @@ -544,7 +550,32 @@ swp-card { border: 1px solid var(--color-border); border-radius: var(--border-radius-lg); padding: var(--card-padding); - margin-top: var(--section-gap); +} + +/* Card header - flex row with title and actions */ +swp-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-5) var(--card-padding); + border-bottom: 1px solid var(--color-border); + margin: calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding)) var(--spacing-5); + border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0; +} + +/* Card title - icon + text */ +swp-card-title { + display: flex; + align-items: center; + gap: var(--spacing-3); + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); + color: var(--color-text); + + & i { + font-size: 20px; + color: var(--color-teal); + } } /* Section label - simple card header */ @@ -1443,3 +1474,117 @@ swp-see-all { text-decoration: underline; } } + +/* =========================================== + INFO BOX (Callout) + =========================================== */ +swp-info-box { + display: flex; + align-items: flex-start; + gap: var(--spacing-4); + padding: var(--spacing-4) var(--spacing-5); + margin-top: var(--section-gap); + background: var(--bg-blue-subtle); + border: 1px solid var(--bg-blue-border); + border-radius: var(--radius-md); + + & i { + font-size: 20px; + color: var(--color-blue); + flex-shrink: 0; + } + + & p { + font-size: var(--font-size-sm); + color: var(--color-text); + margin: 0; + line-height: 1.5; + } + + &.success { + background: var(--bg-green-subtle); + border-color: var(--bg-green-border); + + & i { + color: var(--color-green); + } + } + + &.warning { + background: var(--bg-amber-subtle); + border-color: var(--bg-amber-border); + + & i { + color: var(--color-amber); + } + } +} + +/* =========================================== + SECTION INTRO (Description text) + =========================================== */ +swp-section-intro { + display: block; + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + margin-bottom: var(--spacing-5); + line-height: 1.5; +} + +/* =========================================== + SECTION DIVIDER (Horizontal line) + =========================================== */ +swp-section-divider { + display: block; + height: 1px; + background: var(--color-border); + margin: var(--spacing-5) 0; +} + +/* =========================================== + ACTION BAR (Filter/toolbar with white bg) + =========================================== */ +swp-action-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-4); + padding: var(--spacing-4) var(--spacing-5); + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + margin-bottom: var(--section-gap); +} + +swp-action-bar swp-btn-group { + display: flex; + gap: var(--spacing-3); +} + +/* =========================================== + TWO COLUMN GRID + =========================================== */ +swp-two-column-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--card-gap); + align-items: start; + + /* Full width cards span both columns */ + & > .full-width { + grid-column: 1 / -1; + } +} + +@media (max-width: 900px) { + swp-two-column-grid { + grid-template-columns: 1fr; + } +} + +/* Stacked cards in a grid column */ +.stacked-cards { + display: flex; + flex-direction: column; + gap: var(--card-gap); +} diff --git a/PlanTempus.Application/wwwroot/css/controls.css b/PlanTempus.Application/wwwroot/css/controls.css index dee789b..31da40a 100644 --- a/PlanTempus.Application/wwwroot/css/controls.css +++ b/PlanTempus.Application/wwwroot/css/controls.css @@ -34,6 +34,7 @@ swp-toggle-description { swp-toggle-slider { display: inline-flex; width: fit-content; + flex-shrink: 0; background: var(--color-background); border-radius: 6px; border: 1px solid var(--color-border); diff --git a/PlanTempus.Application/wwwroot/css/page.css b/PlanTempus.Application/wwwroot/css/page.css index a9eb551..066912b 100644 --- a/PlanTempus.Application/wwwroot/css/page.css +++ b/PlanTempus.Application/wwwroot/css/page.css @@ -10,7 +10,8 @@ swp-page-container { display: block; max-width: var(--page-max-width); - margin: 0 auto; + margin-top: var(--section-gap); + margin-bottom: var(--section-gap); padding-left: var(--page-padding); padding-right: var(--page-padding); } diff --git a/PlanTempus.Application/wwwroot/css/services.css b/PlanTempus.Application/wwwroot/css/services.css index 9106073..388e033 100644 --- a/PlanTempus.Application/wwwroot/css/services.css +++ b/PlanTempus.Application/wwwroot/css/services.css @@ -3,6 +3,7 @@ * * Feature-specific styling only. * Reuses: + * - swp-action-bar (components.css) * - swp-stat-card (stats.css) * - swp-data-table (components.css) * - swp-sticky-header, swp-page-container (page.css) @@ -16,21 +17,8 @@ */ /* =========================================== - SERVICES HEADER (Search + Button) + SEARCH INPUT =========================================== */ - -swp-services-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--section-gap); -} - -swp-services-header swp-btn-group { - display: flex; - gap: var(--spacing-3); -} - swp-search-input { display: flex; align-items: center; diff --git a/PlanTempus.Application/wwwroot/css/settings.css b/PlanTempus.Application/wwwroot/css/settings.css new file mode 100644 index 0000000..7e6437d --- /dev/null +++ b/PlanTempus.Application/wwwroot/css/settings.css @@ -0,0 +1,692 @@ +/** + * Settings - Indstillinger page components + * + * Settings-specific styling only. + * Reuses: swp-card, swp-card-header, swp-card-title, swp-edit-section, + * swp-toggle-row, swp-info-box, swp-section-intro (components.css) + */ + +/* =========================================== + HOURS TABLE (Åbningstider) + =========================================== */ +swp-hours-table { + display: flex; + flex-direction: column; +} + +swp-hours-row { + display: grid; + grid-template-columns: 100px 80px 1fr; + gap: var(--spacing-5); + align-items: center; + padding: var(--spacing-4) 0; + border-bottom: 1px solid var(--color-border); + + &:last-child { + border-bottom: none; + } +} + +swp-hours-day { + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-hours-time { + display: flex; + align-items: center; + gap: var(--spacing-3); + + & span { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + } + + & input[type="time"] { + width: 100px; + padding: var(--spacing-2) var(--spacing-3); + font-size: var(--font-size-sm); + font-family: var(--font-mono); + background: var(--color-background-alt); + border: 1px solid transparent; + border-radius: var(--radius-sm); + color: var(--color-text); + transition: all var(--transition-fast); + + &:hover { + background: var(--color-background); + } + + &:focus { + outline: none; + background: var(--color-surface); + border-color: var(--color-teal); + } + } +} + +swp-hours-closed { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + font-style: italic; +} + +/* =========================================== + CARD NOTE (used in Påmindelser) + =========================================== */ +swp-card-note { + display: block; + margin-top: var(--spacing-4); + padding-top: var(--spacing-4); + border-top: 1px solid var(--color-border); + font-size: var(--font-size-xs); + color: var(--color-text-secondary); + font-style: italic; +} + +swp-time-range { + display: flex; + align-items: center; + gap: var(--spacing-3); + + & span { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + } + + & input[type="time"] { + width: 100px; + padding: var(--spacing-2) var(--spacing-3); + font-size: var(--font-size-sm); + font-family: var(--font-mono); + background: var(--color-background-alt); + border: 1px solid transparent; + border-radius: var(--radius-sm); + color: var(--color-text); + transition: all var(--transition-fast); + + &:hover { + background: var(--color-background); + } + + &:focus { + outline: none; + background: var(--color-surface); + border-color: var(--color-teal); + } + } +} + +/* =========================================== + URL FIELD (Booking URL) + =========================================== */ +swp-url-field { + display: flex; + align-items: center; + gap: var(--spacing-2); + background: var(--color-background-alt); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + overflow: hidden; + + & input { + flex: 1; + padding: var(--spacing-3) var(--spacing-4); + font-family: var(--font-mono); + font-size: var(--font-size-sm); + color: var(--color-text); + background: transparent; + border: none; + outline: none; + } +} + +swp-url-copy { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--color-background); + border-left: 1px solid var(--color-border); + cursor: pointer; + transition: all var(--transition-fast); + + & i { + font-size: 18px; + color: var(--color-text-secondary); + } + + &:hover { + background: var(--color-teal); + + & i { + color: white; + } + } +} + +/* =========================================== + CLOSED DAYS LIST (Helligdage & Lukkedage) + =========================================== */ +swp-closed-days-list { + display: flex; + flex-direction: column; +} + +swp-closed-day-item { + display: grid; + grid-template-columns: 1fr auto auto; + gap: var(--spacing-4); + align-items: center; + padding: var(--spacing-3) 0; + border-bottom: 1px solid var(--color-border); + + &:last-child { + border-bottom: none; + } +} + +swp-closed-day-info { + display: flex; + flex-direction: column; + gap: var(--spacing-1); +} + +swp-closed-day-date { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-closed-day-name { + font-size: var(--font-size-xs); + color: var(--color-text-secondary); +} + +swp-closed-day-badge { + display: inline-flex; + align-items: center; + padding: var(--spacing-1) var(--spacing-3); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + border-radius: var(--radius-sm); + background: var(--color-background); + color: var(--color-text-secondary); + + &.holiday { + background: var(--bg-red-strong); + color: var(--color-red); + } + + &.custom { + background: var(--bg-blue-strong); + color: var(--color-blue); + } +} + +/* =========================================== + MODULES BADGE (section header) + =========================================== */ +swp-modules-badge { + display: inline-flex; + align-items: center; + padding: var(--spacing-1) var(--spacing-3); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-semibold); + border-radius: var(--radius-sm); + background: var(--bg-purple-strong); + color: var(--color-purple); +} + +/* =========================================== + MODULES SECTION + =========================================== */ +swp-modules-section { + margin-bottom: var(--section-gap); +} + +swp-modules-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-5); +} + +swp-modules-title { + display: flex; + align-items: center; + gap: var(--spacing-3); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); + color: var(--color-text); + + & i { + font-size: 22px; + color: var(--color-teal); + } +} + +swp-modules-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--card-gap); +} + +@media (max-width: 900px) { + swp-modules-grid { + grid-template-columns: 1fr; + } +} + +/* =========================================== + MODULE CARD + =========================================== */ +swp-module-card { + display: block; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all var(--transition-fast); + + &:hover { + border-color: var(--color-teal); + box-shadow: var(--shadow-sm); + } + + &.full-width { + grid-column: 1 / -1; + } +} + +swp-module-header { + display: flex; + align-items: center; + gap: var(--spacing-5); + padding: var(--card-padding); +} + +swp-module-icon { + width: 48px; + height: 48px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + & i { + font-size: 24px; + } + + &.teal { + background: var(--bg-teal-strong); + color: var(--color-teal); + } + + &.purple { + background: var(--bg-purple-strong); + color: var(--color-purple); + } + + &.blue { + background: var(--bg-blue-strong); + color: var(--color-blue); + } + + &.amber { + background: var(--bg-amber-strong); + color: var(--color-amber); + } + + &.green { + background: var(--bg-green-strong); + color: var(--color-green); + } +} + +swp-module-info { + flex: 1; + min-width: 0; +} + +swp-module-title { + display: block; + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); + color: var(--color-text); + margin-bottom: var(--spacing-1); +} + +swp-module-desc { + display: block; + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + line-height: 1.4; +} + +swp-module-toggle { + flex-shrink: 0; +} + +swp-module-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-4) var(--card-padding); + background: var(--color-background-alt); + border-top: 1px solid var(--color-border); +} + +swp-module-tags { + display: flex; + align-items: center; + gap: var(--spacing-2); +} + +swp-module-tag { + display: inline-flex; + align-items: center; + padding: var(--spacing-1) var(--spacing-3); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + border-radius: var(--radius-sm); + background: var(--color-background); + color: var(--color-text-secondary); + + &.included { + background: var(--bg-green-strong); + color: var(--color-green); + } + + &.price { + background: var(--bg-blue-strong); + color: var(--color-blue); + } + + &.new { + background: var(--bg-purple-strong); + color: var(--color-purple); + } + + &.coming { + background: var(--bg-amber-strong); + color: var(--color-amber); + } +} + +/* Featured module card */ +swp-module-card.featured { + border: 2px solid var(--color-green); + background: linear-gradient(135deg, var(--bg-green-subtle) 0%, var(--color-surface) 100%); + + &:hover { + border-color: var(--color-green); + box-shadow: 0 4px 16px rgba(67, 160, 71, 0.15); + } + + &.purple { + border-color: var(--color-purple); + background: linear-gradient(135deg, var(--bg-purple-subtle) 0%, var(--color-surface) 100%); + + &:hover { + border-color: var(--color-purple); + box-shadow: 0 4px 16px rgba(139, 92, 246, 0.15); + } + } +} + +swp-module-features { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-2) var(--spacing-5); + padding: 0 var(--card-padding) var(--spacing-5); +} + +swp-module-feature { + display: flex; + align-items: center; + gap: var(--spacing-2); + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + + & i { + font-size: 16px; + color: var(--color-green); + } + + &.purple i { + color: var(--color-purple); + } +} + +swp-module-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--spacing-4); + padding: var(--spacing-5) var(--card-padding); + background: var(--bg-green-subtle); + border-top: 1px solid var(--bg-green-border); + border-bottom: 1px solid var(--bg-green-border); + + &.purple { + background: var(--bg-purple-subtle); + border-color: var(--bg-purple-border); + + swp-module-stat-value { + color: var(--color-purple); + } + } +} + +swp-module-stat { + text-align: center; +} + +swp-module-stat-value { + display: block; + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + font-family: var(--font-mono); + color: var(--color-green); +} + +swp-module-stat-label { + display: block; + font-size: var(--font-size-xs); + color: var(--color-text-secondary); + margin-top: var(--spacing-1); +} + +/* =========================================== + TRACKING COMPONENTS + =========================================== */ +swp-tracking-hint { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 10px 12px; + background: color-mix(in srgb, var(--color-blue) 8%, transparent); + border-radius: 6px; + font-size: 12px; + color: var(--color-text-secondary); + + & i { + font-size: 16px; + color: var(--color-blue); + } + + &.privacy { + background: color-mix(in srgb, var(--color-green) 8%, transparent); + + & i { + color: var(--color-green); + } + } +} + +/* Modules tab max-width */ +swp-tab-content[data-tab="modules"] swp-page-container { + max-width: 900px; + margin: 0 auto; +} + +/* Tracking tab uses 2-column grid layout */ +swp-tab-content[data-tab="tracking"] swp-page-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + max-width: 900px; + margin: 0 auto; +} + +@media (max-width: 900px) { + swp-tab-content[data-tab="tracking"] swp-page-container { + grid-template-columns: 1fr; + } +} + +/* Full-width in tracking grid */ +swp-tab-content[data-tab="tracking"] .full-width { + grid-column: 1 / -1; +} + +/* Tracking card variant */ +.tracking-card { + margin-bottom: 0; + + & swp-card-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + &[data-tracker] swp-card-content { + transition: opacity 200ms ease; + } + + &[data-tracker].disabled swp-card-content { + opacity: 0.5; + pointer-events: none; + } +} + +/* Script section for custom tracking */ +swp-script-section { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; + } +} + +swp-script-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 500; + color: var(--color-text); + + & i { + font-size: 16px; + color: var(--color-text-secondary); + } +} + +.script-textarea { + width: 100%; + min-height: 100px; + padding: 12px; + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.5; + color: var(--color-text); + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 6px; + resize: vertical; + + &::placeholder { + color: var(--color-text-secondary); + } + + &:focus { + outline: none; + border-color: var(--color-teal); + } +} + +/* =========================================== + CODE PREVIEW CARD (Tracking) + =========================================== */ +.code-preview-card { + grid-column: 1 / -1; + border: 2px dashed var(--color-teal); + background: color-mix(in srgb, var(--color-teal) 3%, var(--color-surface)); + + & swp-card-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + & swp-card-title { + color: var(--color-teal); + } +} + +swp-code-info { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 12px; + color: var(--color-text-secondary); + + & i { + font-size: 16px; + color: var(--color-teal); + } +} + +.code-preview { + margin: 0; + padding: 16px; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 6px; + overflow-x: auto; + max-height: 400px; + overflow-y: auto; + + & code { + font-family: var(--font-mono); + font-size: 11px; + line-height: 1.6; + color: var(--color-text); + white-space: pre; + } + + & .comment { + color: var(--color-green); + } + + & .tag { + color: var(--color-blue); + } + + & .attr { + color: var(--color-purple); + } + + & .string { + color: var(--color-teal); + } +} diff --git a/PlanTempus.Application/wwwroot/ts/app.ts b/PlanTempus.Application/wwwroot/ts/app.ts index 0c66ab5..ca3a933 100644 --- a/PlanTempus.Application/wwwroot/ts/app.ts +++ b/PlanTempus.Application/wwwroot/ts/app.ts @@ -13,6 +13,7 @@ import { CashController } from './modules/cash'; import { EmployeesController } from './modules/employees'; import { ControlsController } from './modules/controls'; import { ServicesController } from './modules/services'; +import { TrackingController } from './modules/tracking'; /** * Main application class @@ -27,6 +28,7 @@ export class App { readonly employees: EmployeesController; readonly controls: ControlsController; readonly services: ServicesController; + readonly tracking: TrackingController; constructor() { // Initialize controllers @@ -39,6 +41,7 @@ export class App { this.employees = new EmployeesController(); this.controls = new ControlsController(); this.services = new ServicesController(); + this.tracking = new TrackingController(); } } diff --git a/PlanTempus.Application/wwwroot/ts/modules/tracking.ts b/PlanTempus.Application/wwwroot/ts/modules/tracking.ts new file mode 100644 index 0000000..f074aa7 --- /dev/null +++ b/PlanTempus.Application/wwwroot/ts/modules/tracking.ts @@ -0,0 +1,289 @@ +/** + * Tracking Controller + * + * Handles toggle switches, input changes, and code preview generation + * for the Settings > Tracking page. + * + * Note: Toggle slider click behavior is handled by ControlsController. + * This controller listens for 'toggle' events to update card states and preview. + */ + +export class TrackingController { + constructor() { + this.setupToggleListeners(); + this.setupInputListeners(); + this.setupCopyButton(); + this.initializeCardStates(); + this.updatePreview(); + } + + /** + * Listen for toggle events from ControlsController + */ + private setupToggleListeners(): void { + document.querySelectorAll('.tracking-card swp-toggle-slider').forEach(slider => { + slider.addEventListener('toggle', (e) => { + const detail = (e as CustomEvent).detail; + const card = slider.closest('.tracking-card'); + + if (card) { + if (detail.value === 'no') { + card.classList.add('disabled'); + } else { + card.classList.remove('disabled'); + } + } + + this.updatePreview(); + }); + }); + } + + /** + * Setup input listeners for contenteditable fields and textareas + */ + private setupInputListeners(): void { + // Contenteditable fields + const editableIds = [ + 'metaPixelId', 'ga4Id', 'gtmId', 'plausibleDomain', + 'fathomSiteId', 'matomoUrl', 'matomoSiteId' + ]; + + editableIds.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.addEventListener('input', () => this.updatePreview()); + } + }); + + // Textareas + const textareaIds = ['customHeadScripts', 'customBodyScripts']; + textareaIds.forEach(id => { + const element = document.getElementById(id) as HTMLTextAreaElement; + if (element) { + element.addEventListener('input', () => this.updatePreview()); + } + }); + } + + /** + * Setup copy button functionality + */ + private setupCopyButton(): void { + const copyBtn = document.getElementById('copyTrackingCode'); + if (!copyBtn) return; + + copyBtn.addEventListener('click', () => this.copyCode()); + } + + /** + * Initialize card states based on toggle values + */ + private initializeCardStates(): void { + document.querySelectorAll('.tracking-card[data-tracker]').forEach(card => { + const toggle = card.querySelector('swp-toggle-slider'); + if (toggle && toggle.dataset.value === 'no') { + card.classList.add('disabled'); + } + }); + } + + /** + * Check if a tracker is enabled + */ + private isTrackerEnabled(trackerName: string): boolean { + const card = document.querySelector(`[data-tracker="${trackerName}"]`); + if (!card) return false; + const toggle = card.querySelector('swp-toggle-slider'); + return toggle?.dataset.value === 'yes'; + } + + /** + * Get text content from an element by ID + */ + private getValue(id: string): string { + const element = document.getElementById(id); + return element?.textContent?.trim() || ''; + } + + /** + * Get value from textarea by ID + */ + private getTextareaValue(id: string): string { + const element = document.getElementById(id) as HTMLTextAreaElement; + return element?.value?.trim() || ''; + } + + /** + * Escape HTML entities + */ + private escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>'); + } + + /** + * Update the code preview based on active trackers + */ + private updatePreview(): void { + const preview = document.getElementById('trackingCodePreview'); + if (!preview) return; + + let code = ''; + + // Meta Pixel + if (this.isTrackerEnabled('meta')) { + const pixelId = this.getValue('metaPixelId'); + if (pixelId) { + code += `<!-- Meta Pixel --> +<script> + !function(f,b,e,v,n,t,s) + {if(f.fbq)return;n=f.fbq=function(){n.callMethod? + n.callMethod.apply(n,arguments):n.queue.push(arguments)}; + if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; + n.queue=[];t=b.createElement(e);t.async=!0; + t.src=v;s=b.getElementsByTagName(e)[0]; + s.parentNode.insertBefore(t,s)}(window, document,'script', + 'https://connect.facebook.net/en_US/fbevents.js'); + fbq('init', '${pixelId}'); + fbq('track', 'PageView'); +</script> + +`; + } + } + + // Google Analytics + if (this.isTrackerEnabled('ga4')) { + const measurementId = this.getValue('ga4Id'); + if (measurementId) { + code += `<!-- Google Analytics (GA4) --> +<script async src="https://www.googletagmanager.com/gtag/js?id=${measurementId}"></script> +<script> + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', '${measurementId}'); +</script> + +`; + } + } + + // Google Tag Manager + if (this.isTrackerEnabled('gtm')) { + const containerId = this.getValue('gtmId'); + if (containerId) { + code += `<!-- Google Tag Manager --> +<script> + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','${containerId}'); +</script> + +`; + } + } + + // Plausible + if (this.isTrackerEnabled('plausible')) { + const domain = this.getValue('plausibleDomain'); + if (domain) { + code += `<!-- Plausible Analytics --> +<script defer data-domain="${domain}" src="https://plausible.io/js/script.js"></script> + +`; + } + } + + // Fathom + if (this.isTrackerEnabled('fathom')) { + const siteId = this.getValue('fathomSiteId'); + if (siteId) { + code += `<!-- Fathom Analytics --> +<script src="https://cdn.usefathom.com/script.js" data-site="${siteId}" defer></script> + +`; + } + } + + // Matomo + if (this.isTrackerEnabled('matomo')) { + const serverUrl = this.getValue('matomoUrl'); + const siteId = this.getValue('matomoSiteId'); + if (serverUrl && siteId) { + code += `<!-- Matomo Analytics --> +<script> + var _paq = window._paq = window._paq || []; + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + (function() { + var u="${serverUrl}/"; + _paq.push(['setTrackerUrl', u+'matomo.php']); + _paq.push(['setSiteId', '${siteId}']); + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); + })(); +</script> + +`; + } + } + + // Custom Head Scripts + const customHead = this.getTextareaValue('customHeadScripts'); + if (customHead) { + code += `<!-- Custom Scripts --> +${this.escapeHtml(customHead)} + +`; + } + + // Update preview + const codeElement = preview.querySelector('code'); + if (codeElement) { + if (code) { + codeElement.innerHTML = code.trim(); + } else { + codeElement.innerHTML = '<!-- Ingen aktive tracking-koder -->'; + } + } + } + + /** + * Copy generated code to clipboard + */ + private copyCode(): void { + const preview = document.getElementById('trackingCodePreview'); + if (!preview) return; + + const codeElement = preview.querySelector('code'); + if (!codeElement) return; + + // Get plain text version (strip HTML tags and decode entities) + const code = codeElement.innerHTML + .replace(/]*>/g, '') + .replace(/<\/span>/g, '') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + + navigator.clipboard.writeText(code).then(() => { + const btn = document.getElementById('copyTrackingCode'); + if (!btn) return; + + const originalHtml = btn.innerHTML; + btn.innerHTML = ' Kopieret!'; + btn.style.color = 'var(--color-green)'; + + setTimeout(() => { + btn.innerHTML = originalHtml; + btn.style.color = ''; + }, 2000); + }); + } +}