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,123 @@
|
|||
@model PlanTempus.Application.Features.Customers.Components.CustomerDetailViewViewModel
|
||||
|
||||
<swp-customer-detail-view id="customer-detail-view" data-customer="@Model.CustomerId">
|
||||
<!-- Sticky Header (generic from page.css) -->
|
||||
<swp-sticky-header>
|
||||
<swp-header-content>
|
||||
<!-- Page Header with Back Button -->
|
||||
<swp-page-header>
|
||||
<swp-back-link href="/kunder">
|
||||
<i class="ph ph-arrow-left"></i>
|
||||
<span>@Model.BackText</span>
|
||||
</swp-back-link>
|
||||
<swp-page-actions>
|
||||
<swp-btn class="secondary">
|
||||
<i class="ph ph-trash"></i>
|
||||
<span>@Model.DeleteButtonText</span>
|
||||
</swp-btn>
|
||||
<swp-btn class="primary">
|
||||
<i class="ph ph-floppy-disk"></i>
|
||||
<span>@Model.SaveButtonText</span>
|
||||
</swp-btn>
|
||||
</swp-page-actions>
|
||||
</swp-page-header>
|
||||
|
||||
<!-- Customer Header -->
|
||||
@await Component.InvokeAsync("CustomerDetailHeader", Model.CustomerId)
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tabs (outside header-content, inside sticky-header) -->
|
||||
<swp-tab-bar>
|
||||
<swp-tab class="active" data-tab="overview">@Model.TabOverview</swp-tab>
|
||||
<swp-tab data-tab="economy">@Model.TabEconomy</swp-tab>
|
||||
<swp-tab data-tab="statistics">@Model.TabStatistics</swp-tab>
|
||||
<swp-tab data-tab="journal">@Model.TabJournal</swp-tab>
|
||||
<swp-tab data-tab="appointments">@Model.TabAppointments</swp-tab>
|
||||
<swp-tab data-tab="giftcards">@Model.TabGiftcards</swp-tab>
|
||||
<swp-tab data-tab="activity">@Model.TabActivity</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-sticky-header>
|
||||
|
||||
<!-- Tab Contents -->
|
||||
<swp-tab-content data-tab="overview" class="active">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailOverview", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="economy">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailEconomy", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="statistics">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailStatistics", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="journal">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailJournal", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="appointments">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailAppointments", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="giftcards">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailGiftcards", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="activity">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("CustomerDetailActivity", Model.CustomerId)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
</swp-customer-detail-view>
|
||||
|
||||
<script>
|
||||
// Tab switching
|
||||
document.querySelectorAll('#customer-detail-view swp-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const tabName = tab.dataset.tab;
|
||||
const container = document.getElementById('customer-detail-view');
|
||||
|
||||
// Update tab active state
|
||||
container.querySelectorAll('swp-tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
// Update content visibility
|
||||
container.querySelectorAll('swp-tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
if (content.dataset.tab === tabName) {
|
||||
content.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Booking exclusion toggle (feature-specific)
|
||||
const bookingExclusion = document.querySelector('swp-booking-exclusion');
|
||||
if (bookingExclusion) {
|
||||
bookingExclusion.addEventListener('click', () => {
|
||||
const isExcluded = bookingExclusion.dataset.excluded === 'true';
|
||||
bookingExclusion.dataset.excluded = isExcluded ? 'false' : 'true';
|
||||
const icon = bookingExclusion.querySelector('.icon');
|
||||
const text = bookingExclusion.querySelector('span:not(.icon)');
|
||||
if (isExcluded) {
|
||||
icon.className = 'ph ph-check icon';
|
||||
text.textContent = 'Booking tilladt';
|
||||
} else {
|
||||
icon.className = 'ph ph-x icon';
|
||||
text.textContent = 'Booking blokeret';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue