Adds comprehensive customers list and management components
Introduces customer-related view components for table and row display Implements mock data loading and customer list rendering Adds localization support for customer-related text Enhances UI with detailed customer information and interactions
This commit is contained in:
parent
cd7acaf490
commit
6ef001e35f
11 changed files with 869 additions and 675 deletions
|
|
@ -0,0 +1,139 @@
|
|||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Customers.Components;
|
||||
|
||||
public class CustomerTableViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
public CustomerTableViewComponent(ILocalizationService localization, IWebHostEnvironment env)
|
||||
{
|
||||
_localization = localization;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke()
|
||||
{
|
||||
var data = LoadCustomerData();
|
||||
var model = new CustomerTableViewModel
|
||||
{
|
||||
SearchPlaceholder = _localization.Get("customers.searchPlaceholder"),
|
||||
ExportButtonText = _localization.Get("customers.export"),
|
||||
CreateButtonText = _localization.Get("customers.create"),
|
||||
ColumnName = _localization.Get("customers.column.name"),
|
||||
ColumnPhone = _localization.Get("customers.column.phone"),
|
||||
ColumnEmail = _localization.Get("customers.column.email"),
|
||||
ColumnVisits = _localization.Get("customers.column.visits"),
|
||||
ColumnLastVisit = _localization.Get("customers.column.lastVisit"),
|
||||
ColumnHairdresser = _localization.Get("customers.column.hairdresser"),
|
||||
ColumnCreated = _localization.Get("customers.column.created"),
|
||||
ColumnTags = _localization.Get("customers.column.tags"),
|
||||
EmptySearchText = _localization.Get("customers.emptySearch"),
|
||||
Customers = data.Customers
|
||||
.OrderBy(c => c.FirstName)
|
||||
.ThenBy(c => c.LastName)
|
||||
.Select(c => new CustomerItemViewModel
|
||||
{
|
||||
Id = c.Id,
|
||||
FullName = $"{c.FirstName} {c.LastName}",
|
||||
Initials = c.Initials,
|
||||
Phone = c.Phone,
|
||||
Email = c.Email,
|
||||
Visits = c.Visits,
|
||||
LastVisit = FormatLastVisit(c.LastVisit),
|
||||
PreferredHairdresser = c.PreferredHairdresser,
|
||||
CreatedAt = FormatCreatedAt(c.CreatedAt),
|
||||
Tags = c.Tags,
|
||||
AvatarColor = c.AvatarColor
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private CustomerMockData LoadCustomerData()
|
||||
{
|
||||
var jsonPath = Path.Combine(_env.ContentRootPath, "Features", "Customers", "Data", "customersMock.json");
|
||||
var json = System.IO.File.ReadAllText(jsonPath);
|
||||
return JsonSerializer.Deserialize<CustomerMockData>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
}) ?? new CustomerMockData();
|
||||
}
|
||||
|
||||
private static string FormatLastVisit(string dateStr)
|
||||
{
|
||||
if (DateTime.TryParse(dateStr, out var date))
|
||||
{
|
||||
return date.ToString("d. MMM", new CultureInfo("da-DK")).TrimEnd('.');
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
private static string FormatCreatedAt(string dateStr)
|
||||
{
|
||||
if (DateTime.TryParse(dateStr, out var date))
|
||||
{
|
||||
return date.ToString("MMM yyyy", new CultureInfo("da-DK"));
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomerTableViewModel
|
||||
{
|
||||
public required string SearchPlaceholder { get; init; }
|
||||
public required string ExportButtonText { get; init; }
|
||||
public required string CreateButtonText { get; init; }
|
||||
public required string ColumnName { get; init; }
|
||||
public required string ColumnPhone { get; init; }
|
||||
public required string ColumnEmail { get; init; }
|
||||
public required string ColumnVisits { get; init; }
|
||||
public required string ColumnLastVisit { get; init; }
|
||||
public required string ColumnHairdresser { get; init; }
|
||||
public required string ColumnCreated { get; init; }
|
||||
public required string ColumnTags { get; init; }
|
||||
public required string EmptySearchText { get; init; }
|
||||
public required IReadOnlyList<CustomerItemViewModel> Customers { get; init; }
|
||||
}
|
||||
|
||||
public class CustomerItemViewModel
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string FullName { get; init; }
|
||||
public required string Initials { get; init; }
|
||||
public required string Phone { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public int Visits { get; init; }
|
||||
public required string LastVisit { get; init; }
|
||||
public required string PreferredHairdresser { get; init; }
|
||||
public required string CreatedAt { get; init; }
|
||||
public required IReadOnlyList<string> Tags { get; init; }
|
||||
public string? AvatarColor { get; init; }
|
||||
}
|
||||
|
||||
internal class CustomerMockData
|
||||
{
|
||||
public List<CustomerData> Customers { get; set; } = new();
|
||||
}
|
||||
|
||||
internal class CustomerData
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string FirstName { get; set; } = "";
|
||||
public string LastName { get; set; } = "";
|
||||
public string Initials { get; set; } = "";
|
||||
public string Phone { get; set; } = "";
|
||||
public string Email { get; set; } = "";
|
||||
public int Visits { get; set; }
|
||||
public string LastVisit { get; set; } = "";
|
||||
public string PreferredHairdresser { get; set; } = "";
|
||||
public string CreatedAt { get; set; } = "";
|
||||
public List<string> Tags { get; set; } = new();
|
||||
public string? AvatarColor { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
@model PlanTempus.Application.Features.Customers.Components.CustomerTableViewModel
|
||||
|
||||
<swp-action-bar>
|
||||
<swp-search-input>
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="text" id="searchInput" placeholder="@Model.SearchPlaceholder" />
|
||||
</swp-search-input>
|
||||
<swp-btn-group>
|
||||
<swp-btn class="secondary">
|
||||
<i class="ph ph-export"></i>
|
||||
<span>@Model.ExportButtonText</span>
|
||||
</swp-btn>
|
||||
<swp-btn class="primary">
|
||||
<i class="ph ph-plus"></i>
|
||||
<span>@Model.CreateButtonText</span>
|
||||
</swp-btn>
|
||||
</swp-btn-group>
|
||||
</swp-action-bar>
|
||||
|
||||
<swp-card class="customers-list">
|
||||
<swp-data-table>
|
||||
<swp-data-table-header>
|
||||
<swp-data-table-cell>@Model.ColumnName</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnPhone</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnEmail</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnVisits</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnLastVisit</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnHairdresser</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnCreated</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.ColumnTags</swp-data-table-cell>
|
||||
</swp-data-table-header>
|
||||
@foreach (var customer in Model.Customers)
|
||||
{
|
||||
@await Component.InvokeAsync("CustomerRow", customer)
|
||||
}
|
||||
</swp-data-table>
|
||||
|
||||
<swp-empty-state id="emptyState" style="display: none;">
|
||||
<i class="ph ph-users"></i>
|
||||
<span>@Model.EmptySearchText</span>
|
||||
</swp-empty-state>
|
||||
</swp-card>
|
||||
Loading…
Add table
Add a link
Reference in a new issue