Enhances Services module with detail view and interactions

Adds comprehensive service detail view with multiple tabs and dynamic interactions
Implements client-side navigation between service list and detail views
Introduces mock service data catalog for flexible component rendering
Extends localization support for new service detail screens

Improves user experience by adding edit capabilities and smooth view transitions
This commit is contained in:
Janus C. H. Knudsen 2026-01-16 22:03:22 +01:00
parent fad5e46dfb
commit 120367acbb
22 changed files with 1780 additions and 597 deletions

View file

@ -0,0 +1,40 @@
@model PlanTempus.Application.Features.Services.Components.ServiceDetailHeaderViewModel
<swp-service-detail-header>
<swp-service-info>
<swp-service-name-row>
<swp-service-name contenteditable="true">@Model.Name</swp-service-name>
@if (Model.Tags.Any())
{
<swp-tags-row>
@foreach (var tag in Model.Tags)
{
<swp-tag class="@tag.CssClass">@tag.Text</swp-tag>
}
</swp-tags-row>
}
<swp-status-indicator data-active="@Model.IsActive.ToString().ToLower()">
<span class="icon">●</span>
<span class="text">@Model.StatusText</span>
</swp-status-indicator>
</swp-service-name-row>
<swp-fact-boxes-inline>
<swp-fact-inline>
<swp-fact-inline-value>@Model.DurationRange</swp-fact-inline-value>
<swp-fact-inline-label>@Model.LabelDuration</swp-fact-inline-label>
</swp-fact-inline>
<swp-fact-inline>
<swp-fact-inline-value>@Model.FromPrice</swp-fact-inline-value>
<swp-fact-inline-label>@Model.LabelFromPrice</swp-fact-inline-label>
</swp-fact-inline>
<swp-fact-inline>
<swp-fact-inline-value>@Model.EmployeeCount</swp-fact-inline-value>
<swp-fact-inline-label>@Model.LabelEmployees</swp-fact-inline-label>
</swp-fact-inline>
<swp-fact-inline>
<swp-fact-inline-value>@Model.BookingsThisYear</swp-fact-inline-value>
<swp-fact-inline-label>@Model.LabelBookingsThisYear</swp-fact-inline-label>
</swp-fact-inline>
</swp-fact-boxes-inline>
</swp-service-info>
</swp-service-detail-header>

View file

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Mvc;
using PlanTempus.Application.Features.Localization.Services;
namespace PlanTempus.Application.Features.Services.Components;
public class ServiceDetailHeaderViewComponent : ViewComponent
{
private readonly ILocalizationService _localization;
public ServiceDetailHeaderViewComponent(ILocalizationService localization)
{
_localization = localization;
}
public IViewComponentResult Invoke(string key)
{
var service = ServiceDetailCatalog.Get(key);
var model = new ServiceDetailHeaderViewModel
{
Name = service.Name,
IsActive = service.IsActive,
StatusText = service.IsActive
? _localization.Get("services.detail.header.active")
: _localization.Get("services.detail.header.inactive"),
DurationRange = service.DurationRange,
FromPrice = service.FromPrice,
EmployeeCount = service.EmployeeCount,
BookingsThisYear = service.BookingsThisYear,
LabelDuration = _localization.Get("services.detail.header.duration"),
LabelFromPrice = _localization.Get("services.detail.header.fromPrice"),
LabelEmployees = _localization.Get("services.detail.header.employees"),
LabelBookingsThisYear = _localization.Get("services.detail.header.bookingsThisYear"),
Tags = service.Tags.Select(t => new ServiceTagViewModel { Text = t.Text, CssClass = t.CssClass }).ToList()
};
return View(model);
}
}
public class ServiceDetailHeaderViewModel
{
public required string Name { get; init; }
public required bool IsActive { get; init; }
public required string StatusText { get; init; }
public required string DurationRange { get; init; }
public required string FromPrice { get; init; }
public required string EmployeeCount { get; init; }
public required string BookingsThisYear { get; init; }
public required string LabelDuration { get; init; }
public required string LabelFromPrice { get; init; }
public required string LabelEmployees { get; init; }
public required string LabelBookingsThisYear { get; init; }
public List<ServiceTagViewModel> Tags { get; init; } = new();
}
public class ServiceTagViewModel
{
public required string Text { get; init; }
public required string CssClass { get; init; }
}