2026-01-11 11:17:51 +01:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2026-01-12 15:42:18 +01:00
|
|
|
using PlanTempus.Application.Features.Localization.Services;
|
2026-01-11 11:17:51 +01:00
|
|
|
|
|
|
|
|
namespace PlanTempus.Application.Features.Dashboard.Components;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// ViewComponent for rendering a stat card on the dashboard.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class StatCardViewComponent : ViewComponent
|
|
|
|
|
{
|
2026-01-12 15:42:18 +01:00
|
|
|
private readonly ILocalizationService _localization;
|
|
|
|
|
|
|
|
|
|
public StatCardViewComponent(ILocalizationService localization)
|
|
|
|
|
{
|
|
|
|
|
_localization = localization;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 11:17:51 +01:00
|
|
|
public IViewComponentResult Invoke(string key)
|
|
|
|
|
{
|
2026-01-12 15:42:18 +01:00
|
|
|
var model = StatCardCatalog.Get(key, _localization);
|
2026-01-11 11:17:51 +01:00
|
|
|
return View(model);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// ViewModel for the StatCard component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class StatCardViewModel
|
|
|
|
|
{
|
|
|
|
|
public required string Key { get; init; }
|
|
|
|
|
public required string Value { get; init; }
|
|
|
|
|
public required string Label { get; init; }
|
|
|
|
|
public string? TrendText { get; init; }
|
|
|
|
|
public string? TrendIcon { get; init; }
|
|
|
|
|
public string? TrendDirection { get; init; }
|
|
|
|
|
public string? Variant { get; init; }
|
|
|
|
|
public bool HasTrend => !string.IsNullOrEmpty(TrendText);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 15:42:18 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Internal data for stat cards (uses localization keys).
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class StatCardData
|
|
|
|
|
{
|
|
|
|
|
public required string Key { get; init; }
|
|
|
|
|
public required string Value { get; init; }
|
|
|
|
|
public required string LabelKey { get; init; }
|
|
|
|
|
public string? TrendTextKey { get; init; }
|
|
|
|
|
public string? TrendIcon { get; init; }
|
|
|
|
|
public string? TrendDirection { get; init; }
|
|
|
|
|
public string? Variant { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 11:17:51 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Catalog of available stat cards with their data.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class StatCardCatalog
|
|
|
|
|
{
|
2026-01-12 15:42:18 +01:00
|
|
|
private static readonly Dictionary<string, StatCardData> Cards = new()
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
2026-01-12 15:42:18 +01:00
|
|
|
["bookings-today"] = new StatCardData
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
|
|
|
|
Key = "bookings-today",
|
|
|
|
|
Value = "12",
|
2026-01-12 15:42:18 +01:00
|
|
|
LabelKey = "dashboard.stats.bookingsToday",
|
|
|
|
|
TrendTextKey = "dashboard.stats.bookingsTrend",
|
2026-01-11 11:17:51 +01:00
|
|
|
TrendIcon = "ph-check-circle",
|
|
|
|
|
TrendDirection = "up",
|
|
|
|
|
Variant = "highlight"
|
|
|
|
|
},
|
2026-01-12 15:42:18 +01:00
|
|
|
["expected-revenue"] = new StatCardData
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
|
|
|
|
Key = "expected-revenue",
|
|
|
|
|
Value = "8.450 kr",
|
2026-01-12 15:42:18 +01:00
|
|
|
LabelKey = "dashboard.stats.expectedRevenue",
|
|
|
|
|
TrendTextKey = "dashboard.stats.revenueTrend",
|
2026-01-11 11:17:51 +01:00
|
|
|
TrendIcon = "ph-trend-up",
|
|
|
|
|
TrendDirection = "up",
|
|
|
|
|
Variant = "success"
|
|
|
|
|
},
|
2026-01-12 15:42:18 +01:00
|
|
|
["occupancy-rate"] = new StatCardData
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
|
|
|
|
Key = "occupancy-rate",
|
|
|
|
|
Value = "78%",
|
2026-01-12 15:42:18 +01:00
|
|
|
LabelKey = "dashboard.stats.occupancyRate",
|
|
|
|
|
TrendTextKey = "dashboard.stats.occupancyTrend",
|
2026-01-11 11:17:51 +01:00
|
|
|
TrendIcon = "ph-trend-up",
|
|
|
|
|
TrendDirection = "up",
|
|
|
|
|
Variant = null
|
|
|
|
|
},
|
2026-01-12 15:42:18 +01:00
|
|
|
["needs-attention"] = new StatCardData
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
|
|
|
|
Key = "needs-attention",
|
|
|
|
|
Value = "4",
|
2026-01-12 15:42:18 +01:00
|
|
|
LabelKey = "dashboard.stats.needsAttention",
|
|
|
|
|
TrendTextKey = null,
|
2026-01-11 11:17:51 +01:00
|
|
|
TrendIcon = null,
|
|
|
|
|
TrendDirection = null,
|
|
|
|
|
Variant = "warning"
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-12 15:42:18 +01:00
|
|
|
public static StatCardViewModel Get(string key, ILocalizationService localization)
|
2026-01-11 11:17:51 +01:00
|
|
|
{
|
2026-01-12 15:42:18 +01:00
|
|
|
if (!Cards.TryGetValue(key, out var card))
|
|
|
|
|
throw new KeyNotFoundException($"StatCard with key '{key}' not found");
|
2026-01-11 11:17:51 +01:00
|
|
|
|
2026-01-12 15:42:18 +01:00
|
|
|
return new StatCardViewModel
|
|
|
|
|
{
|
|
|
|
|
Key = card.Key,
|
|
|
|
|
Value = card.Value,
|
|
|
|
|
Label = localization.Get(card.LabelKey),
|
|
|
|
|
TrendText = card.TrendTextKey != null ? localization.Get(card.TrendTextKey) : null,
|
|
|
|
|
TrendIcon = card.TrendIcon,
|
|
|
|
|
TrendDirection = card.TrendDirection,
|
|
|
|
|
Variant = card.Variant
|
|
|
|
|
};
|
2026-01-11 11:17:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IEnumerable<string> AllKeys => Cards.Keys;
|
|
|
|
|
}
|