diff --git a/PlanTempus.Application/Features/Localization/Translations/da.json b/PlanTempus.Application/Features/Localization/Translations/da.json index 363deeb..7cbee5b 100644 --- a/PlanTempus.Application/Features/Localization/Translations/da.json +++ b/PlanTempus.Application/Features/Localization/Translations/da.json @@ -274,6 +274,54 @@ "image": "Billede", "uploadImage": "+ Upload billede" }, + "prices": { + "priceStructure": "Prisstruktur", + "simplePrice": "Simpel pris", + "matrixPrice": "Matrix-pris", + "price": "Pris", + "level": "Niveau", + "shortHair": "Kort hår", + "mediumHair": "Mellem hår", + "longHair": "Langt hår", + "extraLongHair": "Ekstra langt", + "addLevel": "Tilføj niveau eller hårlængde", + "economy": "Økonomi", + "vatRate": "Momssats", + "productCost": "Produktomkostning", + "commission": "Provision", + "discounts": "Rabatter & Loyalitet", + "memberDiscount": "Medlemsrabat (10%)", + "giftCardPayment": "Kan betales med gavekort", + "loyaltyPoints": "Optjen loyalitetspoint" + }, + "duration": { + "durationVariants": "Varighedsvarianter", + "addVariant": "Tilføj variant", + "bufferTimes": "Buffer-tider", + "bufferBefore": "Buffer før aftale", + "bufferAfter": "Buffer efter aftale", + "cleanupTime": "Oprydningstid", + "minutes": "min" + }, + "rules": { + "bookingRules": "Booking-regler", + "minNotice": "Minimum varsel", + "maxAdvanceBooking": "Maks. forudbooking", + "cancellationDeadline": "Afbestillingsfrist", + "noShowFee": "No-show gebyr", + "requirements": "Krav & Forberedelse", + "requiresConsultation": "Konsultation påkrævet", + "requiresConsultationDesc": "Kunde skal have konsultation før første booking", + "requiresPatchTest": "Patch test påkrævet", + "requiresPatchTestDesc": "Allergitest 48 timer før farvebehandling (nye kunder)", + "ageRestriction": "Aldersbegrænsning", + "ageRestrictionDesc": "Minimum alder for booking af denne service", + "onlineBookingSettings": "Online booking indstillinger", + "showInOnlineBooking": "Vis i online booking", + "allowEmployeeSelection": "Tillad valg af medarbejder", + "showPrice": "Vis pris", + "showDuration": "Vis varighed" + }, "header": { "duration": "min varighed", "fromPrice": "fra pris", diff --git a/PlanTempus.Application/Features/Localization/Translations/en.json b/PlanTempus.Application/Features/Localization/Translations/en.json index ac05b22..3a2e47a 100644 --- a/PlanTempus.Application/Features/Localization/Translations/en.json +++ b/PlanTempus.Application/Features/Localization/Translations/en.json @@ -241,6 +241,106 @@ "duration": "Duration", "price": "Price", "serviceCount": "Service count" + }, + "detail": { + "back": "Back to services", + "save": "Save changes", + "tabs": { + "general": "General", + "prices": "Prices", + "duration": "Duration", + "employees": "Employees", + "addons": "Add-ons", + "rules": "Rules" + }, + "general": { + "basic": "Basic", + "serviceName": "Service name", + "category": "Category", + "calendarColor": "Calendar color", + "isActive": "Service active", + "internalNotes": "Internal notes", + "bookingType": "Booking type", + "canBookAsMain": "Can be booked as main service", + "canBookAsMainDesc": "Shown in service list and can be booked independently", + "canBookAsAddon": "Can be booked as add-on", + "canBookAsAddonDesc": "Can be added as extra service to other services", + "onlineBooking": "Online booking", + "showInOnlineBooking": "Show in online booking", + "showInOnlineBookingDesc": "Visible to customers in online booking", + "isFeatured": "Featured service", + "isFeaturedDesc": "Shown at top with featured styling", + "description": "Description", + "image": "Image", + "uploadImage": "+ Upload image" + }, + "prices": { + "priceStructure": "Price structure", + "simplePrice": "Simple price", + "matrixPrice": "Matrix price", + "price": "Price", + "level": "Level", + "shortHair": "Short hair", + "mediumHair": "Medium hair", + "longHair": "Long hair", + "extraLongHair": "Extra long", + "addLevel": "Add level or hair length", + "economy": "Economy", + "vatRate": "VAT rate", + "productCost": "Product cost", + "commission": "Commission", + "discounts": "Discounts & Loyalty", + "memberDiscount": "Member discount (10%)", + "giftCardPayment": "Can be paid with gift card", + "loyaltyPoints": "Earn loyalty points" + }, + "duration": { + "durationVariants": "Duration variants", + "addVariant": "Add variant", + "bufferTimes": "Buffer times", + "bufferBefore": "Buffer before appointment", + "bufferAfter": "Buffer after appointment", + "cleanupTime": "Cleanup time", + "minutes": "min" + }, + "rules": { + "bookingRules": "Booking rules", + "minNotice": "Minimum notice", + "maxAdvanceBooking": "Max. advance booking", + "cancellationDeadline": "Cancellation deadline", + "noShowFee": "No-show fee", + "requirements": "Requirements & Preparation", + "requiresConsultation": "Consultation required", + "requiresConsultationDesc": "Customer must have consultation before first booking", + "requiresPatchTest": "Patch test required", + "requiresPatchTestDesc": "Allergy test 48 hours before color treatment (new customers)", + "ageRestriction": "Age restriction", + "ageRestrictionDesc": "Minimum age for booking this service", + "onlineBookingSettings": "Online booking settings", + "showInOnlineBooking": "Show in online booking", + "allowEmployeeSelection": "Allow employee selection", + "showPrice": "Show price", + "showDuration": "Show duration" + }, + "header": { + "duration": "min duration", + "fromPrice": "from price", + "employees": "employees", + "bookingsThisYear": "bookings this year", + "active": "Active", + "inactive": "Inactive" + }, + "categoryDrawer": { + "title": "Create category", + "name": "Category name", + "description": "Description", + "visibilitySection": "Visibility", + "showInBooking": "Category should be shown in online booking", + "showInBookingDescription": "Category will still be visible here in the system", + "timePeriod": "Should only be visible in the following time period", + "timePeriodHint": "Leave fields blank for no time restriction", + "save": "Save category" + } } }, "employees": { diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailCatalog.cs b/PlanTempus.Application/Features/Services/Components/ServiceDetailCatalog.cs index a153fca..e0b0156 100644 --- a/PlanTempus.Application/Features/Services/Components/ServiceDetailCatalog.cs +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailCatalog.cs @@ -25,7 +25,44 @@ public static class ServiceDetailCatalog CanBookAsAddon = false, ShowInOnlineBooking = true, IsFeatured = false, - Description = "Forkæl dig selv med en komplet forvandling! Vores Klip & Farve behandling inkluderer professionel farverådgivning, farvning tilpasset din hudtone, præcisionsklip og styling. Perfekt til dig der ønsker et helt nyt look." + Description = "Forkæl dig selv med en komplet forvandling! Vores Klip & Farve behandling inkluderer professionel farverådgivning, farvning tilpasset din hudtone, præcisionsklip og styling. Perfekt til dig der ønsker et helt nyt look.", + // Priser + PriceMode = PriceMode.Matrix, + PriceMatrix = new() + { + new("Junior", "795", "895", "995", "1.095"), + new("Senior", "895", "995", "1.095", "1.195"), + new("Master", "995", "1.095", "1.195", "1.295") + }, + VatRate = "25", + ProductCost = "85 kr", + Commission = "standard", + MemberDiscount = true, + GiftCardPayment = true, + LoyaltyPoints = true, + // Varighed + DurationVariants = new() + { + new("Kort hår", 60), + new("Mellem hår", 90), + new("Langt hår", 120), + new("Ekstra langt hår", 150) + }, + BufferBefore = "15", + BufferAfter = "10", + CleanupTime = "5", + // Regler + MinNotice = "24", + MaxAdvanceBooking = "3", + CancellationDeadline = "24", + NoShowFee = "50", + RequiresConsultation = false, + RequiresPatchTest = true, + AgeRestriction = false, + ShowInOnlineBookingRules = true, + AllowEmployeeSelection = true, + ShowPrice = true, + ShowDuration = true }, ["service-2"] = new ServiceDetailRecord { @@ -166,6 +203,42 @@ public record ServiceDetailRecord public required bool ShowInOnlineBooking { get; init; } public required bool IsFeatured { get; init; } public required string Description { get; init; } + + // Priser tab + public PriceMode PriceMode { get; init; } = PriceMode.Simple; + public string SimplePrice { get; init; } = "995 kr"; + public List PriceMatrix { get; init; } = new(); + public string VatRate { get; init; } = "25"; + public string ProductCost { get; init; } = "85 kr"; + public string Commission { get; init; } = "standard"; + public bool MemberDiscount { get; init; } = true; + public bool GiftCardPayment { get; init; } = true; + public bool LoyaltyPoints { get; init; } = true; + + // Varighed tab + public List DurationVariants { get; init; } = new(); + public string BufferBefore { get; init; } = "15"; + public string BufferAfter { get; init; } = "10"; + public string CleanupTime { get; init; } = "5"; + + // Regler tab + public string MinNotice { get; init; } = "24"; + public string MaxAdvanceBooking { get; init; } = "3"; + public string CancellationDeadline { get; init; } = "24"; + public string NoShowFee { get; init; } = "50"; + public bool RequiresConsultation { get; init; } = false; + public bool RequiresPatchTest { get; init; } = false; + public bool AgeRestriction { get; init; } = false; + public bool ShowInOnlineBookingRules { get; init; } = true; + public bool AllowEmployeeSelection { get; init; } = true; + public bool ShowPrice { get; init; } = true; + public bool ShowDuration { get; init; } = true; } +public enum PriceMode { Simple, Matrix } + +public record PriceMatrixRow(string Level, string ShortHair, string MediumHair, string LongHair, string ExtraLongHair); + +public record DurationVariant(string Name, int Minutes); + public record ServiceTag(string Text, string CssClass); diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/Default.cshtml b/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/Default.cshtml new file mode 100644 index 0000000..35864d7 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/Default.cshtml @@ -0,0 +1,81 @@ +@model PlanTempus.Application.Features.Services.Components.ServiceDetailDurationViewModel + + + + @Model.LabelDurationVariants + + + @foreach (var variant in Model.DurationVariants) + { + + @variant.Name + + @variant.Minutes + @Model.LabelMinutes + + + + + + } + + + + + @Model.LabelAddVariant + + + + + @Model.LabelBufferTimes + + + @Model.LabelBufferBefore + + + + Ingen + 5 minutter + 10 minutter + 15 minutter + 30 minutter + + + + + @Model.LabelBufferAfter + + + + Ingen + 5 minutter + 10 minutter + 15 minutter + 30 minutter + + + + + @Model.LabelCleanupTime + + + + Ingen + 5 minutter + 10 minutter + 15 minutter + + + + + + diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/ServiceDetailDurationViewComponent.cs b/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/ServiceDetailDurationViewComponent.cs new file mode 100644 index 0000000..e351294 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailDuration/ServiceDetailDurationViewComponent.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc; +using PlanTempus.Application.Features.Localization.Services; + +namespace PlanTempus.Application.Features.Services.Components; + +public class ServiceDetailDurationViewComponent : ViewComponent +{ + private readonly ILocalizationService _localization; + + public ServiceDetailDurationViewComponent(ILocalizationService localization) + { + _localization = localization; + } + + public IViewComponentResult Invoke(string key) + { + var service = ServiceDetailCatalog.Get(key); + + var model = new ServiceDetailDurationViewModel + { + // Data + DurationVariants = service.DurationVariants, + BufferBefore = service.BufferBefore, + BufferAfter = service.BufferAfter, + CleanupTime = service.CleanupTime, + + // Labels + LabelDurationVariants = _localization.Get("services.detail.duration.durationVariants"), + LabelAddVariant = _localization.Get("services.detail.duration.addVariant"), + LabelBufferTimes = _localization.Get("services.detail.duration.bufferTimes"), + LabelBufferBefore = _localization.Get("services.detail.duration.bufferBefore"), + LabelBufferAfter = _localization.Get("services.detail.duration.bufferAfter"), + LabelCleanupTime = _localization.Get("services.detail.duration.cleanupTime"), + LabelMinutes = _localization.Get("services.detail.duration.minutes") + }; + + return View(model); + } +} + +public class ServiceDetailDurationViewModel +{ + // Data + public required List DurationVariants { get; init; } + public required string BufferBefore { get; init; } + public required string BufferAfter { get; init; } + public required string CleanupTime { get; init; } + + // Labels + public required string LabelDurationVariants { get; init; } + public required string LabelAddVariant { get; init; } + public required string LabelBufferTimes { get; init; } + public required string LabelBufferBefore { get; init; } + public required string LabelBufferAfter { get; init; } + public required string LabelCleanupTime { get; init; } + public required string LabelMinutes { get; init; } +} diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/Default.cshtml b/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/Default.cshtml new file mode 100644 index 0000000..8075090 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/Default.cshtml @@ -0,0 +1,112 @@ +@model PlanTempus.Application.Features.Services.Components.ServiceDetailPricesViewModel + + + @Model.LabelPriceStructure + + + @Model.LabelSimplePrice + @Model.LabelMatrixPrice + + + + + + + @Model.LabelPrice + @Model.SimplePrice + + + + + + + + + @Model.LabelLevel + @Model.LabelShortHair + @Model.LabelMediumHair + @Model.LabelLongHair + @Model.LabelExtraLongHair + + @foreach (var row in Model.PriceMatrix) + { + + @row.Level + @row.ShortHair kr + @row.MediumHair kr + @row.LongHair kr + @row.ExtraLongHair kr + + } + + + + + @Model.LabelAddLevel + + + + + + + @Model.LabelEconomy + + + @Model.LabelVatRate + + + + 25% (standard) + 0% (momsfri) + + + + + @Model.LabelProductCost + @Model.ProductCost + + + @Model.LabelCommission + + + + Standard (fra lønsats) + Fast beløb + Procent af pris + + + + + + + + @Model.LabelDiscounts + + @Model.LabelMemberDiscount + + @Model.ToggleYes + @Model.ToggleNo + + + + @Model.LabelGiftCardPayment + + @Model.ToggleYes + @Model.ToggleNo + + + + @Model.LabelLoyaltyPoints + + @Model.ToggleYes + @Model.ToggleNo + + + + diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/ServiceDetailPricesViewComponent.cs b/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/ServiceDetailPricesViewComponent.cs new file mode 100644 index 0000000..db1ce40 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailPrices/ServiceDetailPricesViewComponent.cs @@ -0,0 +1,105 @@ +using Microsoft.AspNetCore.Mvc; +using PlanTempus.Application.Features.Localization.Services; + +namespace PlanTempus.Application.Features.Services.Components; + +public class ServiceDetailPricesViewComponent : ViewComponent +{ + private readonly ILocalizationService _localization; + + public ServiceDetailPricesViewComponent(ILocalizationService localization) + { + _localization = localization; + } + + public IViewComponentResult Invoke(string key) + { + var service = ServiceDetailCatalog.Get(key); + + var model = new ServiceDetailPricesViewModel + { + // Data + PriceMode = service.PriceMode, + SimplePrice = service.SimplePrice, + PriceMatrix = service.PriceMatrix, + VatRate = service.VatRate, + ProductCost = service.ProductCost, + Commission = service.Commission, + MemberDiscount = service.MemberDiscount, + GiftCardPayment = service.GiftCardPayment, + LoyaltyPoints = service.LoyaltyPoints, + + // Labels - Price structure + LabelPriceStructure = _localization.Get("services.detail.prices.priceStructure"), + LabelSimplePrice = _localization.Get("services.detail.prices.simplePrice"), + LabelMatrixPrice = _localization.Get("services.detail.prices.matrixPrice"), + LabelPrice = _localization.Get("services.detail.prices.price"), + LabelLevel = _localization.Get("services.detail.prices.level"), + LabelShortHair = _localization.Get("services.detail.prices.shortHair"), + LabelMediumHair = _localization.Get("services.detail.prices.mediumHair"), + LabelLongHair = _localization.Get("services.detail.prices.longHair"), + LabelExtraLongHair = _localization.Get("services.detail.prices.extraLongHair"), + LabelAddLevel = _localization.Get("services.detail.prices.addLevel"), + + // Labels - Economy + LabelEconomy = _localization.Get("services.detail.prices.economy"), + LabelVatRate = _localization.Get("services.detail.prices.vatRate"), + LabelProductCost = _localization.Get("services.detail.prices.productCost"), + LabelCommission = _localization.Get("services.detail.prices.commission"), + + // Labels - Discounts + LabelDiscounts = _localization.Get("services.detail.prices.discounts"), + LabelMemberDiscount = _localization.Get("services.detail.prices.memberDiscount"), + LabelGiftCardPayment = _localization.Get("services.detail.prices.giftCardPayment"), + LabelLoyaltyPoints = _localization.Get("services.detail.prices.loyaltyPoints"), + + // Toggle labels + ToggleYes = _localization.Get("common.yes"), + ToggleNo = _localization.Get("common.no") + }; + + return View(model); + } +} + +public class ServiceDetailPricesViewModel +{ + // Data + public PriceMode PriceMode { get; init; } + public required string SimplePrice { get; init; } + public required List PriceMatrix { get; init; } + public required string VatRate { get; init; } + public required string ProductCost { get; init; } + public required string Commission { get; init; } + public bool MemberDiscount { get; init; } + public bool GiftCardPayment { get; init; } + public bool LoyaltyPoints { get; init; } + + // Labels - Price structure + public required string LabelPriceStructure { get; init; } + public required string LabelSimplePrice { get; init; } + public required string LabelMatrixPrice { get; init; } + public required string LabelPrice { get; init; } + public required string LabelLevel { get; init; } + public required string LabelShortHair { get; init; } + public required string LabelMediumHair { get; init; } + public required string LabelLongHair { get; init; } + public required string LabelExtraLongHair { get; init; } + public required string LabelAddLevel { get; init; } + + // Labels - Economy + public required string LabelEconomy { get; init; } + public required string LabelVatRate { get; init; } + public required string LabelProductCost { get; init; } + public required string LabelCommission { get; init; } + + // Labels - Discounts + public required string LabelDiscounts { get; init; } + public required string LabelMemberDiscount { get; init; } + public required string LabelGiftCardPayment { get; init; } + public required string LabelLoyaltyPoints { get; init; } + + // Toggle labels + public required string ToggleYes { get; init; } + public required string ToggleNo { get; init; } +} diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/Default.cshtml b/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/Default.cshtml new file mode 100644 index 0000000..57f6813 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/Default.cshtml @@ -0,0 +1,173 @@ +@model PlanTempus.Application.Features.Services.Components.ServiceDetailRulesViewModel + + + + @Model.LabelBookingRules + + + @Model.LabelMinNotice + + + + Ingen + 2 timer + 4 timer + 24 timer + 48 timer + 1 uge + + + + + @Model.LabelMaxAdvanceBooking + + + + 1 måned + 2 måneder + 3 måneder + 6 måneder + 1 år + + + + + @Model.LabelCancellationDeadline + + + + Ingen + 2 timer + 4 timer + 24 timer + 48 timer + + + + + @Model.LabelNoShowFee + + + + Intet + 25% af pris + 50% af pris + Fuld pris + Fast beløb + + + + + + + + @Model.LabelRequirements + +
+ @Model.LabelRequiresConsultation + @Model.LabelRequiresConsultationDesc +
+ + @Model.ToggleYes + @Model.ToggleNo + +
+ +
+ @Model.LabelRequiresPatchTest + @Model.LabelRequiresPatchTestDesc +
+ + @Model.ToggleYes + @Model.ToggleNo + +
+ +
+ @Model.LabelAgeRestriction + @Model.LabelAgeRestrictionDesc +
+ + @Model.ToggleYes + @Model.ToggleNo + +
+
+
+ + + @Model.LabelOnlineBookingSettings + + @Model.LabelShowInOnlineBooking + + @Model.ToggleYes + @Model.ToggleNo + + + + @Model.LabelAllowEmployeeSelection + + @Model.ToggleYes + @Model.ToggleNo + + + + @Model.LabelShowPrice + + @Model.ToggleYes + @Model.ToggleNo + + + + @Model.LabelShowDuration + + @Model.ToggleYes + @Model.ToggleNo + + + + +@functions { + string GetNoticeLabel(string hours) + { + return hours switch + { + "0" => "Ingen", + "168" => "1 uge", + _ => $"{hours} timer" + }; + } + + string GetAdvanceBookingLabel(string months) + { + return months switch + { + "1" => "1 måned", + "12" => "1 år", + _ => $"{months} måneder" + }; + } + + string GetNoShowFeeLabel(string fee) + { + return fee switch + { + "0" => "Intet", + "100" => "Fuld pris", + "fixed" => "Fast beløb", + _ => $"{fee}% af pris" + }; + } +} diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/ServiceDetailRulesViewComponent.cs b/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/ServiceDetailRulesViewComponent.cs new file mode 100644 index 0000000..66271b7 --- /dev/null +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailRules/ServiceDetailRulesViewComponent.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Mvc; +using PlanTempus.Application.Features.Localization.Services; + +namespace PlanTempus.Application.Features.Services.Components; + +public class ServiceDetailRulesViewComponent : ViewComponent +{ + private readonly ILocalizationService _localization; + + public ServiceDetailRulesViewComponent(ILocalizationService localization) + { + _localization = localization; + } + + public IViewComponentResult Invoke(string key) + { + var service = ServiceDetailCatalog.Get(key); + + var model = new ServiceDetailRulesViewModel + { + // Data + MinNotice = service.MinNotice, + MaxAdvanceBooking = service.MaxAdvanceBooking, + CancellationDeadline = service.CancellationDeadline, + NoShowFee = service.NoShowFee, + RequiresConsultation = service.RequiresConsultation, + RequiresPatchTest = service.RequiresPatchTest, + AgeRestriction = service.AgeRestriction, + ShowInOnlineBooking = service.ShowInOnlineBookingRules, + AllowEmployeeSelection = service.AllowEmployeeSelection, + ShowPrice = service.ShowPrice, + ShowDuration = service.ShowDuration, + + // Labels - Booking rules + LabelBookingRules = _localization.Get("services.detail.rules.bookingRules"), + LabelMinNotice = _localization.Get("services.detail.rules.minNotice"), + LabelMaxAdvanceBooking = _localization.Get("services.detail.rules.maxAdvanceBooking"), + LabelCancellationDeadline = _localization.Get("services.detail.rules.cancellationDeadline"), + LabelNoShowFee = _localization.Get("services.detail.rules.noShowFee"), + + // Labels - Requirements + LabelRequirements = _localization.Get("services.detail.rules.requirements"), + LabelRequiresConsultation = _localization.Get("services.detail.rules.requiresConsultation"), + LabelRequiresConsultationDesc = _localization.Get("services.detail.rules.requiresConsultationDesc"), + LabelRequiresPatchTest = _localization.Get("services.detail.rules.requiresPatchTest"), + LabelRequiresPatchTestDesc = _localization.Get("services.detail.rules.requiresPatchTestDesc"), + LabelAgeRestriction = _localization.Get("services.detail.rules.ageRestriction"), + LabelAgeRestrictionDesc = _localization.Get("services.detail.rules.ageRestrictionDesc"), + + // Labels - Online booking settings + LabelOnlineBookingSettings = _localization.Get("services.detail.rules.onlineBookingSettings"), + LabelShowInOnlineBooking = _localization.Get("services.detail.rules.showInOnlineBooking"), + LabelAllowEmployeeSelection = _localization.Get("services.detail.rules.allowEmployeeSelection"), + LabelShowPrice = _localization.Get("services.detail.rules.showPrice"), + LabelShowDuration = _localization.Get("services.detail.rules.showDuration"), + + // Toggle labels + ToggleYes = _localization.Get("common.yes"), + ToggleNo = _localization.Get("common.no") + }; + + return View(model); + } +} + +public class ServiceDetailRulesViewModel +{ + // Data - Booking rules + public required string MinNotice { get; init; } + public required string MaxAdvanceBooking { get; init; } + public required string CancellationDeadline { get; init; } + public required string NoShowFee { get; init; } + + // Data - Requirements + public bool RequiresConsultation { get; init; } + public bool RequiresPatchTest { get; init; } + public bool AgeRestriction { get; init; } + + // Data - Online booking settings + public bool ShowInOnlineBooking { get; init; } + public bool AllowEmployeeSelection { get; init; } + public bool ShowPrice { get; init; } + public bool ShowDuration { get; init; } + + // Labels - Booking rules + public required string LabelBookingRules { get; init; } + public required string LabelMinNotice { get; init; } + public required string LabelMaxAdvanceBooking { get; init; } + public required string LabelCancellationDeadline { get; init; } + public required string LabelNoShowFee { get; init; } + + // Labels - Requirements + public required string LabelRequirements { get; init; } + public required string LabelRequiresConsultation { get; init; } + public required string LabelRequiresConsultationDesc { get; init; } + public required string LabelRequiresPatchTest { get; init; } + public required string LabelRequiresPatchTestDesc { get; init; } + public required string LabelAgeRestriction { get; init; } + public required string LabelAgeRestrictionDesc { get; init; } + + // Labels - Online booking settings + public required string LabelOnlineBookingSettings { get; init; } + public required string LabelShowInOnlineBooking { get; init; } + public required string LabelAllowEmployeeSelection { get; init; } + public required string LabelShowPrice { get; init; } + public required string LabelShowDuration { get; init; } + + // Toggle labels + public required string ToggleYes { get; init; } + public required string ToggleNo { get; init; } +} diff --git a/PlanTempus.Application/Features/Services/Components/ServiceDetailView/Default.cshtml b/PlanTempus.Application/Features/Services/Components/ServiceDetailView/Default.cshtml index f34757b..51110ea 100644 --- a/PlanTempus.Application/Features/Services/Components/ServiceDetailView/Default.cshtml +++ b/PlanTempus.Application/Features/Services/Components/ServiceDetailView/Default.cshtml @@ -44,19 +44,13 @@ - - Priser -

Priser-tab kommer snart...

-
+ @await Component.InvokeAsync("ServiceDetailPrices", Model.ServiceKey)
- - Varighed -

Varighed-tab kommer snart...

-
+ @await Component.InvokeAsync("ServiceDetailDuration", Model.ServiceKey)
@@ -80,10 +74,7 @@ - - Regler -

Regler-tab kommer snart...

-
+ @await Component.InvokeAsync("ServiceDetailRules", Model.ServiceKey)
diff --git a/PlanTempus.Application/wwwroot/css/services.css b/PlanTempus.Application/wwwroot/css/services.css index f286a49..5a83a46 100644 --- a/PlanTempus.Application/wwwroot/css/services.css +++ b/PlanTempus.Application/wwwroot/css/services.css @@ -272,3 +272,157 @@ swp-service-name { border-bottom: 1px dashed var(--color-teal); } } + +/* =========================================== + PRICE MODE TOGGLE (Priser tab) + =========================================== */ + +swp-price-mode { + display: flex; + gap: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + overflow: hidden; + margin-bottom: var(--spacing-6); + width: fit-content; +} + +swp-price-mode-btn { + padding: var(--spacing-3) var(--spacing-6); + font-size: var(--font-size-sm); + cursor: pointer; + background: var(--color-surface); + border-right: 1px solid var(--color-border); + transition: all var(--transition-fast); + + &:last-child { + border-right: none; + } + + &:hover { + background: var(--color-background-alt); + } + + &.active { + background: var(--color-teal); + color: white; + } +} + +/* =========================================== + PRICE MATRIX TABLE (Priser tab) + =========================================== */ + +swp-price-matrix { + display: block; +} + +swp-data-table.price-matrix { + grid-template-columns: 3fr repeat(4, 2fr); + margin-bottom: var(--spacing-6); +} + +swp-data-table.price-matrix swp-data-table-cell:first-child { + font-weight: var(--font-weight-medium); +} + +swp-data-table.price-matrix span[contenteditable="true"] { + outline: none; + padding: var(--spacing-1) var(--spacing-2); + border-radius: var(--radius-sm); + + &:hover { + background: var(--color-background-alt); + } + + &:focus { + background: var(--color-surface); + box-shadow: 0 0 0 2px var(--color-teal); + } +} + +/* =========================================== + DURATION LIST (Varighed tab) + =========================================== */ + +swp-duration-list { + display: flex; + flex-direction: column; + gap: var(--spacing-2); + margin-bottom: var(--spacing-6); +} + +swp-duration-item { + display: grid; + grid-template-columns: 1fr auto auto; + align-items: center; + gap: var(--spacing-4); + padding: var(--spacing-4); + background: var(--color-background-alt); + border-radius: var(--radius-md); + transition: background var(--transition-fast); + + &:hover { + background: var(--color-background-hover); + } +} + +swp-duration-name { + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-duration-value { + display: flex; + align-items: center; + gap: var(--spacing-2); + font-family: var(--font-mono); + font-size: var(--font-size-base); + color: var(--color-text); + + span[contenteditable="true"] { + outline: none; + padding: var(--spacing-1) var(--spacing-2); + border-radius: var(--radius-sm); + min-width: 40px; + text-align: right; + + &:hover { + background: var(--color-background); + } + + &:focus { + background: var(--color-surface); + box-shadow: 0 0 0 2px var(--color-teal); + } + } +} + +swp-duration-unit { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + font-family: var(--font-family); +} + +swp-duration-delete { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-sm); + color: var(--color-text-tertiary); + cursor: pointer; + transition: all var(--transition-fast); + opacity: 0; + + &:hover { + color: var(--color-red); + background: var(--bg-red-subtle); + } +} + +swp-duration-item:hover swp-duration-delete { + opacity: 1; +}