Files
invoice-system/public/index.html
2026-08-19 19:18:45 +02:00

586 lines
36 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quote & Invoice Management - Bay Area Affiliates</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<script src="js/components/customer-search.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<link rel="apple-touch-icon" sizes="192x192" href="/favicon-192.png">
<link rel="stylesheet" href="css/styles.css">
</head>
<body class="bg-gray-100">
<div class="min-h-screen">
<!-- Navigation -->
<nav class="bg-blue-900 text-white shadow-lg sticky top-0 z-40">
<div class="container mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Bay Area Affiliates, Inc.</h1>
<p class="text-sm text-blue-200">Quote & Invoice Management System</p>
</div>
<div class="flex space-x-4">
<button onclick="showTab('quotes')" id="tab-quotes" class="px-4 py-2 rounded bg-blue-800 tab-btn">Quotes</button>
<button onclick="showTab('invoices')" id="tab-invoices" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Invoices</button>
<button onclick="showTab('customers')" id="tab-customers" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Customers</button>
<button onclick="showTab('accounting')" id="tab-accounting" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Accounting</button>
<button onclick="showTab('reports')" id="tab-reports" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Reports</button>
<button onclick="showTab('salestax')" id="tab-salestax" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Sales Tax</button>
<button onclick="showTab('settings')" id="tab-settings" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Settings</button>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="container mx-auto px-6 py-8">
<!-- Quotes Tab -->
<div id="quotes-tab" class="tab-content">
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-bold text-gray-800">Quotes</h2>
<button onclick="openQuoteModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md">
+ New Quote
</button>
</div>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Quote #</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="quotes-list" class="bg-white divide-y divide-gray-200">
</tbody>
</table>
</div>
</div>
<!-- Invoices Tab -->
<div id="invoices-tab" class="tab-content hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-3xl font-bold text-gray-800">Invoices</h2>
<div class="flex items-center gap-6">
<button onclick="window.invoiceView.syncFromQBO()" class="px-6 py-3 bg-amber-300 text-gray-800 rounded-lg font-semibold shadow-md hover:bg-amber-400">
⟳ Sync from QBO
</button>
<button onclick="openInvoiceModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md">
+ New Invoice
</button>
</div>
</div>
<div id="invoice-toolbar"></div>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice #</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Send Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Terms</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="invoices-list" class="bg-white divide-y divide-gray-200">
</tbody>
</table>
</div>
</div>
<!-- Customers Tab -->
<div id="customers-tab" class="tab-content hidden">
<div id="customer-toolbar"></div>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Address</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Account #</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="customers-list" class="bg-white divide-y divide-gray-200">
</tbody>
</table>
</div>
</div>
<!-- Accounting Tab -->
<div id="accounting-tab" class="tab-content hidden">
<div id="accounting-toolbar"></div>
<section class="mb-6">
<h3 class="text-md font-semibold text-gray-700 mb-3">Accounts Overview</h3>
<div id="accounting-accounts"></div>
</section>
<section class="mb-6">
<h3 class="text-md font-semibold text-gray-700 mb-3">Customer Credits</h3>
<div id="accounting-credits"></div>
</section>
<section class="mb-6">
<h3 class="text-md font-semibold text-gray-700 mb-3">Register</h3>
<div id="accounting-register-controls"></div>
</section>
<section class="mb-6">
<h3 class="text-md font-semibold text-gray-700 mb-3">Expenses</h3>
<div id="accounting-expenses"></div>
</section>
</div>
<!-- Sales Tax Tab -->
<div id="salestax-tab" class="tab-content hidden">
<div class="mb-6">
<h2 class="text-3xl font-bold text-gray-800">Sales Tax</h2>
<p class="text-sm text-gray-500 mt-1">Manual period tracking. Payments must be recorded in QBO separately.</p>
</div>
<div id="salestax-content"></div>
</div>
<!-- Reports Tab -->
<div id="reports-tab" class="tab-content hidden">
<div class="mb-6">
<h2 class="text-3xl font-bold text-gray-800">Reports</h2>
<p class="text-sm text-gray-500 mt-1">Due-diligence reporting. Enable anonymization before sharing exports externally.</p>
</div>
<div id="reports-content"></div>
</div>
<!-- Settings Tab -->
<div id="settings-tab" class="tab-content hidden">
<div class="mb-6">
<h2 class="text-3xl font-bold text-gray-800">Settings</h2>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-semibold mb-4">Company Logo</h3>
<p class="text-gray-600 mb-4">Upload your company logo to appear on quotes and invoices. Recommended size: 200x200px (PNG or JPG)</p>
<div class="mb-4">
<div id="logo-preview" class="mb-4 hidden">
<p class="text-sm text-gray-600 mb-2">Current Logo:</p>
<img id="logo-image" src="" alt="Company Logo" class="h-20 border border-gray-300 rounded">
</div>
<input type="file" id="logo-upload" accept="image/png,image/jpeg,image/jpg,image/gif" class="hidden">
<button onclick="document.getElementById('logo-upload').click()"
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg">
Choose Logo
</button>
<span id="logo-filename" class="ml-4 text-gray-600"></span>
</div>
<button onclick="uploadLogo()" id="upload-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg disabled:bg-gray-400" disabled>
Upload Logo
</button>
<div id="upload-status" class="mt-4"></div>
<hr class="my-8 border-gray-200">
<h3 class="text-xl font-semibold mb-4 text-gray-800">Invoice Workers</h3>
<p class="text-gray-600 mb-4">
Comma-separated list of workers available in the invoice "Worker" dropdown.
Example: <code class="bg-gray-100 px-1 rounded">Wallie, Elvin, Andreas</code>
</p>
<div class="mb-4">
<input type="text" id="workers-input"
class="w-full max-w-lg px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
placeholder="Wallie, Elvin, Andreas">
</div>
<button onclick="saveWorkerSettings()"
class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg">
Save Worker List
</button>
<div id="workers-status" class="mt-4"></div>
<hr class="my-8 border-gray-200">
<h3 class="text-xl font-semibold mb-4 text-gray-800">QBO Rechnungs-Import</h3>
<p class="text-gray-600 mb-2">
Importiert alle <strong>unbezahlten</strong> Rechnungen aus QuickBooks Online in dein lokales System.
</p>
<ul class="text-sm text-gray-500 mb-4 list-disc list-inside">
<li>Bereits importierte Rechnungen werden übersprungen</li>
<li>Nur Kunden die lokal mit QBO verknüpft sind</li>
<li>Line Items (Labor/Parts) werden mit importiert</li>
</ul>
<button onclick="importFromQBO()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-semibold shadow-md flex items-center">
<span class="mr-2">📥</span> Unbezahlte Rechnungen importieren
</button>
<div id="qbo-import-result" class="mt-4 hidden"></div>
<hr class="my-8 border-gray-200">
<h3 class="text-xl font-semibold mb-4 text-gray-800">QuickBooks Online Authorization</h3>
<p class="text-gray-600 mb-4">
Wenn der Token abgelaufen ist oder die Verbindung fehlschlägt,
hier neu autorisieren. Du wirst zu Intuit weitergeleitet.
</p>
<div class="flex items-center space-x-4 mb-4">
<a href="/auth/qbo"
class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg font-semibold shadow-md inline-flex items-center">
🔑 Authorize QBO
</a>
<span id="qbo-status" class="text-sm text-gray-500">Checking...</span>
</div>
<script>
fetch('/api/qbo/status')
.then(r => r.json())
.then(data => {
const el = document.getElementById('qbo-status');
if (data.connected) {
el.innerHTML = '<span class="text-green-600">✅ Connected (Realm: ' + data.realmId + ')</span>';
} else {
el.innerHTML = '<span class="text-red-600">❌ Not connected — please authorize</span>';
}
})
.catch(() => {
document.getElementById('qbo-status').innerHTML = '<span class="text-gray-400">Status unknown</span>';
});
</script>
<hr class="my-8 border-gray-200">
<h3 class="text-xl font-semibold mb-4 text-gray-800">QuickBooks Online Connection Test</h3>
<p class="text-gray-600 mb-4">Test the connection and token refresh logic by fetching a report of overdue invoices (> 30 days) directly from QBO.</p>
<button onclick="checkQboOverdue()" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-lg font-semibold shadow-md flex items-center">
<span id="qbo-btn-icon" class="mr-2">📡</span> Test Connection & Get Overdue Report
</button>
<div id="qbo-result" class="mt-6 hidden">
<h4 class="font-bold text-gray-700 mb-2">Results from QBO:</h4>
<div class="bg-gray-50 rounded-lg border border-gray-200 overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Inv #</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Customer</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Due Date</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase">Balance</th>
</tr>
</thead>
<tbody id="qbo-result-list" class="divide-y divide-gray-200 text-sm">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quote Modal -->
<div id="quote-modal" class="modal fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full items-center justify-center z-50">
<div class="relative mx-auto p-8 border w-full max-w-6xl shadow-lg rounded-lg bg-white my-8">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-900" id="quote-modal-title">New Quote</h3>
<button onclick="closeQuoteModal()" class="text-gray-400 hover:text-gray-500">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<form id="quote-form" class="space-y-6">
<div class="grid grid-cols-3 gap-4">
<div x-data="customerSearch('quote')" class="relative">
<label class="block text-sm font-medium text-gray-700 mb-1">Customer</label>
<div class="relative">
<input
type="text"
x-model="search"
@click="open = true"
@focus="open = true"
@keydown.escape="open = false"
@keydown.arrow-down.prevent="highlightNext()"
@keydown.arrow-up.prevent="highlightPrev()"
@keydown.enter.prevent="selectHighlighted()"
placeholder="Search customer..."
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
/>
<input type="hidden" id="quote-customer" :value="selectedId" required>
<div
x-show="open && filteredCustomers.length > 0"
@click.away="open = false"
x-transition
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto"
>
<template x-for="(customer, index) in filteredCustomers" :key="customer.id">
<div
@click="selectCustomer(customer)"
:class="{'bg-blue-100': index === highlighted, 'hover:bg-gray-100': index !== highlighted}"
class="px-4 py-2 cursor-pointer text-sm"
>
<div class="font-medium" x-text="customer.name"></div>
<div class="text-xs text-gray-500" x-text="customer.city + ', ' + customer.state"></div>
</div>
</template>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="quote-date" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
</div>
<div class="flex items-center pt-6">
<input type="checkbox" id="quote-tax-exempt"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="quote-tax-exempt" class="ml-2 block text-sm text-gray-900">Tax Exempt</label>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-3">
<label class="block text-sm font-medium text-gray-700">Items</label>
<button type="button" onclick="addQuoteItem()"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm">
+ Add Item
</button>
</div>
<div id="quote-items"></div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="space-y-2 text-right">
<div class="flex justify-end items-center">
<span class="text-sm font-medium text-gray-700 mr-4">Subtotal:</span>
<span id="quote-subtotal" class="text-lg font-semibold">$0.00</span>
</div>
<div id="quote-tax-row" class="flex justify-end items-center">
<span class="text-sm font-medium text-gray-700 mr-4">Tax (8.25%):</span>
<span id="quote-tax" class="text-lg font-semibold">$0.00</span>
</div>
<div class="flex justify-end items-center pt-2 border-t border-gray-300">
<span class="text-lg font-bold text-gray-900 mr-4">TOTAL:</span>
<span id="quote-total" class="text-2xl font-bold text-blue-600">$0.00</span>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" onclick="closeQuoteModal()"
class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">Cancel</button>
<button type="submit"
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Save Quote</button>
</div>
</form>
</div>
</div>
<!-- Invoice Modal -->
<div id="invoice-modal" class="modal fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full items-center justify-center z-50">
<div class="relative mx-auto p-8 border w-full max-w-6xl shadow-lg rounded-lg bg-white my-8">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-900" id="invoice-modal-title">New Invoice</h3>
<button onclick="closeInvoiceModal()" class="text-gray-400 hover:text-gray-500">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<form id="invoice-form" class="space-y-6">
<div class="grid grid-cols-6 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Invoice #</label>
<input type="text" id="invoice-number" pattern="[0-9]*"
placeholder="Auto (QBO)"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 bg-gray-50"
title="Optional — wird beim QBO Export automatisch vergeben">
</div>
<div x-data="customerSearch('invoice')" class="relative">
<label class="block text-sm font-medium text-gray-700 mb-1">Customer</label>
<div class="relative">
<input
type="text"
x-model="search"
@click="open = true"
@focus="open = true"
@keydown.escape="open = false"
@keydown.arrow-down.prevent="highlightNext()"
@keydown.arrow-up.prevent="highlightPrev()"
@keydown.enter.prevent="selectHighlighted()"
placeholder="Search customer..."
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
/>
<input type="hidden" id="invoice-customer" :value="selectedId" required>
<div
x-show="open && filteredCustomers.length > 0"
@click.away="open = false"
x-transition
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto"
>
<template x-for="(customer, index) in filteredCustomers" :key="customer.id">
<div
@click="selectCustomer(customer)"
:class="{'bg-blue-100': index === highlighted, 'hover:bg-gray-100': index !== highlighted}"
class="px-4 py-2 cursor-pointer text-sm"
>
<div class="font-medium" x-text="customer.name"></div>
<div class="text-xs text-gray-500" x-text="customer.city + ', ' + customer.state"></div>
</div>
</template>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bill To Name (optional)</label>
<input type="text" id="invoice-bill-to-name" placeholder="Default: Company name"
class="w-full px-4 py-2 border border-gray-300 rounded-md">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="invoice-date" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Send Date</label>
<input type="date" id="invoice-send-date"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
title="Wann soll die Rechnung versendet werden?">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Terms</label>
<input type="text" id="invoice-terms" value="Net 14" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
</div>
<div class="flex items-center gap-6 pt-6 w-max">
<div class="flex items-center">
<input type="checkbox" id="invoice-tax-exempt"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="invoice-tax-exempt" class="ml-2 block text-sm text-gray-900">Tax Exempt</label>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="invoice-recurring"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="invoice-recurring" class="text-sm text-gray-900">Recurring</label>
<div id="invoice-recurring-group" style="display: none;">
<select id="invoice-recurring-interval"
class="px-2 py-1 border border-gray-300 rounded-md text-sm bg-white">
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Authorization (optional)</label>
<input type="text" id="invoice-authorization"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
placeholder="P.O. Number, Authorization Code, etc.">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Technician</label>
<select id="invoice-worker"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
<option value="">— None —</option>
</select>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-3">
<label class="block text-sm font-medium text-gray-700">Items</label>
<button type="button" onclick="addInvoiceItem()"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm">
+ Add Item
</button>
</div>
<div id="invoice-items"
class="overflow-y-auto pr-1"
style="max-height: 40vh; min-height: 80px;">
</div>
</div>
<!-- Guthaben aus QBO: erscheint nur, wenn der gewaehlte Kunde einen
negativen A/R-Saldo hat. Fuehrend bleibt QBO. -->
<div id="invoice-credit-banner"
class="hidden px-4 py-3 bg-green-50 border border-green-200 rounded-lg flex flex-wrap items-center gap-3">
<span class="text-sm text-green-800">💰 <span id="invoice-credit-available-text"></span></span>
<button type="button" id="invoice-credit-apply-btn"
onclick="window.invoiceModal.applyCustomerCredit()"
class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded-md text-sm font-medium">
Apply to this invoice
</button>
<span class="text-xs text-green-700">Apply it in QBO as well when receiving payment, then run Sync payments.</span>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="space-y-2 text-right">
<div class="flex justify-end items-center">
<span class="text-sm font-medium text-gray-700 mr-4">Subtotal:</span>
<span id="invoice-subtotal" class="text-lg font-semibold">$0.00</span>
</div>
<div id="invoice-tax-row" class="flex justify-end items-center">
<span class="text-sm font-medium text-gray-700 mr-4">Tax (8.25%):</span>
<span id="invoice-tax" class="text-lg font-semibold">$0.00</span>
</div>
<div class="flex justify-end items-center pt-2 border-t border-gray-300">
<span class="text-lg font-bold text-gray-900 mr-4">TOTAL:</span>
<span id="invoice-total" class="text-2xl font-bold text-blue-600">$0.00</span>
</div>
<!-- Guthabenabzug: mindert bewusst weder Subtotal noch Tax -->
<div id="invoice-credit-fields" class="pt-2 border-t border-gray-200" style="display:none">
<div class="flex flex-wrap justify-end items-center gap-2">
<span class="text-sm font-medium text-green-700">Less: credit on account</span>
<input type="text" id="invoice-credit-memo" maxlength="255"
placeholder="e.g. Double payment Check #3612"
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm w-72">
<span class="text-sm text-green-700">$</span>
<input type="number" id="invoice-credit-applied" step="0.01" min="0" value="0"
class="w-28 px-2 py-1.5 border border-gray-300 rounded-md text-sm text-right font-semibold">
<button type="button" onclick="window.invoiceModal.clearCustomerCredit()"
class="text-red-400 hover:text-red-600 text-sm px-1" title="Remove credit"></button>
</div>
<div class="flex justify-end items-center pt-2">
<span class="text-lg font-bold text-gray-900 mr-4">BALANCE DUE:</span>
<span id="invoice-balance-due" class="text-2xl font-bold text-green-700">$0.00</span>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" onclick="closeInvoiceModal()"
class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">Cancel</button>
<button type="submit"
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Save Invoice</button>
</div>
</form>
</div>
</div>
<!-- Single module entry point — all JS loaded from here -->
<script type="module" src="js/app.js"></script>
</body>
</html>