// Customer management functionality - Tailwind version let currentPage = 0; let currentSearch = ''; let isEditing = false; let editingCustomerId = null; // Enhanced table display function function displayCustomers(customers) { const tbody = document.getElementById('customersTableBody'); const emptyState = document.getElementById('emptyState'); tbody.innerHTML = ''; if (!customers || customers.length === 0) { emptyState.classList.remove('hidden'); return; } emptyState.classList.add('hidden'); customers.forEach(customer => { const row = document.createElement('tr'); row.className = 'group border-b border-neutral-100 dark:border-neutral-700/50 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30 dark:hover:from-blue-900/10 dark:hover:to-indigo-900/10 transition-all duration-200'; // Store customer ID as data attribute to avoid escaping issues in onclick row.dataset.customerId = customer.id; // Build clean, simple row structure with clickable rows (no inline onclick to avoid backslash issues) row.innerHTML = `
${escapeHtml(customer.id || '')}
${escapeHtml(formatFullName(customer))}
${customer.title ? `
${escapeHtml(customer.title)}
` : ''} ${customer.group ? `${escapeHtml(customer.group)}` : '-'}
${escapeHtml(formatCityState(customer))}
${customer.a1 ? `
${escapeHtml(customer.a1)}
` : ''}
${formatPrimaryPhone(customer.phone_numbers || [])}
${customer.email ? `${escapeHtml(customer.email)}` : '-'}
`; // Add event listeners using the stored customer ID (no escaping issues) const customerCells = row.querySelectorAll('.customer-cell'); customerCells.forEach(cell => { cell.addEventListener('click', () => viewCustomer(customer.id)); }); const viewBtn = row.querySelector('.view-customer-btn'); const editBtn = row.querySelector('.edit-customer-btn'); viewBtn.addEventListener('click', (e) => { e.stopPropagation(); viewCustomer(customer.id); }); editBtn.addEventListener('click', (e) => { e.stopPropagation(); editCustomer(customer.id); }); tbody.appendChild(row); }); } // Helper functions function getInitials(customer) { const first = (customer.first || '').trim(); const last = (customer.last || '').trim(); if (first && last) { return (first.charAt(0) + last.charAt(0)).toUpperCase(); } else if (last) { return last.charAt(0).toUpperCase(); } else if (first) { return first.charAt(0).toUpperCase(); } else { return '?'; } } function formatFullName(customer) { const parts = []; if (customer.prefix) parts.push(customer.prefix.trim()); if (customer.first) parts.push(customer.first.trim()); if (customer.middle) parts.push(customer.middle.trim()); if (customer.last) parts.push(customer.last.trim()); if (customer.suffix) parts.push(customer.suffix.trim()); return parts.join(' ') || 'Unknown'; } function formatCityState(customer) { const parts = []; if (customer.city) parts.push(customer.city.trim()); if (customer.abrev) parts.push(customer.abrev.trim()); return parts.join(', ') || '-'; } function formatPrimaryPhone(phones) { if (!phones || phones.length === 0) { return '-'; } const primary = phones[0]; const count = phones.length; if (count === 1) { return escapeHtml(primary.phone || ''); } else { return escapeHtml(primary.phone || '') + ` (+${count - 1} more)`; } } function escapeHtml(str) { if (!str && str !== 0) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Enhanced alert function function showAlert(message, type = 'info') { if (window.alerts && typeof window.alerts.show === 'function') { window.alerts.show(message, type); return; } // Fallback alert(String(message)); } // Modal management functions function showAddCustomerModal() { isEditing = false; editingCustomerId = null; document.getElementById('customerModalLabel').textContent = 'Add New Customer'; document.getElementById('deleteCustomerBtn').classList.add('hidden'); clearCustomerForm(); document.getElementById('customerModal').classList.remove('hidden'); } function closeCustomerModal() { document.getElementById('customerModal').classList.add('hidden'); } function showEditCustomerModal() { document.getElementById('customerModalLabel').textContent = 'Edit Customer'; document.getElementById('deleteCustomerBtn').classList.remove('hidden'); document.getElementById('customerModal').classList.remove('hidden'); } // Customer detail functions async function viewCustomer(customerId) { try { // URL encode the customer ID to handle special characters like backslashes and spaces const encodedCustomerId = encodeURIComponent(customerId); const response = await window.http.wrappedFetch(`/api/customers/${encodedCustomerId}`); if (!response.ok) throw new Error('Failed to load customer details'); const customer = await response.json(); showCustomerDetailsModal(customer); } catch (error) { console.error('Error loading customer:', error); showAlert(`Error loading customer: ${error.message}`, 'danger'); } } function showCustomerDetailsModal(customer) { // Create modal content const modalContent = `

