Adds comprehensive customer detail view components
Implements full customer detail page with multiple feature-rich components including overview, economy, statistics, journal, appointments, giftcards, and activity sections Creates reusable ViewComponents for different customer detail aspects with robust data modeling and presentation logic
This commit is contained in:
parent
38e9243bcd
commit
1b25978d9b
26 changed files with 3792 additions and 956 deletions
|
|
@ -0,0 +1,84 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Customers.Components;
|
||||
|
||||
public class CustomerDetailHeaderViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public CustomerDetailHeaderViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string customerId)
|
||||
{
|
||||
var customer = CustomerDetailCatalog.Get(customerId);
|
||||
var header = customer.Header;
|
||||
var contact = customer.Contact;
|
||||
|
||||
// Format customer since date
|
||||
var customerSince = DateTime.TryParse(header.CustomerSince, out var date)
|
||||
? date.ToString("MMMM yyyy", new System.Globalization.CultureInfo("da-DK"))
|
||||
: header.CustomerSince;
|
||||
|
||||
var model = new CustomerDetailHeaderViewModel
|
||||
{
|
||||
Initials = header.Initials,
|
||||
Name = header.Name,
|
||||
Tags = header.Tags.Select(t => new CustomerTagViewModel
|
||||
{
|
||||
Text = t.ToUpper(),
|
||||
CssClass = t.ToLowerInvariant()
|
||||
}).ToList(),
|
||||
BookingAllowed = header.BookingAllowed,
|
||||
Phone = contact.Phone,
|
||||
PhoneHref = $"tel:{contact.Phone.Replace(" ", "")}",
|
||||
Email = contact.Email,
|
||||
EmailHref = $"mailto:{contact.Email}",
|
||||
CustomerSinceText = $"Kunde siden {customerSince}",
|
||||
FactVisits = header.Facts.Visits.ToString(),
|
||||
FactVisitsLabel = _localization.Get("customers.detail.visits"),
|
||||
FactInterval = header.Facts.AvgIntervalDays.ToString(),
|
||||
FactIntervalLabel = _localization.Get("customers.detail.interval"),
|
||||
FactHairdresser = header.Facts.PreferredHairdresser,
|
||||
FactHairdresserLabel = _localization.Get("customers.detail.preferredHairdresser"),
|
||||
FactRevenue = $"{header.Facts.TotalRevenue:N0} kr".Replace(",", "."),
|
||||
FactRevenueLabel = _localization.Get("customers.detail.totalRevenue"),
|
||||
BookingAllowedText = _localization.Get("customers.detail.bookingAllowed"),
|
||||
BookingBlockedText = _localization.Get("customers.detail.bookingBlocked")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomerDetailHeaderViewModel
|
||||
{
|
||||
public required string Initials { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public List<CustomerTagViewModel> Tags { get; init; } = new();
|
||||
public bool BookingAllowed { get; init; }
|
||||
public required string Phone { get; init; }
|
||||
public required string PhoneHref { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required string EmailHref { get; init; }
|
||||
public required string CustomerSinceText { get; init; }
|
||||
public required string FactVisits { get; init; }
|
||||
public required string FactVisitsLabel { get; init; }
|
||||
public required string FactInterval { get; init; }
|
||||
public required string FactIntervalLabel { get; init; }
|
||||
public required string FactHairdresser { get; init; }
|
||||
public required string FactHairdresserLabel { get; init; }
|
||||
public required string FactRevenue { get; init; }
|
||||
public required string FactRevenueLabel { get; init; }
|
||||
public required string BookingAllowedText { get; init; }
|
||||
public required string BookingBlockedText { get; init; }
|
||||
}
|
||||
|
||||
public class CustomerTagViewModel
|
||||
{
|
||||
public required string Text { get; init; }
|
||||
public required string CssClass { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
@model PlanTempus.Application.Features.Customers.Components.CustomerDetailHeaderViewModel
|
||||
|
||||
<swp-customer-detail-header>
|
||||
<swp-customer-avatar-large>@Model.Initials</swp-customer-avatar-large>
|
||||
<swp-customer-detail-info>
|
||||
<swp-customer-name-row>
|
||||
<swp-customer-detail-name>@Model.Name</swp-customer-detail-name>
|
||||
<swp-customer-detail-tags>
|
||||
@foreach (var tag in Model.Tags)
|
||||
{
|
||||
<swp-tag class="@tag.CssClass">@tag.Text</swp-tag>
|
||||
}
|
||||
</swp-customer-detail-tags>
|
||||
<swp-booking-exclusion data-excluded="@(Model.BookingAllowed ? "false" : "true")">
|
||||
@if (Model.BookingAllowed)
|
||||
{
|
||||
<i class="ph ph-check icon"></i>
|
||||
<span>@Model.BookingAllowedText</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="ph ph-x icon"></i>
|
||||
<span>@Model.BookingBlockedText</span>
|
||||
}
|
||||
</swp-booking-exclusion>
|
||||
</swp-customer-name-row>
|
||||
<swp-contact-line>
|
||||
<a href="@Model.PhoneHref">@Model.Phone</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="@Model.EmailHref">@Model.Email</a>
|
||||
<span class="separator">|</span>
|
||||
<span>@Model.CustomerSinceText</span>
|
||||
</swp-contact-line>
|
||||
<swp-fact-boxes-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.FactVisits</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.FactVisitsLabel</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.FactInterval</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.FactIntervalLabel</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.FactHairdresser</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.FactHairdresserLabel</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.FactRevenue</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.FactRevenueLabel</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
</swp-fact-boxes-inline>
|
||||
</swp-customer-detail-info>
|
||||
</swp-customer-detail-header>
|
||||
Loading…
Add table
Add a link
Reference in a new issue