/** * Customers Controller * * Handles: * - Fuzzy search with Fuse.js * - Customer drawer population */ import Fuse from 'fuse.js'; interface CustomerItem { name: string; phone: string; email: string; visits: string; created: string; tags: string; hairdresser: string; element: HTMLElement; } export class CustomersController { private fuse: Fuse | null = null; private customers: CustomerItem[] = []; private searchInput: HTMLInputElement | null = null; private emptyState: HTMLElement | null = null; private dataTable: HTMLElement | null = null; constructor() { // Only initialize if we're on the customers page const customersTable = document.querySelector('swp-card.customers-list'); if (!customersTable) return; this.init(); } private init(): void { this.searchInput = document.getElementById('searchInput') as HTMLInputElement; this.emptyState = document.getElementById('emptyState'); this.dataTable = document.querySelector('swp-card.customers-list swp-data-table'); this.buildCustomerIndex(); this.setupSearch(); this.setupDrawerPopulation(); } private buildCustomerIndex(): void { const customerRows = document.querySelectorAll('swp-card.customers-list swp-data-table-row'); customerRows.forEach((row) => { const element = row as HTMLElement; const cells = element.querySelectorAll('swp-data-table-cell'); const name = element.dataset.name || ''; const phone = cells[1]?.textContent?.trim() || ''; const email = cells[2]?.textContent?.trim() || ''; const visits = element.dataset.visits || ''; const created = element.dataset.created || ''; const tags = element.dataset.tags || ''; const hairdresser = cells[5]?.textContent?.trim() || ''; this.customers.push({ name, phone, email, visits, created, tags, hairdresser, element }); }); } private setupSearch(): void { if (!this.searchInput) return; // Initialize Fuse.js with multiple search keys this.fuse = new Fuse(this.customers, { keys: ['name', 'phone', 'email'], threshold: 0.3, minMatchCharLength: 2 }); // Listen for input with debounce let debounceTimer: number; this.searchInput.addEventListener('input', (e) => { clearTimeout(debounceTimer); debounceTimer = window.setTimeout(() => { const query = (e.target as HTMLInputElement).value.trim(); this.filterCustomers(query); }, 150); }); } private filterCustomers(query: string): void { if (!query || query.length < 2) { this.showAll(); return; } if (!this.fuse) return; // Get matching customers const results = this.fuse.search(query); const matchingCustomers = new Set(results.map(r => r.item.element)); let visibleCount = 0; // Show/hide customers this.customers.forEach(customer => { if (matchingCustomers.has(customer.element)) { customer.element.style.display = 'grid'; visibleCount++; } else { customer.element.style.display = 'none'; } }); // Show/hide empty state this.updateEmptyState(visibleCount); } private showAll(): void { this.customers.forEach(customer => { customer.element.style.display = 'grid'; }); this.updateEmptyState(this.customers.length); } private updateEmptyState(visibleCount: number): void { if (!this.emptyState || !this.dataTable) return; if (visibleCount === 0) { this.emptyState.style.display = 'flex'; // Hide header when no results const header = this.dataTable.querySelector('swp-data-table-header') as HTMLElement; if (header) header.style.display = 'none'; } else { this.emptyState.style.display = 'none'; // Show header when results exist const header = this.dataTable.querySelector('swp-data-table-header') as HTMLElement; if (header) header.style.display = 'grid'; } } private setupDrawerPopulation(): void { // Listen for clicks on customer rows to populate drawer document.addEventListener('click', (e) => { const row = (e.target as HTMLElement).closest('swp-data-table-row[data-drawer-trigger="customer-drawer"]'); if (!row) return; this.populateDrawer(row); }); } private populateDrawer(row: HTMLElement): void { const cells = row.querySelectorAll('swp-data-table-cell'); const name = row.dataset.name || ''; const phone = cells[1]?.textContent?.trim() || ''; const email = cells[2]?.textContent?.trim() || ''; const visits = row.dataset.visits || ''; const created = row.dataset.created || ''; const tags = row.dataset.tags || ''; const hairdresser = cells[5]?.textContent?.trim() || ''; // Generate initials const initials = name.split(' ').map(n => n[0]).join('').toUpperCase(); // Format "Kunde siden" const createdDate = created ? this.formatCreatedDate(created) : 'Ukendt'; // Update drawer elements const drawerAvatar = document.getElementById('drawerAvatar'); const drawerName = document.getElementById('drawerName'); const drawerSince = document.getElementById('drawerSince'); const drawerPhoneLink = document.getElementById('drawerPhoneLink') as HTMLAnchorElement; const drawerEmailLink = document.getElementById('drawerEmailLink') as HTMLAnchorElement; const drawerVisits = document.getElementById('drawerVisits'); const drawerHairdresser = document.getElementById('drawerHairdresser'); const drawerTags = document.getElementById('drawerTags'); const editPhone = document.getElementById('editPhone') as HTMLInputElement; const editEmail = document.getElementById('editEmail') as HTMLInputElement; if (drawerAvatar) drawerAvatar.textContent = initials; if (drawerName) drawerName.textContent = name; if (drawerSince) drawerSince.textContent = `Kunde siden ${createdDate}`; if (drawerPhoneLink) { drawerPhoneLink.textContent = phone; drawerPhoneLink.href = `tel:${phone.replace(/\s/g, '')}`; } if (drawerEmailLink) { drawerEmailLink.textContent = email; drawerEmailLink.href = `mailto:${email}`; } if (drawerVisits) drawerVisits.textContent = visits; if (drawerHairdresser) drawerHairdresser.textContent = hairdresser; // Update editable fields if (editPhone) editPhone.value = phone; if (editEmail) editEmail.value = email; // Update tags if (drawerTags) { drawerTags.innerHTML = ''; if (tags) { tags.split(',').forEach(tag => { const tagEl = document.createElement('swp-tag'); tagEl.className = tag.trim(); tagEl.textContent = this.formatTagLabel(tag.trim()); drawerTags.appendChild(tagEl); }); } } } private formatCreatedDate(dateStr: string): string { const months = [ 'januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december' ]; const [year, month] = dateStr.split('-'); const monthIndex = parseInt(month, 10) - 1; return `${months[monthIndex]} ${year}`; } private formatTagLabel(tag: string): string { // Capitalize first letter return tag.charAt(0).toUpperCase() + tag.slice(1); } }