716 lines
35 KiB
JavaScript
716 lines
35 KiB
JavaScript
// 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 = `
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
<div class="text-sm font-mono font-semibold text-neutral-900 dark:text-neutral-100 bg-gradient-to-br from-neutral-50 to-neutral-100 dark:from-neutral-800 dark:to-neutral-700 px-3 py-2 rounded-lg shadow-sm border border-neutral-200/50 dark:border-neutral-600/50 group-hover:shadow-md transition-shadow">
|
|
${escapeHtml(customer.id || '')}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
<div class="text-sm font-semibold text-neutral-900 dark:text-neutral-100 group-hover:text-blue-700 dark:group-hover:text-blue-300 transition-colors">
|
|
${escapeHtml(formatFullName(customer))}
|
|
</div>
|
|
${customer.title ? `<div class="text-xs text-neutral-500 dark:text-neutral-400 mt-1 font-medium">${escapeHtml(customer.title)}</div>` : ''}
|
|
</td>
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
${customer.group ? `<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-bold bg-gradient-to-r from-blue-50 to-blue-100 dark:from-blue-900/40 dark:to-blue-800/40 text-blue-800 dark:text-blue-200 border border-blue-200/70 dark:border-blue-700/70 shadow-sm">${escapeHtml(customer.group)}</span>` : '<span class="text-neutral-400 text-sm font-medium">-</span>'}
|
|
</td>
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
<div class="text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
|
${escapeHtml(formatCityState(customer))}
|
|
</div>
|
|
${customer.a1 ? `<div class="text-xs text-neutral-500 dark:text-neutral-400 mt-1 truncate max-w-xs font-medium">${escapeHtml(customer.a1)}</div>` : ''}
|
|
</td>
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
<div class="text-sm font-mono font-medium text-neutral-900 dark:text-neutral-100">
|
|
${formatPrimaryPhone(customer.phone_numbers || [])}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-4 cursor-pointer customer-cell">
|
|
${customer.email ? `<a href="mailto:${encodeURIComponent(customer.email)}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200 text-sm font-medium transition-colors underline decoration-blue-300/50 hover:decoration-blue-500" onclick="event.stopPropagation()">${escapeHtml(customer.email)}</a>` : '<span class="text-neutral-400 text-sm font-medium">-</span>'}
|
|
</td>
|
|
<td class="px-4 py-4 text-right">
|
|
<div class="flex items-center justify-end space-x-2 opacity-70 group-hover:opacity-100 transition-opacity">
|
|
<button class="view-customer-btn inline-flex items-center px-3 py-2 bg-gradient-to-r from-slate-100 to-slate-200 dark:from-slate-700 dark:to-slate-600 text-slate-700 dark:text-slate-200 hover:from-slate-200 hover:to-slate-300 dark:hover:from-slate-600 dark:hover:to-slate-500 rounded-lg text-sm font-semibold transition-all duration-200 shadow-sm hover:shadow-md border border-slate-300/50 dark:border-slate-500/50">
|
|
<i class="fa-solid fa-eye mr-2"></i>
|
|
View
|
|
</button>
|
|
<button class="edit-customer-btn" style="display: inline-flex; align-items: center; background-color: #dc2626; color: white; padding: 8px 12px; border-radius: 6px; border: none; font-weight: 600; font-size: 14px;">
|
|
<i class="fa-solid fa-pencil" style="margin-right: 8px;"></i>
|
|
Edit
|
|
</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
|
|
// 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 '<span class="text-neutral-400">-</span>';
|
|
}
|
|
|
|
const primary = phones[0];
|
|
const count = phones.length;
|
|
|
|
if (count === 1) {
|
|
return escapeHtml(primary.phone || '');
|
|
} else {
|
|
return escapeHtml(primary.phone || '') + ` <span class="text-xs text-neutral-500">(+${count - 1} more)</span>`;
|
|
}
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
if (!str && str !== 0) return '';
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.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 = `
|
|
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" id="customerDetailsModal">
|
|
<div class="bg-white dark:bg-neutral-800 rounded-xl shadow-xl max-w-5xl w-full max-h-[85vh] overflow-hidden">
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
|
<h2 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100">Customer Details</h2>
|
|
<button onclick="closeCustomerDetailsModal()" class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-colors">
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
|
</button>
|
|
</div>
|
|
<div class="px-8 py-6 max-h-[70vh] overflow-y-auto">
|
|
<!-- Customer Header -->
|
|
<div class="mb-8 pb-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-100 mb-2">${escapeHtml(formatFullName(customer))}</h1>
|
|
<div class="flex items-center space-x-4">
|
|
<span class="inline-flex items-center px-3 py-1 rounded-lg text-sm font-mono bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border">${escapeHtml(customer.id || '')}</span>
|
|
${customer.group ? `<span class="inline-flex items-center px-3 py-1 rounded-lg text-sm font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 border border-blue-200 dark:border-blue-700">${escapeHtml(customer.group)}</span>` : ''}
|
|
${customer.title ? `<span class="text-neutral-600 dark:text-neutral-400">${escapeHtml(customer.title)}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content Grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
<!-- Contact Information (Left Column) -->
|
|
<div class="lg:col-span-1">
|
|
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg p-6 border border-neutral-200 dark:border-neutral-700">
|
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 flex items-center">
|
|
<i class="fa-solid fa-address-card mr-2 text-blue-600 dark:text-blue-400"></i>
|
|
Contact Details
|
|
</h3>
|
|
<div class="space-y-4">
|
|
${customer.email ? `
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fa-solid fa-envelope text-neutral-500 w-4"></i>
|
|
<a href="mailto:${encodeURIComponent(customer.email)}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline break-all">${escapeHtml(customer.email)}</a>
|
|
</div>
|
|
` : `
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fa-solid fa-envelope text-neutral-400 w-4"></i>
|
|
<span class="text-neutral-400">No email provided</span>
|
|
</div>
|
|
`}
|
|
|
|
${formatCustomerPhonesCard(customer.phone_numbers || [])}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address Information (Middle Column) -->
|
|
<div class="lg:col-span-1">
|
|
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg p-6 border border-neutral-200 dark:border-neutral-700">
|
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 flex items-center">
|
|
<i class="fa-solid fa-map-marker-alt mr-2 text-green-600 dark:text-green-400"></i>
|
|
Address
|
|
</h3>
|
|
${formatCustomerAddressCard(customer)}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional Information (Right Column) -->
|
|
<div class="lg:col-span-1">
|
|
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg p-6 border border-neutral-200 dark:border-neutral-700">
|
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 flex items-center">
|
|
<i class="fa-solid fa-info-circle mr-2 text-purple-600 dark:text-purple-400"></i>
|
|
Additional Info
|
|
</h3>
|
|
<div class="space-y-3">
|
|
${customer.dob ? `
|
|
<div class="flex justify-between">
|
|
<span class="text-neutral-600 dark:text-neutral-400">Date of Birth:</span>
|
|
<span class="text-neutral-900 dark:text-neutral-100">${escapeHtml(customer.dob)}</span>
|
|
</div>
|
|
` : ''}
|
|
${customer.legal_status ? `
|
|
<div class="flex justify-between">
|
|
<span class="text-neutral-600 dark:text-neutral-400">Legal Status:</span>
|
|
<span class="text-neutral-900 dark:text-neutral-100">${escapeHtml(customer.legal_status)}</span>
|
|
</div>
|
|
` : ''}
|
|
${customer.ss_number ? `
|
|
<div class="flex justify-between">
|
|
<span class="text-neutral-600 dark:text-neutral-400">SSN:</span>
|
|
<span class="font-mono text-neutral-900 dark:text-neutral-100">${escapeHtml(customer.ss_number)}</span>
|
|
</div>
|
|
` : ''}
|
|
${!customer.dob && !customer.legal_status && !customer.ss_number ? `
|
|
<div class="text-center text-neutral-400 py-4">
|
|
<i class="fa-solid fa-info-circle mb-2 text-lg"></i>
|
|
<p>No additional information available</p>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Section (Full Width) -->
|
|
${customer.memo ? `
|
|
<div class="mt-8">
|
|
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg p-6 border border-neutral-200 dark:border-neutral-700">
|
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 flex items-center">
|
|
<i class="fa-solid fa-sticky-note mr-2 text-orange-600 dark:text-orange-400"></i>
|
|
Notes
|
|
</h3>
|
|
<div class="text-neutral-700 dark:text-neutral-300 leading-relaxed whitespace-pre-wrap bg-white dark:bg-neutral-800 p-4 rounded border border-neutral-200 dark:border-neutral-600">${escapeHtml(customer.memo)}</div>
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
<div class="flex items-center justify-end gap-4 px-8 py-6 border-t border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50">
|
|
<button onclick="closeCustomerDetailsModal()" class="px-6 py-3 bg-neutral-200 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-300 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200 font-medium">
|
|
Close
|
|
</button>
|
|
<button onclick="closeCustomerDetailsModal(); editCustomer('${escapeHtml(customer.id)}')" class="px-6 py-3 bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors duration-200 font-medium shadow-sm">
|
|
<i class="fa-solid fa-pencil mr-2"></i>Edit Customer
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 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(`<div>${escapeHtml(customer.a1)}</div>`);
|
|
if (customer.a2) parts.push(`<div>${escapeHtml(customer.a2)}</div>`);
|
|
if (customer.a3) parts.push(`<div>${escapeHtml(customer.a3)}</div>`);
|
|
|
|
const cityState = formatCityState(customer);
|
|
if (cityState !== '-') {
|
|
parts.push(`<div>${escapeHtml(cityState)}</div>`);
|
|
}
|
|
|
|
if (customer.zip) {
|
|
parts.push(`<div>${escapeHtml(customer.zip)}</div>`);
|
|
}
|
|
|
|
return parts.length > 0 ? `<div class="mb-2"><span class="font-medium">Address:</span><div class="ml-4">${parts.join('')}</div></div>` : '';
|
|
}
|
|
|
|
function formatCustomerPhones(phones) {
|
|
if (!phones || phones.length === 0) {
|
|
return '<div><span class="font-medium">Phone:</span> <span class="text-neutral-400">None</span></div>';
|
|
}
|
|
|
|
const phoneList = phones.map(phone =>
|
|
`<div class="ml-4">${phone.location ? `<span class="text-xs text-neutral-500">${escapeHtml(phone.location)}:</span> ` : ''}${escapeHtml(phone.phone)}</div>`
|
|
).join('');
|
|
|
|
return `<div class="mb-2"><span class="font-medium">Phone${phones.length > 1 ? 's' : ''}:</span>${phoneList}</div>`;
|
|
}
|
|
|
|
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 '<div class="flex items-start"><span class="font-semibold text-neutral-700 dark:text-neutral-300 w-20 flex-shrink-0">Address:</span> <span class="text-neutral-400">Not provided</span></div>';
|
|
}
|
|
|
|
return `<div class="flex items-start"><span class="font-semibold text-neutral-700 dark:text-neutral-300 w-20 flex-shrink-0">Address:</span> <div class="space-y-1">${addressParts.map(part => `<div>${part}</div>`).join('')}</div></div>`;
|
|
}
|
|
|
|
function formatCustomerPhonesLarge(phones) {
|
|
if (!phones || phones.length === 0) {
|
|
return '<div class="flex items-start"><span class="font-semibold text-neutral-700 dark:text-neutral-300 w-20 flex-shrink-0">Phone:</span> <span class="text-neutral-400">None</span></div>';
|
|
}
|
|
|
|
const phoneList = phones.map(phone =>
|
|
`<div class="flex items-center space-x-2">
|
|
<span class="font-mono bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-sm">${escapeHtml(phone.phone)}</span>
|
|
${phone.location ? `<span class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded">${escapeHtml(phone.location)}</span>` : ''}
|
|
</div>`
|
|
).join('');
|
|
|
|
return `<div class="flex items-start"><span class="font-semibold text-neutral-700 dark:text-neutral-300 w-20 flex-shrink-0">Phone${phones.length > 1 ? 's' : ''}:</span> <div class="space-y-2">${phoneList}</div></div>`;
|
|
}
|
|
|
|
function formatCustomerPhonesCard(phones) {
|
|
if (!phones || phones.length === 0) {
|
|
return `
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fa-solid fa-phone text-neutral-400 w-4"></i>
|
|
<span class="text-neutral-400">No phone numbers</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return phones.map(phone => `
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fa-solid fa-phone text-neutral-500 w-4"></i>
|
|
<div class="flex items-center space-x-2">
|
|
<span class="font-mono text-neutral-900 dark:text-neutral-100">${escapeHtml(phone.phone)}</span>
|
|
${phone.location ? `<span class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">${escapeHtml(phone.location)}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
`).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 `
|
|
<div class="text-center text-neutral-400 py-4">
|
|
<i class="fa-solid fa-map-marker-alt mb-2 text-lg"></i>
|
|
<p>No address provided</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return `
|
|
<div class="space-y-2">
|
|
${addressParts.map(part => `<div class="text-neutral-700 dark:text-neutral-300">${part}</div>`).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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 = `
|
|
<div class="flex-1">
|
|
<input type="text"
|
|
value="${escapeHtml(phone.location || '')}"
|
|
placeholder="Location (e.g., Home, Office)"
|
|
class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 text-sm"
|
|
data-phone-index="${index}"
|
|
data-phone-field="location">
|
|
</div>
|
|
<div class="flex-1">
|
|
<input type="text"
|
|
value="${escapeHtml(phone.phone || '')}"
|
|
placeholder="Phone number"
|
|
class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 text-sm"
|
|
data-phone-index="${index}"
|
|
data-phone-field="phone">
|
|
</div>
|
|
<button type="button" onclick="removePhoneNumber(${index})" class="px-2 py-2 bg-danger-600 text-white hover:bg-danger-700 rounded-lg text-sm transition-colors">
|
|
<i class="fa-solid fa-trash"></i>
|
|
</button>
|
|
`;
|
|
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 = `
|
|
<div class="flex-1">
|
|
<input type="text"
|
|
value=""
|
|
placeholder="Location (e.g., Home, Office)"
|
|
class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 text-sm"
|
|
data-phone-index="${currentCount}"
|
|
data-phone-field="location">
|
|
</div>
|
|
<div class="flex-1">
|
|
<input type="text"
|
|
value=""
|
|
placeholder="Phone number"
|
|
class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 text-sm"
|
|
data-phone-index="${currentCount}"
|
|
data-phone-field="phone">
|
|
</div>
|
|
<button type="button" onclick="removePhoneNumber(${currentCount})" class="px-2 py-2 bg-danger-600 text-white hover:bg-danger-700 rounded-lg text-sm transition-colors">
|
|
<i class="fa-solid fa-trash"></i>
|
|
</button>
|
|
`;
|
|
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; |