Customer Details

${escapeHtml(formatFullName(customer))}

${escapeHtml(customer.id || '')} ${customer.group ? `${escapeHtml(customer.group)}` : ''} ${customer.title ? `${escapeHtml(customer.title)}` : ''}

Contact Details

${customer.email ? ` ` : `
No email provided
`} ${formatCustomerPhonesCard(customer.phone_numbers || [])}

Address

${formatCustomerAddressCard(customer)}

Additional Info

${customer.dob ? `
Date of Birth: ${escapeHtml(customer.dob)}
` : ''} ${customer.legal_status ? `
Legal Status: ${escapeHtml(customer.legal_status)}
` : ''} ${customer.ss_number ? `
SSN: ${escapeHtml(customer.ss_number)}
` : ''} ${!customer.dob && !customer.legal_status && !customer.ss_number ? `

No additional information available

` : ''}
${customer.memo ? `

Notes

${escapeHtml(customer.memo)}
` : ''}
`; // Add modal to body document.body.insertAdjacentHTML('beforeend', modalContent); } function closeCustomerDetailsModal() { const modal = document.getElementById('customerDetailsModal'); if (modal) { modal.remove(); } } function formatCustomerAddress(customer) { const parts = []; if (customer.a1) parts.push(`
${escapeHtml(customer.a1)}
`); if (customer.a2) parts.push(`
${escapeHtml(customer.a2)}
`); if (customer.a3) parts.push(`
${escapeHtml(customer.a3)}
`); const cityState = formatCityState(customer); if (cityState !== '-') { parts.push(`
${escapeHtml(cityState)}
`); } if (customer.zip) { parts.push(`
${escapeHtml(customer.zip)}
`); } return parts.length > 0 ? `
Address:
${parts.join('')}
` : ''; } function formatCustomerPhones(phones) { if (!phones || phones.length === 0) { return '
Phone: None
'; } const phoneList = phones.map(phone => `
${phone.location ? `${escapeHtml(phone.location)}: ` : ''}${escapeHtml(phone.phone)}
` ).join(''); return `
Phone${phones.length > 1 ? 's' : ''}:${phoneList}
`; } function formatCustomerAddressLarge(customer) { const addressParts = []; if (customer.a1) addressParts.push(escapeHtml(customer.a1)); if (customer.a2) addressParts.push(escapeHtml(customer.a2)); if (customer.a3) addressParts.push(escapeHtml(customer.a3)); const cityState = formatCityState(customer); if (cityState !== '-') addressParts.push(escapeHtml(cityState)); if (customer.zip) addressParts.push(escapeHtml(customer.zip)); if (addressParts.length === 0) { return '
Address: Not provided
'; } return `
Address:
${addressParts.map(part => `
${part}
`).join('')}
`; } function formatCustomerPhonesLarge(phones) { if (!phones || phones.length === 0) { return '
Phone: None
'; } const phoneList = phones.map(phone => `
${escapeHtml(phone.phone)} ${phone.location ? `${escapeHtml(phone.location)}` : ''}
` ).join(''); return `
Phone${phones.length > 1 ? 's' : ''}:
${phoneList}
`; } function formatCustomerPhonesCard(phones) { if (!phones || phones.length === 0) { return `
No phone numbers
`; } return phones.map(phone => `
${escapeHtml(phone.phone)} ${phone.location ? `${escapeHtml(phone.location)}` : ''}
`).join(''); } function formatCustomerAddressCard(customer) { const addressParts = []; if (customer.a1) addressParts.push(escapeHtml(customer.a1)); if (customer.a2) addressParts.push(escapeHtml(customer.a2)); if (customer.a3) addressParts.push(escapeHtml(customer.a3)); const cityState = formatCityState(customer); if (cityState !== '-') addressParts.push(escapeHtml(cityState)); if (customer.zip) addressParts.push(escapeHtml(customer.zip)); if (addressParts.length === 0) { return `

