customer module + features
This commit is contained in:
176
public/app.js
176
public/app.js
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user