459 lines
28 KiB
HTML
459 lines
28 KiB
HTML
<!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('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>
|
|
<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 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>
|
|
|
|
<!-- 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">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 30" 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="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>
|
|
|
|
<div>
|
|
<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>
|
|
<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"></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="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>
|
|
</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> |