163 lines
6.7 KiB
C#
163 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; }
|
||
|
|
}
|