// 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)} ` : '- '}
View
Edit
`;
// 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)}
` : ''}
Close
Edit Customer
`;
// 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 `
`;
}
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;
}
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;