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
|
|
@ -617,68 +617,6 @@ swp-profile-box.full-width {
|
|||
grid-column: span 2;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CUSTOMER DETAIL - APPOINTMENTS TABLE
|
||||
=========================================== */
|
||||
swp-card.customer-appointments {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-card.customer-appointments swp-card-header {
|
||||
padding: var(--spacing-6);
|
||||
}
|
||||
|
||||
swp-card.customer-appointments swp-data-table {
|
||||
grid-template-columns: 80px 60px 1fr 100px 110px;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CUSTOMER DETAIL - GIFTCARDS TABLE
|
||||
=========================================== */
|
||||
swp-card.customer-giftcards {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-card.customer-giftcards swp-card-header {
|
||||
padding: var(--spacing-6);
|
||||
}
|
||||
|
||||
swp-card.customer-giftcards swp-data-table {
|
||||
grid-template-columns: 140px 100px 80px 80px 100px;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CUSTOMER DETAIL - ACTIVITY LIST
|
||||
=========================================== */
|
||||
swp-card.customer-activity {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-card.customer-activity swp-card-header {
|
||||
padding: var(--spacing-6);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
swp-card.customer-activity swp-attention-list {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr 100px;
|
||||
padding: var(--spacing-4);
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
swp-card.customer-activity swp-attention-item {
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-4);
|
||||
}
|
||||
|
||||
swp-card.customer-activity swp-attention-action {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CUSTOMER DETAIL - JOURNAL NOTES
|
||||
Override notes-section when inside a card
|
||||
|
|
@ -798,6 +736,12 @@ swp-attendance-segment.noshow {
|
|||
background: var(--color-red);
|
||||
}
|
||||
|
||||
/* Empty state: all segments gray */
|
||||
swp-attendance-bar.empty swp-attendance-segment {
|
||||
background: var(--color-gray-300);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
/* Top List */
|
||||
swp-top-list {
|
||||
display: flex;
|
||||
|
|
@ -1468,3 +1412,29 @@ swp-activity-actor {
|
|||
margin-top: var(--spacing-6);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
CUSTOMER DETAIL - ECONOMY TAB
|
||||
=========================================== */
|
||||
.customer-economy {
|
||||
swp-data-table {
|
||||
grid-template-columns: 110px 110px 155px 1fr 110px 150px
|
||||
|
||||
}
|
||||
|
||||
swp-data-table-header swp-data-table-cell:last-child,
|
||||
swp-data-table-row swp-data-table-cell:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
/* Purchase type tags - matches chart legend colors (services=teal, products=blue) */
|
||||
swp-tag.service {
|
||||
background: var(--bg-teal-strong);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
swp-tag.product {
|
||||
background: var(--bg-blue-strong);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
* Handles:
|
||||
* - Fuzzy search with Fuse.js
|
||||
* - Customer drawer population
|
||||
* - Customer detail economy chart
|
||||
*/
|
||||
|
||||
import Fuse from 'fuse.js';
|
||||
import { createChart } from '@sevenweirdpeople/swp-charting';
|
||||
|
||||
interface CustomerItem {
|
||||
name: string;
|
||||
|
|
@ -233,3 +235,94 @@ export class CustomersController {
|
|||
return tag.charAt(0).toUpperCase() + tag.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer Economy Controller
|
||||
*
|
||||
* Handles the economy chart on customer detail page.
|
||||
* Initializes chart lazily when economy tab is shown.
|
||||
*/
|
||||
interface ChartDataPoint {
|
||||
x: string;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface ChartSeries {
|
||||
name: string;
|
||||
color: string;
|
||||
data: ChartDataPoint[];
|
||||
}
|
||||
|
||||
interface CustomerChartData {
|
||||
categories: string[];
|
||||
series: ChartSeries[];
|
||||
}
|
||||
|
||||
class CustomerEconomyController {
|
||||
private chartInitialized = false;
|
||||
private chart: ReturnType<typeof createChart> | null = null;
|
||||
|
||||
constructor() {
|
||||
this.setupTabListener();
|
||||
// Check if economy tab is already active on page load
|
||||
this.checkInitialTab();
|
||||
}
|
||||
|
||||
private setupTabListener(): void {
|
||||
document.addEventListener('click', (e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const tab = target.closest<HTMLElement>('swp-tab[data-tab="economy"]');
|
||||
|
||||
if (tab) {
|
||||
// Small delay to let tab content become visible
|
||||
setTimeout(() => this.initializeChart(), 50);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private checkInitialTab(): void {
|
||||
const activeTab = document.querySelector('swp-tab[data-tab="economy"].active');
|
||||
if (activeTab) {
|
||||
this.initializeChart();
|
||||
}
|
||||
}
|
||||
|
||||
private initializeChart(): void {
|
||||
if (this.chartInitialized) return;
|
||||
|
||||
const container = document.getElementById('customerRevenueChart');
|
||||
if (!container) return;
|
||||
|
||||
const dataScript = document.getElementById('customerRevenueChartData');
|
||||
if (!dataScript) return;
|
||||
|
||||
try {
|
||||
const data = JSON.parse(dataScript.textContent || '') as CustomerChartData;
|
||||
this.createRevenueChart(container, data);
|
||||
this.chartInitialized = true;
|
||||
} catch (err) {
|
||||
console.error('Failed to parse chart data:', err);
|
||||
}
|
||||
}
|
||||
|
||||
private createRevenueChart(container: HTMLElement, data: CustomerChartData): void {
|
||||
this.chart = createChart(container, {
|
||||
deferRender: true,
|
||||
height: 200,
|
||||
xAxis: {
|
||||
categories: data.categories
|
||||
},
|
||||
series: data.series.map(s => ({
|
||||
name: s.name,
|
||||
color: s.color,
|
||||
data: s.data
|
||||
})),
|
||||
legend: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize economy controller if on customer detail page
|
||||
if (document.getElementById('customerRevenueChart') || document.querySelector('swp-tab[data-tab="economy"]')) {
|
||||
new CustomerEconomyController();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue