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:
Janus C. H. Knudsen 2026-01-25 01:55:41 +01:00
parent 38e9243bcd
commit 1b25978d9b
26 changed files with 3792 additions and 956 deletions

View file

@ -0,0 +1,301 @@
using System.Text.Json;
namespace PlanTempus.Application.Features.Customers.Components;
/// <summary>
/// Shared catalog for customer detail data.
/// Loads from customerDetailMock.json and used by all CustomerDetail* ViewComponents.
/// </summary>
public static class CustomerDetailCatalog
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
private static Dictionary<string, CustomerDetailRecord>? _customers;
private static Dictionary<string, CustomerDetailRecord> Customers
{
get
{
if (_customers == null)
{
var jsonPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Features", "Customers", "Data", "customerDetailMock.json");
var json = File.ReadAllText(jsonPath);
_customers = JsonSerializer.Deserialize<Dictionary<string, CustomerDetailRecord>>(json, JsonOptions)
?? new Dictionary<string, CustomerDetailRecord>();
}
return _customers;
}
}
public static CustomerDetailRecord Get(string customerId)
{
if (!Customers.TryGetValue(customerId, out var customer))
throw new KeyNotFoundException($"Customer with id '{customerId}' not found");
return customer;
}
public static IEnumerable<string> AllIds => Customers.Keys;
}
// Root record for customer detail
public record CustomerDetailRecord
{
public required string Id { get; init; }
public required CustomerHeaderRecord Header { get; init; }
public required CustomerContactRecord Contact { get; init; }
public List<CustomerProfileItem> Profile { get; init; } = new();
public required CustomerMarketingRecord Marketing { get; init; }
public required CustomerPaymentRecord Payment { get; init; }
public required CustomerPreferencesRecord Preferences { get; init; }
public List<CustomerWarningItem> Warnings { get; init; } = new();
public required CustomerGroupRecord Group { get; init; }
public List<CustomerRelationRecord> Relations { get; init; } = new();
public required CustomerStatisticsRecord Statistics { get; init; }
public List<CustomerJournalEntry> Journal { get; init; } = new();
public required CustomerAppointmentsRecord Appointments { get; init; }
public required CustomerGiftcardsRecord Giftcards { get; init; }
public List<CustomerActivityEntry> Activity { get; init; } = new();
public CustomerEconomyRecord? Economy { get; init; }
}
// Header section
public record CustomerHeaderRecord
{
public required string Initials { get; init; }
public required string Name { get; init; }
public required string CustomerSince { get; init; }
public List<string> Tags { get; init; } = new();
public bool BookingAllowed { get; init; }
public required CustomerFactsRecord Facts { get; init; }
}
public record CustomerFactsRecord
{
public int Visits { get; init; }
public int AvgIntervalDays { get; init; }
public required string PreferredHairdresser { get; init; }
public decimal TotalRevenue { get; init; }
}
// Contact section
public record CustomerContactRecord
{
public required string Phone { get; init; }
public required string Email { get; init; }
public required string Address { get; init; }
public required string Zip { get; init; }
public required string City { get; init; }
}
// Profile item (flexible)
public record CustomerProfileItem
{
public required string Title { get; init; }
public required string Value { get; init; }
}
// Marketing settings
public record CustomerMarketingRecord
{
public bool EmailOptIn { get; init; }
public bool SmsOptIn { get; init; }
}
// Payment settings
public record CustomerPaymentRecord
{
public bool RequirePrepayment { get; init; }
public bool AllowPartialPayment { get; init; }
}
// Preferences
public record CustomerPreferencesRecord
{
public required string PreferredHairdresser { get; init; }
public required string PreferredDays { get; init; }
public required string SpecialRequests { get; init; }
}
// Warning item (flexible)
public record CustomerWarningItem
{
public required string Title { get; init; }
public required string Value { get; init; }
}
// Group
public record CustomerGroupRecord
{
public required string GroupId { get; init; }
public required string GroupName { get; init; }
}
// Relation
public record CustomerRelationRecord
{
public required string Id { get; init; }
public required string Name { get; init; }
public required string Initials { get; init; }
public required string Type { get; init; }
}
// Statistics
public record CustomerStatisticsRecord
{
public required CustomerAttendanceRecord Attendance { get; init; }
public List<CustomerTopItem> TopServices { get; init; } = new();
public List<CustomerTopItem> TopProducts { get; init; } = new();
public required CustomerBookingBehaviorRecord BookingBehavior { get; init; }
public required CustomerLoyaltyRecord Loyalty { get; init; }
}
public record CustomerAttendanceRecord
{
public int Attended { get; init; }
public int Cancelled { get; init; }
public int NoShow { get; init; }
public int ReliabilityPercent { get; init; }
}
public record CustomerTopItem
{
public required string Name { get; init; }
public int Count { get; init; }
}
public record CustomerBookingBehaviorRecord
{
public int AvgBookingNoticeDays { get; init; }
public required string PreferredDay { get; init; }
public required string PreferredTimeSlot { get; init; }
public int OnlineBookingRate { get; init; }
public int AvgCancellationNoticeDays { get; init; }
}
public record CustomerLoyaltyRecord
{
public double CustomerSinceYears { get; init; }
public int DaysSinceLastVisit { get; init; }
public required string ChurnRisk { get; init; }
public int AvgIntervalDays { get; init; }
}
// Journal
public record CustomerJournalEntry
{
public required string Id { get; init; }
public required string Type { get; init; }
public required string Tag { get; init; }
public List<string> Subtags { get; init; } = new();
public required string Text { get; init; }
public required string Date { get; init; }
public required string Author { get; init; }
}
// Appointments
public record CustomerAppointmentsRecord
{
public List<CustomerUpcomingAppointment> Upcoming { get; init; } = new();
public List<CustomerHistoryAppointment> History { get; init; } = new();
}
public record CustomerUpcomingAppointment
{
public required string Date { get; init; }
public required string Time { get; init; }
public required string Service { get; init; }
public required string Hairdresser { get; init; }
public required string Duration { get; init; }
}
public record CustomerHistoryAppointment
{
public required string Date { get; init; }
public required string Service { get; init; }
public required string Hairdresser { get; init; }
public required string Duration { get; init; }
public decimal Price { get; init; }
}
// Giftcards
public record CustomerGiftcardsRecord
{
public List<CustomerGiftcardItem> Active { get; init; } = new();
public List<CustomerGiftcardItem> Expired { get; init; } = new();
}
public record CustomerGiftcardItem
{
public required string Id { get; init; }
public required string Type { get; init; }
public required string Label { get; init; }
public decimal? OriginalValue { get; init; }
public decimal? CurrentBalance { get; init; }
public int? TotalPunches { get; init; }
public int? UsedPunches { get; init; }
public string? ExpiresAt { get; init; }
}
// Activity
public record CustomerActivityEntry
{
public required string Date { get; init; }
public required string Time { get; init; }
public required string Type { get; init; }
public required string Icon { get; init; }
public required string Title { get; init; }
public string? Actor { get; init; }
public List<string> Badges { get; init; } = new();
}
// Economy
public record CustomerEconomyRecord
{
public required CustomerYearRevenue CurrentYear { get; init; }
public required CustomerYearRevenue LastYear { get; init; }
public decimal AvgPerVisit { get; init; }
public decimal AvgPerMonth { get; init; }
public CustomerChartData? ChartData { get; init; }
public List<CustomerPurchase> Purchases { get; init; } = new();
}
public record CustomerChartData
{
public List<string> Categories { get; init; } = new();
public List<CustomerChartSeries> Series { get; init; } = new();
}
public record CustomerChartSeries
{
public required string Name { get; init; }
public required string Color { get; init; }
public List<CustomerChartDataPoint> Data { get; init; } = new();
}
public record CustomerChartDataPoint
{
public required string X { get; init; }
public decimal Y { get; init; }
}
public record CustomerYearRevenue
{
public int Year { get; init; }
public decimal Total { get; init; }
}
public record CustomerPurchase
{
public required string Invoice { get; init; }
public required string Date { get; init; }
public required string Time { get; init; }
public required string Employee { get; init; }
public required string Services { get; init; }
public required string Type { get; init; }
public decimal Amount { get; init; }
}