560 lines
23 KiB
JavaScript
560 lines
23 KiB
JavaScript
// Customer management functionality - Tailwind version
|
|
let currentPage = 0;
|
|
let currentSearch = '';
|
|
let isEditing = false;
|
|
let editingCustomerId = null;
|
|
|
|
// Helper function for authenticated API calls
|
|
function getAuthHeaders() {
|
|
const token = localStorage.getItem('auth_token');
|
|
return {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
}
|
|
|
|
// 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');
|
|
}
|
|
|
|
// 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 = 'hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors duration-150';
|
|
|
|
row.innerHTML = `
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-neutral-900 dark:text-neutral-100">${customer.id}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-8 w-8">
|
|
<div class="h-8 w-8 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
|
|
<span class="text-sm font-medium text-primary-600 dark:text-primary-400">${getInitials(customer)}</span>
|
|
</div>
|
|
</div>
|
|
<div class="ml-3">
|
|
<div class="text-sm font-medium text-neutral-900 dark:text-neutral-100">${formatName(customer)}</div>
|
|
${customer.title ? `<div class="text-sm text-neutral-500 dark:text-neutral-400">${customer.title}</div>` : ''}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
${customer.group ? `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-neutral-100 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200">${customer.group}</span>` : '<span class="text-neutral-400 dark:text-neutral-500">-</span>'}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100">
|
|
${formatLocation(customer)}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100">
|
|
${formatPhones(customer.phone_numbers)}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100">
|
|
${customer.email ? `<a href="mailto:${customer.email}" class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 transition-colors">${customer.email}</a>` : '<span class="text-neutral-400 dark:text-neutral-500">-</span>'}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<button onclick="editCustomer('${customer.id}')" class="inline-flex items-center gap-1 px-3 py-1.5 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors duration-200 text-xs">
|
|
<i class="fa-solid fa-pencil"></i>
|
|
<span>Edit</span>
|
|
</button>
|
|
<button onclick="viewCustomer('${customer.id}')" class="inline-flex items-center gap-1 px-3 py-1.5 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200 text-xs">
|
|
<i class="fa-regular fa-eye"></i>
|
|
<span>View</span>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// Helper functions
|
|
function getInitials(customer) {
|
|
const first = customer.first || '';
|
|
const last = customer.last || '';
|
|
return (first.charAt(0) + last.charAt(0)).toUpperCase();
|
|
}
|
|
|
|
function formatName(customer) {
|
|
const parts = [];
|
|
if (customer.prefix) parts.push(customer.prefix);
|
|
if (customer.first) parts.push(customer.first);
|
|
if (customer.middle) parts.push(customer.middle);
|
|
parts.push(customer.last);
|
|
if (customer.suffix) parts.push(customer.suffix);
|
|
return parts.join(' ');
|
|
}
|
|
|
|
function formatLocation(customer) {
|
|
const parts = [];
|
|
if (customer.city) parts.push(customer.city);
|
|
if (customer.abrev) parts.push(customer.abrev);
|
|
return parts.join(', ') || '-';
|
|
}
|
|
|
|
function formatPhones(phones) {
|
|
if (!phones || phones.length === 0) return '<span class="text-neutral-400 dark:text-neutral-500">-</span>';
|
|
|
|
return phones.map(p =>
|
|
`<div class="mb-1">
|
|
<span class="text-xs text-neutral-500 dark:text-neutral-400">${p.location || 'Phone'}:</span>
|
|
<span class="ml-1 font-mono text-sm">${p.phone}</span>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
|
|
// Enhanced pagination
|
|
function updatePagination(currentPage, totalPages) {
|
|
const pagination = document.getElementById('pagination');
|
|
pagination.innerHTML = '';
|
|
|
|
if (totalPages <= 1) return;
|
|
|
|
// Previous button
|
|
const prevButton = document.createElement('button');
|
|
prevButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${
|
|
currentPage === 0
|
|
? 'bg-neutral-100 dark:bg-neutral-800 text-neutral-400 dark:text-neutral-500 cursor-not-allowed'
|
|
: 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600'
|
|
}`;
|
|
prevButton.innerHTML = '<i class="fa-solid fa-chevron-left"></i>';
|
|
prevButton.disabled = currentPage === 0;
|
|
prevButton.onclick = () => currentPage > 0 && loadCustomers(currentPage - 1, currentSearch);
|
|
pagination.appendChild(prevButton);
|
|
|
|
// Page numbers
|
|
const startPage = Math.max(0, currentPage - 2);
|
|
const endPage = Math.min(totalPages - 1, currentPage + 2);
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
const pageButton = document.createElement('button');
|
|
pageButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${
|
|
i === currentPage
|
|
? 'bg-primary-600 text-white'
|
|
: 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600'
|
|
}`;
|
|
pageButton.textContent = i + 1;
|
|
pageButton.onclick = () => loadCustomers(i, currentSearch);
|
|
pagination.appendChild(pageButton);
|
|
}
|
|
|
|
// Next button
|
|
const nextButton = document.createElement('button');
|
|
nextButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${
|
|
currentPage === totalPages - 1
|
|
? 'bg-neutral-100 dark:bg-neutral-800 text-neutral-400 dark:text-neutral-500 cursor-not-allowed'
|
|
: 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600'
|
|
}`;
|
|
nextButton.innerHTML = '<i class="fa-solid fa-chevron-right"></i>';
|
|
nextButton.disabled = currentPage === totalPages - 1;
|
|
nextButton.onclick = () => currentPage < totalPages - 1 && loadCustomers(currentPage + 1, currentSearch);
|
|
pagination.appendChild(nextButton);
|
|
}
|
|
|
|
// Enhanced alert function (delegates to shared alerts utility)
|
|
function showAlert(message, type = 'info') {
|
|
if (window.alerts && typeof window.alerts.show === 'function') {
|
|
window.alerts.show(message, type);
|
|
return;
|
|
}
|
|
// Fallback
|
|
alert(String(message));
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
document.addEventListener('click', function(event) {
|
|
const modal = document.getElementById('customerModal');
|
|
if (event.target === modal) {
|
|
closeCustomerModal();
|
|
}
|
|
});
|
|
|
|
// Handle escape key for modal
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
closeCustomerModal();
|
|
}
|
|
});
|
|
|
|
// Make functions globally available for backwards compatibility
|
|
window.showAddCustomerModal = showAddCustomerModal;
|
|
window.closeCustomerModal = closeCustomerModal;
|
|
window.showEditCustomerModal = showEditCustomerModal;
|
|
window.displayCustomers = displayCustomers;
|
|
window.showAlert = showAlert;
|
|
|
|
// Form handling functions
|
|
function clearCustomerForm() {
|
|
document.getElementById('customerId').value = '';
|
|
document.getElementById('customerId').disabled = 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 = '';
|
|
document.getElementById('phoneList').innerHTML = '';
|
|
window.currentPhones = []; // Track phones with {id, location, phone, action: 'add'|'update'|'delete'|'none'}
|
|
}
|
|
|
|
async function populateCustomerForm(customer) {
|
|
document.getElementById('customerId').value = customer.id;
|
|
document.getElementById('customerId').disabled = true;
|
|
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 ? new Date(customer.dob).toISOString().split('T')[0] : '';
|
|
document.getElementById('ss_number').value = customer.ss_number || '';
|
|
document.getElementById('legal_status').value = customer.legal_status || '';
|
|
document.getElementById('memo').value = customer.memo || '';
|
|
|
|
// Populate phones
|
|
const phoneList = document.getElementById('phoneList');
|
|
phoneList.innerHTML = '';
|
|
window.currentPhones = customer.phone_numbers.map(p => ({...p, action: 'none'}));
|
|
window.currentPhones.forEach((phone, index) => addPhoneRow(index, phone));
|
|
}
|
|
|
|
function addPhoneRow(index, phone = {location: '', phone: '', action: 'add'}) {
|
|
const phoneList = document.getElementById('phoneList');
|
|
const row = document.createElement('div');
|
|
row.className = 'flex items-end gap-4 mb-2';
|
|
row.dataset.index = index;
|
|
row.innerHTML = `
|
|
<div class="flex-1">
|
|
<label class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Location</label>
|
|
<input type="text" value="${phone.location || ''}" 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 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 phone-location">
|
|
</div>
|
|
<div class="flex-1">
|
|
<label class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Phone Number</label>
|
|
<input type="tel" value="${phone.phone || ''}" 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 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 phone-number">
|
|
</div>
|
|
<button type="button" class="mt-6 px-3 py-2 bg-danger-600 text-white hover:bg-danger-700 rounded-lg transition-colors duration-200 text-sm remove-phone">
|
|
<i class="fa-solid fa-trash"></i>
|
|
</button>
|
|
`;
|
|
phoneList.appendChild(row);
|
|
|
|
// Event listeners for changes
|
|
row.querySelector('.phone-location').addEventListener('input', () => updatePhone(index));
|
|
row.querySelector('.phone-number').addEventListener('input', () => updatePhone(index));
|
|
row.querySelector('.remove-phone').addEventListener('click', () => removePhone(index));
|
|
}
|
|
|
|
function updatePhone(index) {
|
|
const row = document.querySelector(`[data-index="${index}"]`);
|
|
const location = row.querySelector('.phone-location').value;
|
|
const phone = row.querySelector('.phone-number').value;
|
|
const current = window.currentPhones[index];
|
|
if (current) {
|
|
current.location = location;
|
|
current.phone = phone;
|
|
if (current.action === 'none') current.action = 'update';
|
|
}
|
|
}
|
|
|
|
function removePhone(index) {
|
|
const row = document.querySelector(`[data-index="${index}"]`);
|
|
row.remove();
|
|
const phone = window.currentPhones[index];
|
|
if (phone.id) {
|
|
phone.action = 'delete';
|
|
} else {
|
|
window.currentPhones.splice(index, 1);
|
|
}
|
|
// Re-index rows
|
|
document.querySelectorAll('#phoneList > div').forEach((r, i) => {
|
|
r.dataset.index = i;
|
|
});
|
|
}
|
|
|
|
document.getElementById('addPhoneBtn').addEventListener('click', () => {
|
|
const index = window.currentPhones.length;
|
|
window.currentPhones.push({location: '', phone: '', action: 'add'});
|
|
addPhoneRow(index);
|
|
});
|
|
|
|
// Validation
|
|
async function validateForm() {
|
|
let isValid = true;
|
|
const requiredFields = ['customerId', 'last'];
|
|
requiredFields.forEach(id => {
|
|
const input = document.getElementById(id);
|
|
if (!input.value.trim()) {
|
|
isValid = false;
|
|
input.classList.add('border-danger-500');
|
|
} else {
|
|
input.classList.remove('border-danger-500');
|
|
}
|
|
});
|
|
|
|
// Email validation
|
|
const email = document.getElementById('email');
|
|
if (email.value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
|
|
isValid = false;
|
|
email.classList.add('border-danger-500');
|
|
} else {
|
|
email.classList.remove('border-danger-500');
|
|
}
|
|
|
|
// Check unique ID for create
|
|
if (!isEditing) {
|
|
try {
|
|
const response = await fetch(`/api/customers/${document.getElementById('customerId').value}`);
|
|
if (response.ok) {
|
|
isValid = false;
|
|
showAlert('Customer ID already exists', 'danger');
|
|
document.getElementById('customerId').classList.add('border-danger-500');
|
|
}
|
|
} catch (error) {}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
// Save customer
|
|
async function saveCustomer() {
|
|
if (!await validateForm()) return;
|
|
|
|
const customerData = {
|
|
id: document.getElementById('customerId').value,
|
|
last: document.getElementById('last').value,
|
|
first: document.getElementById('first').value,
|
|
middle: document.getElementById('middle').value,
|
|
prefix: document.getElementById('prefix').value,
|
|
suffix: document.getElementById('suffix').value,
|
|
title: document.getElementById('title').value,
|
|
group: document.getElementById('group').value,
|
|
a1: document.getElementById('a1').value,
|
|
a2: document.getElementById('a2').value,
|
|
a3: document.getElementById('a3').value,
|
|
city: document.getElementById('city').value,
|
|
abrev: document.getElementById('abrev').value,
|
|
zip: document.getElementById('zip').value,
|
|
email: document.getElementById('email').value,
|
|
dob: document.getElementById('dob').value || null,
|
|
ss_number: document.getElementById('ss_number').value,
|
|
legal_status: document.getElementById('legal_status').value,
|
|
memo: document.getElementById('memo').value
|
|
};
|
|
|
|
try {
|
|
let response;
|
|
if (isEditing) {
|
|
response = await fetch(`/api/customers/${editingCustomerId}`, {
|
|
method: 'PUT',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify(customerData)
|
|
});
|
|
} else {
|
|
response = await fetch('/api/customers/', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify(customerData)
|
|
});
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to save customer');
|
|
}
|
|
|
|
const savedCustomer = await response.json();
|
|
|
|
// Handle phones
|
|
for (const phone of window.currentPhones) {
|
|
if (phone.action === 'add') {
|
|
await fetch(`/api/customers/${savedCustomer.id}/phones`, {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({location: phone.location, phone: phone.phone})
|
|
});
|
|
} else if (phone.action === 'update') {
|
|
await fetch(`/api/customers/${savedCustomer.id}/phones/${phone.id}`, {
|
|
method: 'PUT',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({location: phone.location, phone: phone.phone})
|
|
});
|
|
} else if (phone.action === 'delete') {
|
|
await fetch(`/api/customers/${savedCustomer.id}/phones/${phone.id}`, {
|
|
method: 'DELETE',
|
|
headers: getAuthHeaders()
|
|
});
|
|
}
|
|
}
|
|
|
|
showAlert(`Customer ${isEditing ? 'updated' : 'created'} successfully`, 'success');
|
|
closeCustomerModal();
|
|
loadCustomers(currentPage, currentSearch);
|
|
|
|
} catch (error) {
|
|
showAlert(`Error saving customer: ${error.message}`, 'danger');
|
|
}
|
|
}
|
|
|
|
// Edit customer
|
|
async function editCustomer(customerId) {
|
|
try {
|
|
const response = await fetch(`/api/customers/${customerId}`, {
|
|
headers: getAuthHeaders()
|
|
});
|
|
if (!response.ok) throw new Error('Failed to load customer');
|
|
|
|
const customer = await response.json();
|
|
isEditing = true;
|
|
editingCustomerId = customerId;
|
|
populateCustomerForm(customer);
|
|
showEditCustomerModal();
|
|
|
|
} catch (error) {
|
|
showAlert(`Error loading customer: ${error.message}`, 'danger');
|
|
}
|
|
}
|
|
|
|
// Delete customer
|
|
async function deleteCustomer() {
|
|
if (!confirm('Are you sure you want to delete this customer?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/customers/${editingCustomerId}`, {
|
|
method: 'DELETE',
|
|
headers: getAuthHeaders()
|
|
});
|
|
|
|
if (!response.ok) throw new Error('Failed to delete customer');
|
|
|
|
showAlert('Customer deleted successfully', 'success');
|
|
closeCustomerModal();
|
|
loadCustomers(currentPage, currentSearch);
|
|
|
|
} catch (error) {
|
|
showAlert(`Error deleting customer: ${error.message}`, 'danger');
|
|
}
|
|
}
|
|
|
|
// Populate datalists
|
|
async function populateDatalists() {
|
|
try {
|
|
const groupsResp = await fetch('/api/customers/groups', {headers: getAuthHeaders()});
|
|
const groups = await groupsResp.json();
|
|
const groupList = document.getElementById('groupList');
|
|
groups.forEach(g => {
|
|
const option = document.createElement('option');
|
|
option.value = g.group;
|
|
groupList.appendChild(option);
|
|
});
|
|
|
|
const statesResp = await fetch('/api/customers/states', {headers: getAuthHeaders()});
|
|
const states = await statesResp.json();
|
|
const stateList = document.getElementById('stateList');
|
|
states.forEach(s => {
|
|
const option = document.createElement('option');
|
|
option.value = s.state;
|
|
stateList.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading datalists:', error);
|
|
}
|
|
}
|
|
|
|
// Update setupEventListeners
|
|
function setupEventListeners() {
|
|
// Existing listeners...
|
|
document.getElementById('searchBtn').addEventListener('click', performSearch);
|
|
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') performSearch();
|
|
});
|
|
|
|
document.getElementById('phoneSearchBtn').addEventListener('click', performPhoneSearch);
|
|
document.getElementById('phoneSearch').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') performPhoneSearch();
|
|
});
|
|
|
|
document.getElementById('addCustomerBtn').addEventListener('click', showAddCustomerModal);
|
|
document.getElementById('saveCustomerBtn').addEventListener('click', saveCustomer);
|
|
document.getElementById('deleteCustomerBtn').addEventListener('click', deleteCustomer);
|
|
document.getElementById('statsBtn').addEventListener('click', showStats);
|
|
|
|
// Form validation on blur for customerId
|
|
document.getElementById('customerId').addEventListener('blur', async function() {
|
|
if (!isEditing && this.value) {
|
|
try {
|
|
const response = await fetch(`/api/customers/${this.value}`);
|
|
if (response.ok) {
|
|
showAlert('Customer ID already exists', 'warning');
|
|
this.classList.add('border-danger-500');
|
|
} else {
|
|
this.classList.remove('border-danger-500');
|
|
}
|
|
} catch (error) {}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update DOMContentLoaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (!token) {
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
loadCustomers();
|
|
loadGroups();
|
|
loadStates();
|
|
populateDatalists();
|
|
setupEventListeners();
|
|
clearCustomerForm(); // Initial clear
|
|
}); |