customer module + features

This commit is contained in:
2026-02-23 14:09:52 -06:00
parent 5e63adfee8
commit 9ebfd9b8c3
4 changed files with 482 additions and 353 deletions

View File

@@ -1,5 +1,5 @@
// Global state
let customers = [];
let customers = []; // shared, updated by customer-view.js
let quotes = [];
let invoices = [];
let currentQuoteId = null;
@@ -204,50 +204,8 @@ async function uploadLogo() {
}
}
// Customer Management
async function loadCustomers() {
try {
const response = await fetch('/api/customers');
customers = await response.json();
renderCustomers();
} catch (error) {
console.error('Error loading customers:', error);
alert('Error loading customers');
}
}
// =====================================================
// 1. renderCustomers() — ERSETZE komplett
// Zeigt QBO-Status und Export-Button in der Kundenliste
// =====================================================
function renderCustomers() {
const tbody = document.getElementById('customers-list');
tbody.innerHTML = customers.map(customer => {
const lines = [customer.line1, customer.line2, customer.line3, customer.line4].filter(Boolean);
const cityStateZip = [customer.city, customer.state, customer.zip_code].filter(Boolean).join(' ');
let fullAddress = lines.join(', ');
if (cityStateZip) fullAddress += (fullAddress ? ', ' : '') + cityStateZip;
// QBO Status
const qboStatus = customer.qbo_id
? `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-green-100 text-green-800" title="QBO ID: ${customer.qbo_id}">QBO ✓</span>`
: `<button onclick="exportCustomerToQbo(${customer.id})" class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-orange-100 text-orange-800 hover:bg-orange-200 cursor-pointer" title="Kunde nach QBO exportieren">QBO Export</button>`;
return `
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
${customer.name} ${qboStatus}
</td>
<td class="px-6 py-4 text-sm text-gray-500">${fullAddress || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${customer.account_number || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<button onclick="editCustomer(${customer.id})" class="text-blue-600 hover:text-blue-900">Edit</button>
<button onclick="deleteCustomer(${customer.id})" class="text-red-600 hover:text-red-900">Delete</button>
</td>
</tr>`;
}).join('');
}
// --- 2. Credits async laden ---
async function loadCustomerCredits() {
@@ -406,139 +364,7 @@ async function submitDownpayment(customerId, customerQboId) {
if (typeof hideSpinner === 'function') hideSpinner();
}
}
function openCustomerModal(customerId = null) {
currentCustomerId = customerId;
const modal = document.getElementById('customer-modal');
const title = document.getElementById('customer-modal-title');
if (customerId) {
title.textContent = 'Edit Customer';
const customer = customers.find(c => c.id === customerId);
document.getElementById('customer-id').value = customer.id;
document.getElementById('customer-name').value = customer.name;
// Neue Felder befüllen
document.getElementById('customer-line1').value = customer.line1 || '';
document.getElementById('customer-line2').value = customer.line2 || '';
document.getElementById('customer-line3').value = customer.line3 || '';
document.getElementById('customer-line4').value = customer.line4 || '';
document.getElementById('customer-city').value = customer.city || '';
document.getElementById('customer-state').value = customer.state || '';
document.getElementById('customer-zip').value = customer.zip_code || '';
document.getElementById('customer-account').value = customer.account_number || '';
document.getElementById('customer-email').value = customer.email || '';
document.getElementById('customer-phone').value = customer.phone || '';
document.getElementById('customer-taxable').checked = customer.taxable !== false;
} else {
title.textContent = 'New Customer';
document.getElementById('customer-form').reset();
document.getElementById('customer-id').value = '';
document.getElementById('customer-taxable').checked = true;
}
modal.classList.add('active');
}
function closeCustomerModal() {
document.getElementById('customer-modal').classList.remove('active');
currentCustomerId = null;
}
async function handleCustomerSubmit(e) {
e.preventDefault();
const data = {
name: document.getElementById('customer-name').value,
// Neue Felder auslesen
line1: document.getElementById('customer-line1').value,
line2: document.getElementById('customer-line2').value,
line3: document.getElementById('customer-line3').value,
line4: document.getElementById('customer-line4').value,
city: document.getElementById('customer-city').value,
state: document.getElementById('customer-state').value.toUpperCase(),
zip_code: document.getElementById('customer-zip').value,
account_number: document.getElementById('customer-account').value,
email: document.getElementById('customer-email')?.value || '',
phone: document.getElementById('customer-phone')?.value || '',
phone2: '', // Erstmal leer lassen, falls kein Feld im Formular ist
taxable: document.getElementById('customer-taxable')?.checked ?? true
};
try {
const customerId = document.getElementById('customer-id').value;
const url = customerId ? `/api/customers/${customerId}` : '/api/customers';
const method = customerId ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
closeCustomerModal();
loadCustomers();
} else {
alert('Error saving customer');
}
} catch (error) {
console.error('Error:', error);
alert('Error saving customer');
}
}
async function editCustomer(id) {
openCustomerModal(id);
}
async function deleteCustomer(id) {
if (!confirm('Are you sure you want to delete this customer?')) return;
try {
const response = await fetch(`/api/customers/${id}`, { method: 'DELETE' });
if (response.ok) {
loadCustomers();
} else {
alert('Error deleting customer');
}
} catch (error) {
console.error('Error:', error);
alert('Error deleting customer');
}
}
async function exportCustomerToQbo(customerId) {
const customer = customers.find(c => c.id === customerId);
if (!customer) return;
if (!confirm(`Kunde "${customer.name}" nach QuickBooks Online exportieren?`)) return;
showSpinner('Exportiere Kunde nach QBO...');
try {
const response = await fetch(`/api/customers/${customerId}/export-qbo`, { method: 'POST' });
const result = await response.json();
if (response.ok) {
alert(`✅ Kunde "${result.name}" erfolgreich in QBO erstellt (ID: ${result.qbo_id}).`);
// Kunden-Liste neu laden
const custResponse = await fetch('/api/customers');
customers = await custResponse.json();
renderCustomers();
} else {
alert(`❌ Fehler: ${result.error}`);
}
} catch (error) {
console.error('Error exporting customer:', error);
alert('Netzwerkfehler beim Export.');
} finally {
hideSpinner();
}
}
// Quote Management
async function loadQuotes() {
try {