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
162 lines
6.7 KiB
C#
162 lines
6.7 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using PlanTempus.Application.Features.Localization.Services;
|
|
|
|
namespace PlanTempus.Application.Features.Customers.Components;
|
|
|
|
public class CustomerDetailStatisticsViewComponent : ViewComponent
|
|
{
|
|
private readonly ILocalizationService _localization;
|
|
|
|
public CustomerDetailStatisticsViewComponent(ILocalizationService localization)
|
|
{
|
|
_localization = localization;
|
|
}
|
|
|
|
public IViewComponentResult Invoke(string customerId)
|
|
{
|
|
var customer = CustomerDetailCatalog.Get(customerId);
|
|
var stats = customer.Statistics;
|
|
|
|
// Calculate widths for the attendance bar
|
|
var total = stats.Attendance.Attended + stats.Attendance.Cancelled + stats.Attendance.NoShow;
|
|
|
|
string attendedWidth, cancelledWidth, noShowWidth;
|
|
bool allZero = total == 0;
|
|
|
|
if (allZero)
|
|
{
|
|
// All zero: gray bar with equal segments
|
|
attendedWidth = "33.33%";
|
|
cancelledWidth = "33.33%";
|
|
noShowWidth = "33.34%";
|
|
}
|
|
else
|
|
{
|
|
// Calculate percentages, but use 12px minimum for zero values
|
|
var attendedPercent = stats.Attendance.Attended * 100 / total;
|
|
var cancelledPercent = stats.Attendance.Cancelled * 100 / total;
|
|
var noShowPercent = stats.Attendance.NoShow * 100 / total;
|
|
|
|
attendedWidth = stats.Attendance.Attended == 0 ? "12px" : $"{attendedPercent}%";
|
|
cancelledWidth = stats.Attendance.Cancelled == 0 ? "12px" : $"{cancelledPercent}%";
|
|
noShowWidth = stats.Attendance.NoShow == 0 ? "12px" : $"{noShowPercent}%";
|
|
}
|
|
|
|
var model = new CustomerDetailStatisticsViewModel
|
|
{
|
|
// Attendance
|
|
AttendanceTitle = "Fremmøde & Pålidelighed",
|
|
Attended = stats.Attendance.Attended,
|
|
AttendedLabel = "Fremmøder",
|
|
Cancelled = stats.Attendance.Cancelled,
|
|
CancelledLabel = "Aflysninger",
|
|
NoShow = stats.Attendance.NoShow,
|
|
NoShowLabel = "No-shows",
|
|
ReliabilityPercent = stats.Attendance.ReliabilityPercent,
|
|
ReliabilityLabel = "Pålidelighed",
|
|
AttendedWidth = attendedWidth,
|
|
CancelledWidth = cancelledWidth,
|
|
NoShowWidth = noShowWidth,
|
|
AllZero = allZero,
|
|
|
|
// Service patterns
|
|
ServicePatternsTitle = "Service-mønstre",
|
|
TopServicesLabel = "Top 3 Services",
|
|
TopServices = stats.TopServices.Select((s, i) => new TopItemViewModel
|
|
{
|
|
Rank = i + 1,
|
|
Name = s.Name,
|
|
Count = s.Count
|
|
}).ToList(),
|
|
TopProductsLabel = "Top 3 Produkter",
|
|
TopProducts = stats.TopProducts.Select((p, i) => new TopItemViewModel
|
|
{
|
|
Rank = i + 1,
|
|
Name = p.Name,
|
|
Count = p.Count
|
|
}).ToList(),
|
|
|
|
// Booking behavior
|
|
BookingBehaviorTitle = "Booking-adfærd",
|
|
AvgBookingNotice = $"{stats.BookingBehavior.AvgBookingNoticeDays} dage",
|
|
AvgBookingNoticeLabel = "Gns. bookingvarsel",
|
|
PreferredDay = stats.BookingBehavior.PreferredDay,
|
|
PreferredDayLabel = "Foretrukken dag",
|
|
PreferredTimeSlot = stats.BookingBehavior.PreferredTimeSlot,
|
|
PreferredTimeSlotLabel = "Foretrukken tid",
|
|
OnlineBookingRate = $"{stats.BookingBehavior.OnlineBookingRate}%",
|
|
OnlineBookingRateLabel = "Online booking rate",
|
|
AvgCancellationNotice = $"{stats.BookingBehavior.AvgCancellationNoticeDays} dage",
|
|
AvgCancellationNoticeLabel = "Gns. aflysningsvarsel",
|
|
|
|
// Loyalty
|
|
LoyaltyTitle = "Loyalitet",
|
|
CustomerSinceYears = $"{stats.Loyalty.CustomerSinceYears:0.0} ar".Replace(".", ","),
|
|
CustomerSinceYearsLabel = "Kunde siden",
|
|
DaysSinceLastVisit = stats.Loyalty.DaysSinceLastVisit,
|
|
DaysSinceLastVisitLabel = "Dage siden sidst",
|
|
ChurnRisk = stats.Loyalty.ChurnRisk,
|
|
ChurnRiskLabel = "Churn-risiko",
|
|
AvgIntervalDays = $"{stats.Loyalty.AvgIntervalDays} dage",
|
|
AvgIntervalDaysLabel = "Gns. interval"
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
public class CustomerDetailStatisticsViewModel
|
|
{
|
|
// Attendance
|
|
public required string AttendanceTitle { get; init; }
|
|
public int Attended { get; init; }
|
|
public required string AttendedLabel { get; init; }
|
|
public int Cancelled { get; init; }
|
|
public required string CancelledLabel { get; init; }
|
|
public int NoShow { get; init; }
|
|
public required string NoShowLabel { get; init; }
|
|
public int ReliabilityPercent { get; init; }
|
|
public required string ReliabilityLabel { get; init; }
|
|
public required string AttendedWidth { get; init; }
|
|
public required string CancelledWidth { get; init; }
|
|
public required string NoShowWidth { get; init; }
|
|
public bool AllZero { get; init; }
|
|
|
|
// Service patterns
|
|
public required string ServicePatternsTitle { get; init; }
|
|
public required string TopServicesLabel { get; init; }
|
|
public List<TopItemViewModel> TopServices { get; init; } = new();
|
|
public required string TopProductsLabel { get; init; }
|
|
public List<TopItemViewModel> TopProducts { get; init; } = new();
|
|
|
|
// Booking behavior
|
|
public required string BookingBehaviorTitle { get; init; }
|
|
public required string AvgBookingNotice { get; init; }
|
|
public required string AvgBookingNoticeLabel { get; init; }
|
|
public required string PreferredDay { get; init; }
|
|
public required string PreferredDayLabel { get; init; }
|
|
public required string PreferredTimeSlot { get; init; }
|
|
public required string PreferredTimeSlotLabel { get; init; }
|
|
public required string OnlineBookingRate { get; init; }
|
|
public required string OnlineBookingRateLabel { get; init; }
|
|
public required string AvgCancellationNotice { get; init; }
|
|
public required string AvgCancellationNoticeLabel { get; init; }
|
|
|
|
// Loyalty
|
|
public required string LoyaltyTitle { get; init; }
|
|
public required string CustomerSinceYears { get; init; }
|
|
public required string CustomerSinceYearsLabel { get; init; }
|
|
public int DaysSinceLastVisit { get; init; }
|
|
public required string DaysSinceLastVisitLabel { get; init; }
|
|
public required string ChurnRisk { get; init; }
|
|
public required string ChurnRiskLabel { get; init; }
|
|
public required string AvgIntervalDays { get; init; }
|
|
public required string AvgIntervalDaysLabel { get; init; }
|
|
}
|
|
|
|
public class TopItemViewModel
|
|
{
|
|
public int Rank { get; init; }
|
|
public required string Name { get; init; }
|
|
public int Count { get; init; }
|
|
}
|