No address provided

`; } return `
${addressParts.map(part => `
${part}
`).join('')}
`; } async function editCustomer(customerId) { try { // URL encode the customer ID to handle special characters like backslashes and spaces const encodedCustomerId = encodeURIComponent(customerId); const response = await window.http.wrappedFetch(`/api/customers/${encodedCustomerId}`); if (!response.ok) throw new Error('Failed to load customer details'); const customer = await response.json(); populateEditForm(customer); showEditCustomerModal(); } catch (error) { console.error('Error loading customer for edit:', error); showAlert(`Error loading customer: ${error.message}`, 'danger'); } } function populateEditForm(customer) { isEditing = true; editingCustomerId = customer.id; // Populate form fields document.getElementById('customerId').value = customer.id || ''; document.getElementById('last').value = customer.last || ''; document.getElementById('first').value = customer.first || ''; document.getElementById('middle').value = customer.middle || ''; document.getElementById('prefix').value = customer.prefix || ''; document.getElementById('suffix').value = customer.suffix || ''; document.getElementById('title').value = customer.title || ''; document.getElementById('group').value = customer.group || ''; document.getElementById('a1').value = customer.a1 || ''; document.getElementById('a2').value = customer.a2 || ''; document.getElementById('a3').value = customer.a3 || ''; document.getElementById('city').value = customer.city || ''; document.getElementById('abrev').value = customer.abrev || ''; document.getElementById('zip').value = customer.zip || ''; document.getElementById('email').value = customer.email || ''; document.getElementById('dob').value = customer.dob || ''; document.getElementById('ss_number').value = customer.ss_number || ''; document.getElementById('legal_status').value = customer.legal_status || ''; document.getElementById('memo').value = customer.memo || ''; // Populate phone numbers populatePhoneNumbers(customer.phone_numbers || []); // Make customer ID readonly for editing document.getElementById('customerId').readOnly = true; } async function saveCustomer() { try { // Gather form data const customerData = { last: document.getElementById('last').value, first: document.getElementById('first').value || null, middle: document.getElementById('middle').value || null, prefix: document.getElementById('prefix').value || null, suffix: document.getElementById('suffix').value || null, title: document.getElementById('title').value || null, group: document.getElementById('group').value || null, a1: document.getElementById('a1').value || null, a2: document.getElementById('a2').value || null, a3: document.getElementById('a3').value || null, city: document.getElementById('city').value || null, abrev: document.getElementById('abrev').value || null, zip: document.getElementById('zip').value || null, email: document.getElementById('email').value || null, dob: document.getElementById('dob').value || null, ss_number: document.getElementById('ss_number').value || null, legal_status: document.getElementById('legal_status').value || null, memo: document.getElementById('memo').value || null }; // Validate required fields if (!customerData.last) { showAlert('Last name/Company is required', 'danger'); return; } // Double confirm for edits if (isEditing) { const confirmMessage = `Are you sure you want to update customer "${customerData.last}"? This will permanently modify the customer record.`; const firstConfirm = confirm(confirmMessage); if (!firstConfirm) { return; // User cancelled } // Second confirmation const secondConfirm = confirm('Please confirm again: Do you really want to save these changes? This action cannot be undone.'); if (!secondConfirm) { return; // User cancelled on second confirmation } } let response; if (isEditing) { // Update existing customer const encodedCustomerId = encodeURIComponent(editingCustomerId); response = await window.http.wrappedFetch(`/api/customers/${encodedCustomerId}`, { method: 'PUT', body: JSON.stringify(customerData) }); } else { // Create new customer customerData.id = document.getElementById('customerId').value; if (!customerData.id) { showAlert('Customer ID is required', 'danger'); return; } response = await window.http.wrappedFetch('/api/customers/', { method: 'POST', body: JSON.stringify(customerData) }); } if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to save customer'); } showAlert(isEditing ? 'Customer updated successfully!' : 'Customer created successfully!', 'success'); closeCustomerModal(); loadCustomers(); // Refresh the customer list } catch (error) { console.error('Error saving customer:', error); showAlert(`Error saving customer: ${error.message}`, 'danger'); } } function deleteCustomer() { showAlert('Delete customer feature coming soon...', 'info'); } function validateCustomerId() { // Placeholder } function populatePhoneNumbers(phones) { const phoneList = document.getElementById('phoneList'); phoneList.innerHTML = ''; phones.forEach((phone, index) => { const phoneDiv = document.createElement('div'); phoneDiv.className = 'flex items-center space-x-2'; phoneDiv.innerHTML = `
`; phoneList.appendChild(phoneDiv); }); } function clearCustomerForm() { isEditing = false; editingCustomerId = null; // Clear all form fields document.getElementById('customerId').value = ''; document.getElementById('customerId').readOnly = false; document.getElementById('last').value = ''; document.getElementById('first').value = ''; document.getElementById('middle').value = ''; document.getElementById('prefix').value = ''; document.getElementById('suffix').value = ''; document.getElementById('title').value = ''; document.getElementById('group').value = ''; document.getElementById('a1').value = ''; document.getElementById('a2').value = ''; document.getElementById('a3').value = ''; document.getElementById('city').value = ''; document.getElementById('abrev').value = ''; document.getElementById('zip').value = ''; document.getElementById('email').value = ''; document.getElementById('dob').value = ''; document.getElementById('ss_number').value = ''; document.getElementById('legal_status').value = ''; document.getElementById('memo').value = ''; // Clear phone numbers document.getElementById('phoneList').innerHTML = ''; } function removePhoneNumber(index) { const phoneList = document.getElementById('phoneList'); const phoneInputs = phoneList.children; if (phoneInputs[index]) { phoneInputs[index].remove(); // Re-index remaining phone inputs Array.from(phoneInputs).forEach((phoneDiv, newIndex) => { const inputs = phoneDiv.querySelectorAll('input'); inputs.forEach(input => { input.setAttribute('data-phone-index', newIndex); }); const button = phoneDiv.querySelector('button'); if (button) { button.setAttribute('onclick', `removePhoneNumber(${newIndex})`); } }); } } function addPhoneNumber() { const phoneList = document.getElementById('phoneList'); const currentCount = phoneList.children.length; const phoneDiv = document.createElement('div'); phoneDiv.className = 'flex items-center space-x-2'; phoneDiv.innerHTML = `
`; phoneList.appendChild(phoneDiv); } // Make functions globally available window.showAddCustomerModal = showAddCustomerModal; window.closeCustomerModal = closeCustomerModal; window.showEditCustomerModal = showEditCustomerModal; window.displayCustomers = displayCustomers; window.showAlert = showAlert; window.editCustomer = editCustomer; window.viewCustomer = viewCustomer; window.saveCustomer = saveCustomer; window.deleteCustomer = deleteCustomer; window.validateCustomerId = validateCustomerId; window.closeCustomerDetailsModal = closeCustomerDetailsModal; window.populateEditForm = populateEditForm; window.populatePhoneNumbers = populatePhoneNumbers; window.clearCustomerForm = clearCustomerForm; window.addPhoneNumber = addPhoneNumber; window.removePhoneNumber = removePhoneNumber;