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:
parent
38e9243bcd
commit
1b25978d9b
26 changed files with 3792 additions and 956 deletions
|
|
@ -0,0 +1,169 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Customers.Components;
|
||||
|
||||
public class CustomerDetailOverviewViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public CustomerDetailOverviewViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string customerId)
|
||||
{
|
||||
var customer = CustomerDetailCatalog.Get(customerId);
|
||||
|
||||
var model = new CustomerDetailOverviewViewModel
|
||||
{
|
||||
// Contact
|
||||
ContactTitle = _localization.Get("customers.detail.contactInfo"),
|
||||
Phone = customer.Contact.Phone,
|
||||
PhoneLabel = _localization.Get("customers.detail.phone"),
|
||||
Email = customer.Contact.Email,
|
||||
EmailLabel = _localization.Get("customers.detail.email"),
|
||||
Address = customer.Contact.Address,
|
||||
AddressLabel = _localization.Get("customers.detail.address"),
|
||||
ZipCity = $"{customer.Contact.Zip} {customer.Contact.City}",
|
||||
ZipCityLabel = _localization.Get("customers.detail.zipCity"),
|
||||
|
||||
// Profile
|
||||
ProfileTitle = _localization.Get("customers.detail.profile"),
|
||||
ProfileItems = customer.Profile.Select(p => new ProfileItemViewModel
|
||||
{
|
||||
Title = p.Title,
|
||||
Value = p.Value
|
||||
}).ToList(),
|
||||
|
||||
// Marketing
|
||||
MarketingTitle = _localization.Get("customers.detail.marketing"),
|
||||
EmailMarketingLabel = _localization.Get("customers.detail.emailMarketing"),
|
||||
EmailOptIn = customer.Marketing.EmailOptIn,
|
||||
SmsMarketingLabel = _localization.Get("customers.detail.smsMarketing"),
|
||||
SmsOptIn = customer.Marketing.SmsOptIn,
|
||||
YesLabel = _localization.Get("common.yes"),
|
||||
NoLabel = _localization.Get("common.no"),
|
||||
|
||||
// Payment
|
||||
PaymentTitle = _localization.Get("customers.detail.paymentSettings"),
|
||||
RequirePrepaymentLabel = _localization.Get("customers.detail.requirePrepayment"),
|
||||
RequirePrepaymentDesc = "Kunden skal betale fuldt beløb ved booking",
|
||||
RequirePrepayment = customer.Payment.RequirePrepayment,
|
||||
AllowPartialPaymentLabel = _localization.Get("customers.detail.allowPartialPayment"),
|
||||
AllowPartialPaymentDesc = "Kunden kan vælge at betale et depositum",
|
||||
AllowPartialPayment = customer.Payment.AllowPartialPayment,
|
||||
|
||||
// Preferences
|
||||
PreferencesTitle = _localization.Get("customers.detail.preferences"),
|
||||
PreferredHairdresser = customer.Preferences.PreferredHairdresser,
|
||||
PreferredHairdresserLabel = _localization.Get("customers.detail.preferredHairdresser"),
|
||||
PreferredDays = customer.Preferences.PreferredDays,
|
||||
PreferredDaysLabel = _localization.Get("customers.detail.preferredDay"),
|
||||
SpecialRequests = customer.Preferences.SpecialRequests,
|
||||
SpecialRequestsLabel = _localization.Get("customers.detail.specialRequests"),
|
||||
|
||||
// Warnings
|
||||
WarningsTitle = _localization.Get("customers.detail.warnings"),
|
||||
Warnings = customer.Warnings.Select(w => new WarningItemViewModel
|
||||
{
|
||||
Title = w.Title,
|
||||
Value = w.Value
|
||||
}).ToList(),
|
||||
|
||||
// Group & Relations
|
||||
GroupRelationsTitle = _localization.Get("customers.detail.groupAndRelations"),
|
||||
GroupLabel = "Kundegruppe:",
|
||||
GroupId = customer.Group.GroupId,
|
||||
GroupName = customer.Group.GroupName,
|
||||
Relations = customer.Relations.Select(r => new RelationItemViewModel
|
||||
{
|
||||
Id = r.Id,
|
||||
Name = r.Name,
|
||||
Initials = r.Initials,
|
||||
Type = r.Type
|
||||
}).ToList(),
|
||||
AddRelationText = "Tilføj relation"
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomerDetailOverviewViewModel
|
||||
{
|
||||
// Contact
|
||||
public required string ContactTitle { get; init; }
|
||||
public required string Phone { get; init; }
|
||||
public required string PhoneLabel { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required string EmailLabel { get; init; }
|
||||
public required string Address { get; init; }
|
||||
public required string AddressLabel { get; init; }
|
||||
public required string ZipCity { get; init; }
|
||||
public required string ZipCityLabel { get; init; }
|
||||
|
||||
// Profile
|
||||
public required string ProfileTitle { get; init; }
|
||||
public List<ProfileItemViewModel> ProfileItems { get; init; } = new();
|
||||
|
||||
// Marketing
|
||||
public required string MarketingTitle { get; init; }
|
||||
public required string EmailMarketingLabel { get; init; }
|
||||
public bool EmailOptIn { get; init; }
|
||||
public required string SmsMarketingLabel { get; init; }
|
||||
public bool SmsOptIn { get; init; }
|
||||
public required string YesLabel { get; init; }
|
||||
public required string NoLabel { get; init; }
|
||||
|
||||
// Payment
|
||||
public required string PaymentTitle { get; init; }
|
||||
public required string RequirePrepaymentLabel { get; init; }
|
||||
public required string RequirePrepaymentDesc { get; init; }
|
||||
public bool RequirePrepayment { get; init; }
|
||||
public required string AllowPartialPaymentLabel { get; init; }
|
||||
public required string AllowPartialPaymentDesc { get; init; }
|
||||
public bool AllowPartialPayment { get; init; }
|
||||
|
||||
// Preferences
|
||||
public required string PreferencesTitle { get; init; }
|
||||
public required string PreferredHairdresser { get; init; }
|
||||
public required string PreferredHairdresserLabel { get; init; }
|
||||
public required string PreferredDays { get; init; }
|
||||
public required string PreferredDaysLabel { get; init; }
|
||||
public required string SpecialRequests { get; init; }
|
||||
public required string SpecialRequestsLabel { get; init; }
|
||||
|
||||
// Warnings
|
||||
public required string WarningsTitle { get; init; }
|
||||
public List<WarningItemViewModel> Warnings { get; init; } = new();
|
||||
|
||||
// Group & Relations
|
||||
public required string GroupRelationsTitle { get; init; }
|
||||
public required string GroupLabel { get; init; }
|
||||
public required string GroupId { get; init; }
|
||||
public required string GroupName { get; init; }
|
||||
public List<RelationItemViewModel> Relations { get; init; } = new();
|
||||
public required string AddRelationText { get; init; }
|
||||
}
|
||||
|
||||
public class ProfileItemViewModel
|
||||
{
|
||||
public required string Title { get; init; }
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
|
||||
public class WarningItemViewModel
|
||||
{
|
||||
public required string Title { get; init; }
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
|
||||
public class RelationItemViewModel
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Initials { get; init; }
|
||||
public required string Type { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
@model PlanTempus.Application.Features.Customers.Components.CustomerDetailOverviewViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<!-- Left Column -->
|
||||
<swp-card-column>
|
||||
<!-- Kontaktoplysninger -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-address-book"></i>
|
||||
<span>@Model.ContactTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.PhoneLabel</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Phone</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.EmailLabel</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Email</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.AddressLabel</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Address</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.ZipCityLabel</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.ZipCity</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Profil -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-user-circle"></i>
|
||||
<span>@Model.ProfileTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-profile-boxes>
|
||||
@foreach (var item in Model.ProfileItems)
|
||||
{
|
||||
<swp-profile-box>
|
||||
<swp-profile-box-label>@item.Title</swp-profile-box-label>
|
||||
<swp-profile-box-value>@item.Value</swp-profile-box-value>
|
||||
</swp-profile-box>
|
||||
}
|
||||
</swp-profile-boxes>
|
||||
</swp-card>
|
||||
</swp-card-column>
|
||||
|
||||
<!-- Right Column -->
|
||||
<swp-card-column>
|
||||
<!-- Marketing -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-megaphone"></i>
|
||||
<span>@Model.MarketingTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.EmailMarketingLabel</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="@(Model.EmailOptIn ? "yes" : "no")">
|
||||
<swp-toggle-option>@Model.YesLabel</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.NoLabel</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.SmsMarketingLabel</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="@(Model.SmsOptIn ? "yes" : "no")">
|
||||
<swp-toggle-option>@Model.YesLabel</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.NoLabel</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
</swp-card>
|
||||
|
||||
<!-- Betalingsindstillinger -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-credit-card"></i>
|
||||
<span>@Model.PaymentTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-info>
|
||||
<swp-toggle-label>@Model.RequirePrepaymentLabel</swp-toggle-label>
|
||||
<swp-toggle-desc>@Model.RequirePrepaymentDesc</swp-toggle-desc>
|
||||
</swp-toggle-info>
|
||||
<swp-toggle-slider data-value="@(Model.RequirePrepayment ? "yes" : "no")">
|
||||
<swp-toggle-option>@Model.YesLabel</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.NoLabel</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-info>
|
||||
<swp-toggle-label>@Model.AllowPartialPaymentLabel</swp-toggle-label>
|
||||
<swp-toggle-desc>@Model.AllowPartialPaymentDesc</swp-toggle-desc>
|
||||
</swp-toggle-info>
|
||||
<swp-toggle-slider data-value="@(Model.AllowPartialPayment ? "yes" : "no")">
|
||||
<swp-toggle-option>@Model.YesLabel</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.NoLabel</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
</swp-card>
|
||||
|
||||
<!-- Praeferencer -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-sliders-horizontal"></i>
|
||||
<span>@Model.PreferencesTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-profile-boxes>
|
||||
<swp-profile-box>
|
||||
<swp-profile-box-label>@Model.PreferredHairdresserLabel</swp-profile-box-label>
|
||||
<swp-profile-box-value>@Model.PreferredHairdresser</swp-profile-box-value>
|
||||
</swp-profile-box>
|
||||
<swp-profile-box>
|
||||
<swp-profile-box-label>@Model.PreferredDaysLabel</swp-profile-box-label>
|
||||
<swp-profile-box-value>@Model.PreferredDays</swp-profile-box-value>
|
||||
</swp-profile-box>
|
||||
<swp-profile-box class="full-width">
|
||||
<swp-profile-box-label>@Model.SpecialRequestsLabel</swp-profile-box-label>
|
||||
<swp-profile-box-value>@Model.SpecialRequests</swp-profile-box-value>
|
||||
</swp-profile-box>
|
||||
</swp-profile-boxes>
|
||||
</swp-card>
|
||||
|
||||
<!-- Advarsler -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-warning"></i>
|
||||
<span>@Model.WarningsTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-profile-boxes>
|
||||
@foreach (var warning in Model.Warnings)
|
||||
{
|
||||
<swp-profile-box class="warning full-width">
|
||||
<swp-profile-box-label>@warning.Title</swp-profile-box-label>
|
||||
<swp-profile-box-value>@warning.Value</swp-profile-box-value>
|
||||
</swp-profile-box>
|
||||
}
|
||||
</swp-profile-boxes>
|
||||
</swp-card>
|
||||
|
||||
<!-- Kundegruppe & Relationer -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-users-three"></i>
|
||||
<span>@Model.GroupRelationsTitle</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-customer-group-row>
|
||||
<swp-customer-group-label>@Model.GroupLabel</swp-customer-group-label>
|
||||
<swp-select>
|
||||
<button type="button">
|
||||
<swp-select-value>@Model.GroupName</swp-select-value>
|
||||
<i class="ph ph-caret-down"></i>
|
||||
</button>
|
||||
<swp-select-dropdown>
|
||||
<swp-select-option class="@(Model.GroupId == "standard" ? "selected" : "")" data-value="standard">Standard</swp-select-option>
|
||||
<swp-select-option class="@(Model.GroupId == "premium" ? "selected" : "")" data-value="premium">Premium</swp-select-option>
|
||||
<swp-select-option class="@(Model.GroupId == "erhverv" ? "selected" : "")" data-value="erhverv">Erhverv</swp-select-option>
|
||||
<swp-select-option class="@(Model.GroupId == "medarbejder" ? "selected" : "")" data-value="medarbejder">Medarbejder</swp-select-option>
|
||||
<swp-select-option class="@(Model.GroupId == "familie" ? "selected" : "")" data-value="familie">Familie & Venner</swp-select-option>
|
||||
</swp-select-dropdown>
|
||||
</swp-select>
|
||||
</swp-customer-group-row>
|
||||
|
||||
<swp-relations-list>
|
||||
@foreach (var relation in Model.Relations)
|
||||
{
|
||||
<swp-relation-item>
|
||||
<swp-relation-avatar>@relation.Initials</swp-relation-avatar>
|
||||
<swp-relation-info>
|
||||
<swp-relation-name>@relation.Name</swp-relation-name>
|
||||
<swp-relation-type>@relation.Type</swp-relation-type>
|
||||
</swp-relation-info>
|
||||
<swp-relation-actions>
|
||||
<swp-relation-link>Abn</swp-relation-link>
|
||||
<swp-relation-remove>×</swp-relation-remove>
|
||||
</swp-relation-actions>
|
||||
</swp-relation-item>
|
||||
}
|
||||
|
||||
<swp-add-relation>
|
||||
<i class="ph ph-plus"></i>
|
||||
<span>@Model.AddRelationText</span>
|
||||
</swp-add-relation>
|
||||
</swp-relations-list>
|
||||
</swp-card>
|
||||
</swp-card-column>
|
||||
</swp-detail-grid>
|
||||
Loading…
Add table
Add a link
Reference in a new issue