476 lines
30 KiB
HTML
476 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CoreCashbook</title>
|
|
<!-- Chart.js CDN -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
:root {
|
|
--bg-color: #12141c; --surface-color: #1e212b; --surface-hover: #2a2e3d;
|
|
--text-main: #ffffff; --text-muted: #8b92a5;
|
|
--accent-blue: #3b82f6; --accent-red: #ef4444; --accent-green: #10b981; --accent-orange: #f59e0b;
|
|
}
|
|
body { background-color: var(--bg-color); color: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; margin: 0; padding: 20px; box-sizing: border-box; }
|
|
.container { background-color: var(--surface-color); padding: 2rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); width: 100%; max-width: 400px; margin-top: 10vh; }
|
|
.dashboard-container { max-width: 1400px; width: 100%; margin-top: 0; }
|
|
h1, h2, h3 { margin-top: 0; } h1 { font-size: 1.5rem; text-align: center; }
|
|
.form-group { margin-bottom: 1rem; } label { display: block; margin-bottom: 0.5rem; color: var(--text-muted); font-size: 0.9rem; }
|
|
input, select { width: 100%; box-sizing: border-box; padding: 0.75rem; background-color: var(--bg-color); border: 1px solid var(--surface-hover); color: white; border-radius: 6px; outline: none; }
|
|
input:focus, select:focus { border-color: var(--accent-blue); }
|
|
button { width: 100%; padding: 0.75rem; background-color: var(--accent-blue); color: white; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: opacity 0.2s; }
|
|
button:hover { opacity: 0.9; }
|
|
.error-msg { color: var(--accent-red); font-size: 0.85rem; text-align: center; margin-bottom: 1rem; display: none; }
|
|
.hidden { display: none !important; }
|
|
|
|
.panel { background-color: var(--bg-color); border-radius: 8px; padding: 1.5rem; margin-top: 1.5rem; overflow-x: auto; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
|
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--surface-hover); }
|
|
th { color: var(--text-muted); text-transform: uppercase; font-size: 0.75rem; }
|
|
tr:hover { background-color: rgba(255,255,255,0.02); }
|
|
.btn-delete { background: transparent; color: var(--accent-red); border: none; padding: 0; width: auto; font-size: 1.1rem; cursor: pointer; }
|
|
.btn-edit { background: transparent; color: var(--accent-blue); border: none; padding: 0; width: auto; font-size: 1.1rem; cursor: pointer; margin-right: 5px; }
|
|
.btn-export { background-color: var(--surface-hover); border: 1px solid var(--text-muted); color: var(--text-main); width: auto; padding: 0.5rem 1rem; text-decoration: none; display: inline-block; border-radius: 6px; font-size: 0.85rem; cursor: pointer;}
|
|
|
|
.balance-card { background: rgba(59, 130, 246, 0.1); border-left: 4px solid var(--accent-blue); padding: 1.5rem; border-radius: 8px; margin-bottom: 1.5rem; text-align: center; }
|
|
.balance-amount { font-size: 2.5rem; font-weight: bold; margin: 10px 0; }
|
|
.text-green { color: var(--accent-green); } .text-red { color: var(--accent-red); }
|
|
|
|
.modal-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.85); z-index: 2000; display: flex; justify-content: center; align-items: center; }
|
|
.modal-content { background: var(--surface-color); width: 95%; max-width: 1000px; height: 90vh; border-radius: 12px; display: flex; flex-direction: column; padding: 1.5rem; box-shadow: 0 10px 30px rgba(0,0,0,0.8); box-sizing: border-box;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="pdfModal" class="modal-overlay hidden">
|
|
<div class="modal-content">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; border-bottom: 1px solid var(--surface-hover); padding-bottom: 1rem;">
|
|
<h2 style="margin:0;">Beleg Ansicht</h2>
|
|
<button onclick="document.getElementById('pdfModal').classList.add('hidden')" style="background: transparent; color: var(--text-muted); border: none; font-size: 1.5rem; cursor: pointer; padding: 0; width: auto;">✖</button>
|
|
</div>
|
|
<iframe id="pdfFrame" style="flex-grow: 1; width: 100%; border: none; border-radius: 8px; background: white;" src=""></iframe>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="editTxModal" class="modal-overlay hidden">
|
|
<div class="modal-content" style="max-width: 600px; height: auto;">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; border-bottom: 1px solid var(--surface-hover); padding-bottom: 1rem;">
|
|
<h2 style="margin:0;">Buchung bearbeiten</h2>
|
|
<button onclick="document.getElementById('editTxModal').classList.add('hidden')" style="background: transparent; color: var(--text-muted); border: none; font-size: 1.5rem; cursor: pointer; padding: 0; width: auto;">✖</button>
|
|
</div>
|
|
<form id="editTxForm" style="display:flex; flex-direction:column; gap:10px;">
|
|
<input type="hidden" id="editTxId">
|
|
<div style="display:flex; gap:10px;">
|
|
<select id="editTxType" style="flex:1;" required><option value="income">📈 Einnahme</option><option value="expense">📉 Ausgabe</option></select>
|
|
<input type="date" id="editTxDate" style="flex:1;" required>
|
|
</div>
|
|
<div style="display:flex; gap:10px;">
|
|
<input type="text" id="editTxTitle" placeholder="Kurztitel" required style="flex:1;">
|
|
<input type="number" id="editTxAmount" placeholder="Betrag (€)" step="0.01" required style="flex:1;">
|
|
</div>
|
|
<div style="display:flex; gap:10px;">
|
|
<input type="text" id="editTxCompany" placeholder="Firma" style="flex:1;">
|
|
<input type="text" id="editTxCategory" placeholder="Kategorie" style="flex:1;">
|
|
</div>
|
|
<input type="text" id="editTxDescription" placeholder="Beschreibung / Artikel" style="width:100%;">
|
|
|
|
<div style="display:flex; gap:10px;">
|
|
<select id="editTxRecurring" style="flex:1;"><option value="0">Einmalig</option><option value="1">Wiederkehrend</option></select>
|
|
<select id="editTxFreq" style="flex:1;"><option value="">- Intervall -</option><option value="monthly">Monatlich</option><option value="yearly">Jährlich</option></select>
|
|
</div>
|
|
|
|
<label style="display:flex; align-items:center; gap:5px; margin-top:5px; cursor:pointer;"><input type="checkbox" id="editTxIsPaid" style="width:auto;"> Bereits bezahlt?</label>
|
|
|
|
<div style="background:rgba(255,255,255,0.05); padding:10px; border-radius:6px; margin-top:10px;">
|
|
<label>Beleg verwalten (PDF)</label>
|
|
<div id="currentReceiptInfo" style="margin-bottom:10px; font-size:0.9rem;"></div>
|
|
<label style="display:flex; align-items:center; gap:10px; cursor:pointer;"><input type="checkbox" id="editTxRemoveReceipt" style="width:auto;"> Aktuellen Beleg löschen</label>
|
|
<input type="file" id="editTxReceipt" accept="application/pdf" style="margin-top:10px; background: rgba(0,0,0,0.2);">
|
|
</div>
|
|
|
|
<button type="submit" style="margin-top:10px;">Änderungen speichern</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- LOGIN -->
|
|
<div class="container" id="loginView">
|
|
<h1>CoreCashbook</h1>
|
|
<div class="error-msg" id="loginError">Login fehlgeschlagen.</div>
|
|
<form id="loginForm">
|
|
<div class="form-group"><label>Benutzername</label><input type="text" id="username" required></div>
|
|
<div class="form-group"><label>Passwort</label><input type="password" id="password" required></div>
|
|
<button type="submit">Anmelden</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- MAIN APP -->
|
|
<div class="container dashboard-container hidden" id="dashboardView">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--surface-hover); padding-bottom: 1rem; flex-wrap: wrap; gap: 10px;">
|
|
<h1 style="margin: 0;">CoreCashbook</h1>
|
|
<div style="display: flex; gap: 10px;">
|
|
<button onclick="toggleView('dashboard')" style="width:auto; background:transparent; border:1px solid var(--text-muted);">Dashboard</button>
|
|
<button onclick="toggleView('fibu')" style="width:auto; background:transparent; border:1px solid var(--text-muted);">FiBu</button>
|
|
<button onclick="toggleView('logbook')" style="width:auto; background:transparent; border:1px solid var(--text-muted);">Fahrtenbuch</button>
|
|
<button onclick="logout()" style="width:auto; background:var(--accent-red);">Logout</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tabDashboard" class="panel">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
<h3 style="margin:0; color:var(--text-muted);">Dashboard (Nur bezahlte Buchungen)</h3>
|
|
<select id="filterDashboardCat" onchange="loadDashboard()" style="width:auto; min-width: 200px;">
|
|
<option value="">Alle Kategorien</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="balance-card">
|
|
<h3 style="margin:0; color:var(--text-muted);">Gewinn / Verlust (gefiltert)</h3>
|
|
<div class="balance-amount" id="displayBalance">0,00 €</div>
|
|
</div>
|
|
<div style="height: 300px; width: 100%;">
|
|
<canvas id="cashflowChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tabFibu" class="panel hidden">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 10px;">
|
|
<h3 style="margin:0;">Einnahmen & Ausgaben</h3>
|
|
<div style="display: flex; gap: 10px;">
|
|
<select id="filterFibuCat" onchange="loadTransactions()" style="width:auto; min-width: 200px;">
|
|
<option value="">Alle Kategorien</option>
|
|
</select>
|
|
<a href="/api/cashbook/export/transactions" class="btn-export" target="_blank">📄 CSV Export (FiBu)</a>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="txForm" style="display:flex; gap:10px; flex-wrap:wrap; background:rgba(0,0,0,0.2); padding:15px; border-radius:8px; margin-bottom: 1.5rem;">
|
|
<select id="txType" style="flex:1; min-width:120px;" required><option value="income">📈 Einnahme</option><option value="expense" selected>📉 Ausgabe</option></select>
|
|
<input type="date" id="txDate" style="flex:1; min-width:130px;" required>
|
|
<input type="text" id="txTitle" placeholder="Kurztitel" required style="flex:1; min-width:150px;">
|
|
<input type="text" id="txCompany" placeholder="Firma (z.B. Strato AG)" style="flex:1; min-width:150px;">
|
|
<input type="text" id="txDescription" placeholder="Beschreibung / Artikel" style="flex:2; min-width:200px;">
|
|
|
|
<input type="number" id="txAmount" placeholder="Betrag (€)" step="0.01" required style="flex:1; min-width:100px;">
|
|
<input type="text" id="txCategory" placeholder="Kategorie" style="flex:1; min-width:120px;">
|
|
<select id="txRecurring" style="flex:1; min-width:130px;"><option value="0">Einmalig</option><option value="1">Wiederkehrend</option></select>
|
|
<select id="txFreq" style="flex:1; min-width:120px;"><option value="">- Intervall -</option><option value="monthly">Monatlich</option><option value="yearly">Jährlich</option></select>
|
|
<label style="display:flex; align-items:center; gap:5px; margin-top:10px; cursor:pointer;"><input type="checkbox" id="txIsPaid" style="width:auto;" checked> Bereits bezahlt?</label>
|
|
<input type="file" id="txReceipt" accept="application/pdf" style="flex:1; min-width:180px; background: rgba(0,0,0,0.1);" title="Optionaler PDF Beleg">
|
|
<button type="submit" style="width:auto; padding: 0.75rem 1.5rem;">Buchen</button>
|
|
</form>
|
|
|
|
<table>
|
|
<thead><tr><th>Datum</th><th>Typ</th><th>Titel</th><th>Firma</th><th>Beschreibung</th><th>Kategorie</th><th>Betrag</th><th>Status</th><th>Wiederkehrend</th><th style="text-align:center;">Beleg</th><th style="text-align:right;">Aktion</th></tr></thead>
|
|
<tbody id="txTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div id="tabLogbook" class="panel hidden">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
<h3 style="margin:0;">Fahrtenbuch</h3>
|
|
<a href="/api/cashbook/export/logbook" class="btn-export" target="_blank">📄 CSV Export (Fahrtenbuch)</a>
|
|
</div>
|
|
|
|
<form id="lbForm" style="display:flex; gap:10px; flex-wrap:wrap; background:rgba(0,0,0,0.2); padding:15px; border-radius:8px; margin-bottom: 1.5rem;">
|
|
<input type="date" id="lbDate" required style="flex:1; min-width:130px;">
|
|
<input type="text" id="lbStart" placeholder="Start (z.B. Grünheide)" required style="flex:2; min-width:150px;">
|
|
<input type="text" id="lbEnd" placeholder="Ziel (z.B. Berlin)" required style="flex:2; min-width:150px;">
|
|
<input type="number" id="lbKm" placeholder="Distanz (km)" step="0.1" required style="flex:1; min-width:100px;">
|
|
<input type="text" id="lbPurpose" placeholder="Zweck / Kunde" required style="flex:2; min-width:150px;">
|
|
<button type="submit" style="width:auto; padding: 0.75rem 1.5rem;">Eintragen</button>
|
|
</form>
|
|
|
|
<table>
|
|
<thead><tr><th>Datum</th><th>Start</th><th>Ziel</th><th>Zweck</th><th>Distanz</th><th>Aktion</th></tr></thead>
|
|
<tbody id="lbTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let cashflowChartObj = null;
|
|
let globalTransactions = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', checkSession);
|
|
|
|
function checkSession() {
|
|
fetch('/api/me').then(res => res.json()).then(data => {
|
|
if (data.success) {
|
|
loadCategories();
|
|
showDashboard();
|
|
} else {
|
|
document.getElementById('loginView').classList.remove('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadCategories() {
|
|
fetch('/api/cashbook/categories').then(res=>res.json()).then(data => {
|
|
if(data.success) {
|
|
let dashSelect = document.getElementById('filterDashboardCat');
|
|
let fibuSelect = document.getElementById('filterFibuCat');
|
|
let dashVal = dashSelect.value;
|
|
let fibuVal = fibuSelect.value;
|
|
let options = '<option value="">Alle Kategorien</option>';
|
|
data.data.forEach(cat => { options += `<option value="${cat}">${cat}</option>`; });
|
|
dashSelect.innerHTML = options;
|
|
fibuSelect.innerHTML = options;
|
|
dashSelect.value = dashVal;
|
|
fibuSelect.value = fibuVal;
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
let fd = new FormData(); fd.append('username', document.getElementById('username').value); fd.append('password', document.getElementById('password').value);
|
|
fetch('/api/login', { method: 'POST', body: fd }).then(res => res.json()).then(data => {
|
|
if (data.success) { document.getElementById('loginError').style.display = 'none'; checkSession(); }
|
|
else { document.getElementById('loginError').style.display = 'block'; }
|
|
});
|
|
});
|
|
|
|
function logout() { fetch('/api/logout').then(() => { location.reload(); }); }
|
|
|
|
function showDashboard() {
|
|
document.getElementById('loginView').classList.add('hidden');
|
|
document.getElementById('dashboardView').classList.remove('hidden');
|
|
document.getElementById('txDate').valueAsDate = new Date();
|
|
document.getElementById('lbDate').valueAsDate = new Date();
|
|
toggleView('dashboard');
|
|
}
|
|
|
|
function toggleView(tab) {
|
|
document.getElementById('tabDashboard').classList.add('hidden');
|
|
document.getElementById('tabFibu').classList.add('hidden');
|
|
document.getElementById('tabLogbook').classList.add('hidden');
|
|
|
|
if (tab === 'dashboard') { document.getElementById('tabDashboard').classList.remove('hidden'); loadDashboard(); }
|
|
if (tab === 'fibu') { document.getElementById('tabFibu').classList.remove('hidden'); loadTransactions(); }
|
|
if (tab === 'logbook') { document.getElementById('tabLogbook').classList.remove('hidden'); loadLogbook(); }
|
|
}
|
|
|
|
function openPdfModal(id) {
|
|
document.getElementById('pdfFrame').src = `/api/cashbook/receipt?id=${id}`;
|
|
document.getElementById('pdfModal').classList.remove('hidden');
|
|
}
|
|
|
|
function loadDashboard() {
|
|
let cat = document.getElementById('filterDashboardCat').value;
|
|
fetch('/api/cashbook/dashboard?category=' + encodeURIComponent(cat)).then(res=>res.json()).then(data => {
|
|
if(data.success) {
|
|
let balEl = document.getElementById('displayBalance');
|
|
balEl.innerText = parseFloat(data.balance).toFixed(2).replace('.', ',') + ' €';
|
|
balEl.className = 'balance-amount ' + (data.balance >= 0 ? 'text-green' : 'text-red');
|
|
renderChart(data.chart);
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderChart(chartData) {
|
|
const ctx = document.getElementById('cashflowChart').getContext('2d');
|
|
let labels = Object.keys(chartData);
|
|
|
|
let incomes = labels.map(m => chartData[m].income || 0);
|
|
let expenses = labels.map(m => chartData[m].expense || 0);
|
|
let profits = labels.map(m => (chartData[m].income || 0) - (chartData[m].expense || 0));
|
|
|
|
if (cashflowChartObj) { cashflowChartObj.destroy(); }
|
|
|
|
cashflowChartObj = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{ label: 'Einnahmen', data: incomes, backgroundColor: '#10b981' },
|
|
{ label: 'Ausgaben', data: expenses, backgroundColor: '#ef4444' },
|
|
{ label: 'Gewinn / Verlust', data: profits, backgroundColor: '#3b82f6' }
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true, maintainAspectRatio: false,
|
|
scales: {
|
|
y: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#8b92a5' } },
|
|
x: { grid: { display: false }, ticks: { color: '#8b92a5' } }
|
|
},
|
|
plugins: { legend: { labels: { color: '#ffffff' } } }
|
|
}
|
|
});
|
|
}
|
|
|
|
function togglePaid(id, newStatus) {
|
|
let fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('status', newStatus);
|
|
fetch('/api/cashbook/transactions/toggle-paid', { method: 'POST', body: fd }).then(res => res.json()).then(data => {
|
|
if(data.success) { loadTransactions(); loadDashboard(); }
|
|
});
|
|
}
|
|
|
|
function loadTransactions() {
|
|
let cat = document.getElementById('filterFibuCat').value;
|
|
fetch('/api/cashbook/transactions?category=' + encodeURIComponent(cat)).then(res=>res.json()).then(data => {
|
|
let tbody = document.getElementById('txTableBody'); tbody.innerHTML = '';
|
|
if(data.success) {
|
|
globalTransactions = data.data;
|
|
globalTransactions.forEach(tx => {
|
|
let amountStr = parseFloat(tx.amount).toFixed(2).replace('.', ',') + ' €';
|
|
let amountHtml = tx.type === 'income' ? `<span class="text-green">+${amountStr}</span>` : `<span class="text-red">-${amountStr}</span>`;
|
|
let recStr = tx.is_recurring == 1 ? `Ja (${tx.frequency})` : '-';
|
|
let receiptHtml = tx.receipt_path ? `<span onclick="openPdfModal(${tx.id})" style="cursor:pointer; font-size:1.2rem;" title="Rechnung ansehen">📄</span>` : '<span style="color:var(--text-muted);">-</span>';
|
|
|
|
// Status Badge
|
|
let paidStatus = tx.is_paid == 1 ? 1 : 0;
|
|
let newStatus = paidStatus == 1 ? 0 : 1;
|
|
let statusHtml = paidStatus == 1
|
|
? `<span onclick="togglePaid(${tx.id}, ${newStatus})" style="cursor:pointer;" title="Auf 'Offen' setzen">✅ Bezahlt</span>`
|
|
: `<span onclick="togglePaid(${tx.id}, ${newStatus})" style="cursor:pointer; color:var(--accent-orange);" title="Als 'Bezahlt' markieren">⏳ Offen</span>`;
|
|
|
|
tbody.innerHTML += `<tr><td>${tx.date}</td><td>${tx.type === 'income' ? '📈' : '📉'}</td><td><strong>${tx.title}</strong></td><td>${tx.company||'-'}</td><td>${tx.description||'-'}</td><td>${tx.category||'-'}</td><td>${amountHtml}</td><td>${statusHtml}</td><td>${recStr}</td><td style="text-align:center;">${receiptHtml}</td><td style="text-align:right;"><button class="btn-edit" onclick="openEditTxModal(${tx.id})" title="Bearbeiten">✏️</button><button class="btn-delete" onclick="deleteTx(${tx.id})" title="Löschen">✖</button></td></tr>`;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function openEditTxModal(id) {
|
|
let tx = globalTransactions.find(t => t.id == id);
|
|
if (!tx) return;
|
|
|
|
document.getElementById('editTxId').value = tx.id;
|
|
document.getElementById('editTxType').value = tx.type;
|
|
document.getElementById('editTxDate').value = tx.date;
|
|
document.getElementById('editTxTitle').value = tx.title;
|
|
document.getElementById('editTxAmount').value = tx.amount;
|
|
document.getElementById('editTxCompany').value = tx.company || '';
|
|
document.getElementById('editTxCategory').value = tx.category || '';
|
|
document.getElementById('editTxDescription').value = tx.description || '';
|
|
document.getElementById('editTxRecurring').value = tx.is_recurring;
|
|
document.getElementById('editTxFreq').value = tx.frequency || '';
|
|
document.getElementById('editTxIsPaid').checked = (tx.is_paid == 1);
|
|
|
|
document.getElementById('editTxRemoveReceipt').checked = false;
|
|
document.getElementById('editTxReceipt').value = '';
|
|
|
|
let info = document.getElementById('currentReceiptInfo');
|
|
if(tx.receipt_path) {
|
|
info.innerHTML = `<span style="color:var(--accent-green);">Aktuell hinterlegt: 📄 ${tx.receipt_path}</span>`;
|
|
} else {
|
|
info.innerHTML = `<span style="color:var(--text-muted);">Kein Beleg hinterlegt.</span>`;
|
|
}
|
|
|
|
document.getElementById('editTxModal').classList.remove('hidden');
|
|
}
|
|
|
|
document.getElementById('editTxForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
let fd = new FormData();
|
|
fd.append('id', document.getElementById('editTxId').value);
|
|
fd.append('type', document.getElementById('editTxType').value);
|
|
fd.append('date', document.getElementById('editTxDate').value);
|
|
fd.append('title', document.getElementById('editTxTitle').value);
|
|
fd.append('company', document.getElementById('editTxCompany').value);
|
|
fd.append('description', document.getElementById('editTxDescription').value);
|
|
fd.append('amount', document.getElementById('editTxAmount').value);
|
|
fd.append('category', document.getElementById('editTxCategory').value);
|
|
fd.append('is_recurring', document.getElementById('editTxRecurring').value);
|
|
fd.append('frequency', document.getElementById('editTxFreq').value);
|
|
fd.append('is_paid', document.getElementById('editTxIsPaid').checked ? '1' : '0');
|
|
fd.append('remove_receipt', document.getElementById('editTxRemoveReceipt').checked ? '1' : '0');
|
|
|
|
let receiptFile = document.getElementById('editTxReceipt').files[0];
|
|
if(receiptFile) fd.append('receipt', receiptFile);
|
|
|
|
fetch('/api/cashbook/transactions/edit', { method: 'POST', body: fd }).then(res=>res.json()).then(data => {
|
|
if(data.success) {
|
|
document.getElementById('editTxModal').classList.add('hidden');
|
|
loadTransactions();
|
|
loadCategories();
|
|
loadDashboard();
|
|
} else {
|
|
alert(data.error);
|
|
}
|
|
});
|
|
});
|
|
|
|
document.getElementById('txForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
let fd = new FormData();
|
|
fd.append('type', document.getElementById('txType').value);
|
|
fd.append('date', document.getElementById('txDate').value);
|
|
fd.append('title', document.getElementById('txTitle').value);
|
|
fd.append('company', document.getElementById('txCompany').value);
|
|
fd.append('description', document.getElementById('txDescription').value);
|
|
fd.append('amount', document.getElementById('txAmount').value);
|
|
fd.append('category', document.getElementById('txCategory').value);
|
|
fd.append('is_recurring', document.getElementById('txRecurring').value);
|
|
fd.append('frequency', document.getElementById('txFreq').value);
|
|
fd.append('is_paid', document.getElementById('txIsPaid').checked ? '1' : '0');
|
|
|
|
let receiptFile = document.getElementById('txReceipt').files[0];
|
|
if(receiptFile) fd.append('receipt', receiptFile);
|
|
|
|
fetch('/api/cashbook/transactions', { method: 'POST', body: fd }).then(res=>res.json()).then(data => {
|
|
if(data.success) {
|
|
document.getElementById('txTitle').value = '';
|
|
document.getElementById('txCompany').value = '';
|
|
document.getElementById('txDescription').value = '';
|
|
document.getElementById('txAmount').value = '';
|
|
document.getElementById('txReceipt').value = '';
|
|
loadTransactions();
|
|
loadCategories();
|
|
loadDashboard();
|
|
} else {
|
|
alert(data.error);
|
|
}
|
|
});
|
|
});
|
|
|
|
function deleteTx(id) {
|
|
if(!confirm('Buchung wirklich löschen? Der verknüpfte Beleg wird ebenfalls unwiderruflich gelöscht!')) return;
|
|
let fd = new FormData(); fd.append('id', id);
|
|
fetch('/api/cashbook/transactions/delete', { method: 'POST', body: fd }).then(res=>res.json()).then(data => {
|
|
if(data.success) {
|
|
loadTransactions();
|
|
loadCategories();
|
|
loadDashboard();
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- FAHRTENBUCH ---
|
|
function loadLogbook() {
|
|
fetch('/api/cashbook/logbook').then(res=>res.json()).then(data => {
|
|
let tbody = document.getElementById('lbTableBody'); tbody.innerHTML = '';
|
|
if(data.success) {
|
|
data.data.forEach(lb => {
|
|
let kmStr = parseFloat(lb.distance_km).toFixed(1).replace('.', ',') + ' km';
|
|
tbody.innerHTML += `<tr><td>${lb.date}</td><td>${lb.start_location}</td><td>${lb.end_location}</td><td>${lb.purpose}</td><td><strong>${kmStr}</strong></td><td><button class="btn-delete" onclick="deleteLb(${lb.id})">✖</button></td></tr>`;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById('lbForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
let fd = new FormData();
|
|
fd.append('date', document.getElementById('lbDate').value);
|
|
fd.append('start_location', document.getElementById('lbStart').value);
|
|
fd.append('end_location', document.getElementById('lbEnd').value);
|
|
fd.append('distance_km', document.getElementById('lbKm').value);
|
|
fd.append('purpose', document.getElementById('lbPurpose').value);
|
|
|
|
fetch('/api/cashbook/logbook', { method: 'POST', body: fd }).then(res=>res.json()).then(data => {
|
|
if(data.success) { document.getElementById('lbStart').value = ''; document.getElementById('lbEnd').value = ''; document.getElementById('lbKm').value = ''; document.getElementById('lbPurpose').value = ''; loadLogbook(); }
|
|
});
|
|
});
|
|
|
|
function deleteLb(id) {
|
|
if(!confirm('Fahrt wirklich löschen?')) return;
|
|
let fd = new FormData(); fd.append('id', id);
|
|
fetch('/api/cashbook/logbook/delete', { method: 'POST', body: fd }).then(res=>res.json()).then(data => { if(data.success) loadLogbook(); });
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |