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
301 lines
9.4 KiB
C#
301 lines
9.4 KiB
C#
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; }
|
|
}
|