560 lines
25 KiB
PHP
560 lines
25 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../src/config.php';
|
|
require_once __DIR__ . '/../src/auth.php';
|
|
require_once __DIR__ . '/../src/db.php';
|
|
require_once __DIR__ . '/../src/journal_functions.php';
|
|
require_once __DIR__ . '/../src/icons.php';
|
|
require_login();
|
|
|
|
$msg = '';
|
|
$error = '';
|
|
|
|
// ---- Aktionen ----
|
|
if (isset($_GET['delete'])) {
|
|
$del_id = (int)$_GET['delete'];
|
|
delete_journal_entry($del_id);
|
|
$msg = 'Buchung gelöscht.';
|
|
}
|
|
|
|
// Umsatz-Zusammenfassung speichern
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['form'] ?? '') === 'summary_values') {
|
|
$sv_year_id = (int)$_POST['year_id'];
|
|
$sv_month = (int)$_POST['month'];
|
|
$values = $_POST['summary_val'] ?? [];
|
|
save_journal_monthly_summary_values($sv_year_id, $sv_month, $values);
|
|
$msg = 'Umsatzübersicht gespeichert.';
|
|
}
|
|
|
|
// ---- Jahr und Monat bestimmen ----
|
|
$years = get_journal_years();
|
|
|
|
$year_id = isset($_GET['year_id']) ? (int)$_GET['year_id'] : null;
|
|
$month = isset($_GET['month']) ? (int)$_GET['month'] : (int)date('n');
|
|
|
|
if (!$year_id && $years) {
|
|
$current_cal_year = (int)date('Y');
|
|
foreach ($years as $y) {
|
|
if ($y['year'] == $current_cal_year) {
|
|
$year_id = $y['id'];
|
|
break;
|
|
}
|
|
}
|
|
if (!$year_id) $year_id = $years[0]['id'];
|
|
}
|
|
|
|
$current_year = null;
|
|
foreach ($years as $y) {
|
|
if ($y['id'] == $year_id) {
|
|
$current_year = $y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ---- Daten laden ----
|
|
$columns = [];
|
|
$entries = [];
|
|
$totals = [];
|
|
$summary_items = [];
|
|
$summary_values = [];
|
|
|
|
if ($year_id) {
|
|
$columns = get_journal_columns();
|
|
$entries = get_journal_entries($year_id, $month);
|
|
$totals = calculate_monthly_totals($year_id, $month);
|
|
$summary_items = get_journal_summary_items(true);
|
|
$summary_values_raw = get_journal_monthly_summary_values($year_id, $month);
|
|
foreach ($summary_values_raw as $sv) {
|
|
$summary_values[$sv['summary_item_id']] = $sv['amount'];
|
|
}
|
|
}
|
|
|
|
// Spaltengruppen für colspan
|
|
$groups = [];
|
|
foreach ($columns as $col) {
|
|
$g = $col['group'];
|
|
if (!isset($groups[$g])) $groups[$g] = 0;
|
|
$groups[$g]++;
|
|
}
|
|
|
|
// Konto-Optionen als JSON
|
|
$acct_options_json = json_encode(get_journal_account_options());
|
|
?>
|
|
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Journal <?= $current_year ? (int)$current_year['year'] : '' ?> - <?= journal_month_name_full($month) ?></title>
|
|
<link rel="stylesheet" href="assets/style.css">
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>PIRP</h1>
|
|
<nav>
|
|
<a href="<?= url_for('index.php') ?>"><?= icon_dashboard() ?>Dashboard</a>
|
|
<a href="<?= url_for('invoices.php') ?>"><?= icon_invoices() ?>Rechnungen</a>
|
|
<a href="<?= url_for('customers.php') ?>"><?= icon_customers() ?>Kunden</a>
|
|
<a href="<?= url_for('expenses.php') ?>"><?= icon_expenses() ?>Ausgaben</a>
|
|
<a href="<?= url_for('belegarchiv.php') ?>"><?= icon_archive() ?>Belege</a>
|
|
<a href="<?= url_for('journal.php') ?>" class="active"><?= icon_journal() ?>Journal</a>
|
|
<a href="<?= url_for('euer.php') ?>"><?= icon_euer() ?>EÜR</a>
|
|
<a href="<?= url_for('settings.php') ?>"><?= icon_settings() ?>Einstellungen</a>
|
|
<a href="<?= url_for('logout.php') ?>"><?= icon_logout() ?>Logout (<?= htmlspecialchars($_SESSION['username'] ?? '') ?>)</a>
|
|
<span class="cmd-k-hint" onclick="document.dispatchEvent(new KeyboardEvent('keydown',{key:'k',ctrlKey:true}))"><kbd>Ctrl+K</kbd></span>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
<div class="journal-subnav">
|
|
<a href="<?= url_for('journal.php') ?>" class="active">Monatsansicht</a>
|
|
<a href="<?= url_for('journal_summary.php?year_id=' . $year_id) ?>">Jahressummen</a>
|
|
<a href="<?= url_for('journal_search.php') ?>">Suche</a>
|
|
</div>
|
|
|
|
<?php if ($msg): ?><p class="success"><?= htmlspecialchars($msg) ?></p><?php endif; ?>
|
|
<?php if ($error): ?><p class="error"><?= htmlspecialchars($error) ?></p><?php endif; ?>
|
|
|
|
<?php if (empty($years)): ?>
|
|
<p class="warning">Bitte zuerst ein <a href="<?= url_for('settings.php?tab=journal') ?>">Jahr anlegen</a>.</p>
|
|
<?php else: ?>
|
|
|
|
<!-- Jahr-Dropdown + Monatstabs -->
|
|
<div class="journal-controls">
|
|
<form method="get" class="journal-year-select">
|
|
<label style="margin:0;font-size:10px;">Jahr:
|
|
<select name="year_id" onchange="this.form.submit();" style="width:auto;min-width:70px;">
|
|
<?php foreach ($years as $y): ?>
|
|
<option value="<?= $y['id'] ?>" <?= $y['id'] == $year_id ? 'selected' : '' ?>>
|
|
<?= (int)$y['year'] ?><?= $y['is_closed'] ? ' (geschl.)' : '' ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
<input type="hidden" name="month" value="<?= $month ?>">
|
|
</form>
|
|
|
|
<div class="journal-month-tabs">
|
|
<?php for ($m = 1; $m <= 12; $m++): ?>
|
|
<a href="<?= url_for('journal.php?year_id=' . $year_id . '&month=' . $m) ?>"
|
|
class="<?= $m === $month ? 'active' : '' ?>">
|
|
<?= journal_month_name($m) ?>
|
|
</a>
|
|
<?php endfor; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monatssummen -->
|
|
<div class="journal-month-summary">
|
|
<span class="journal-soll">S: <?= journal_format_amount($totals['_soll'] ?? 0) ?></span>
|
|
<span class="journal-haben">H: <?= journal_format_amount($totals['_haben'] ?? 0) ?></span>
|
|
<?php
|
|
$diff = ($totals['_soll'] ?? 0) - ($totals['_haben'] ?? 0);
|
|
if (abs($diff) > 0.005):
|
|
?>
|
|
<span class="journal-diff-warning">Diff: <?= journal_format_amount($diff) ?></span>
|
|
<?php endif; ?>
|
|
<span class="journal-sep"></span>
|
|
<?php
|
|
$kasse_balance = ($totals['kasse_s'] ?? 0) - ($totals['kasse_h'] ?? 0);
|
|
$bank_balance = ($totals['bank_s'] ?? 0) - ($totals['bank_h'] ?? 0);
|
|
?>
|
|
<span class="journal-kasse">Kasse: <?= journal_format_amount($kasse_balance) ?></span>
|
|
<span class="journal-bank">Bank: <?= journal_format_amount($bank_balance) ?></span>
|
|
<span style="color:var(--text-dim);margin-left:auto;"><?= count($entries) ?> Buchungen</span>
|
|
</div>
|
|
|
|
<?php if (isset($diff) && abs($diff) > 0.005): ?>
|
|
<div class="journal-diff-banner">
|
|
<strong>⚠ Buchung nicht ausgeglichen</strong>
|
|
— Differenz: <strong><?= journal_format_amount($diff) ?></strong>
|
|
<span style="margin-left:10px;font-size:10px;opacity:0.65;">Soll: <?= journal_format_amount($totals['_soll'] ?? 0) ?> · Haben: <?= journal_format_amount($totals['_haben'] ?? 0) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Journal-Tabelle -->
|
|
<div class="journal-table-wrap">
|
|
<table class="journal-table" id="journal-table">
|
|
<thead>
|
|
<!-- Gruppenheader -->
|
|
<tr class="journal-group-header">
|
|
<th></th>
|
|
<th colspan="4"></th>
|
|
<?php foreach ($groups as $gname => $gcount): ?>
|
|
<th colspan="<?= $gcount ?>"><?= htmlspecialchars($gname) ?></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<!-- Spaltenheader -->
|
|
<tr>
|
|
<th class="journal-col-action"></th>
|
|
<th class="journal-col-date">Tag</th>
|
|
<th class="journal-col-att">B</th>
|
|
<th class="journal-col-text">Text</th>
|
|
<th class="journal-col-betrag">Betrag</th>
|
|
<?php foreach ($columns as $col): ?>
|
|
<th class="journal-col-amount journal-<?= $col['side'] ?>"><?= htmlspecialchars($col['label']) ?></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<!-- Monatssummen oben (wie Excel Zeile 2+3) -->
|
|
<tr class="journal-top-sums">
|
|
<td></td>
|
|
<td colspan="4" style="text-align:right;"><strong>S</strong></td>
|
|
<?php foreach ($columns as $col): ?>
|
|
<td class="journal-col-amount journal-<?= $col['side'] ?>">
|
|
<?php if ($col['side'] === 'soll'): ?>
|
|
<strong><?= journal_format_amount($totals[$col['key']] ?? 0) ?></strong>
|
|
<?php endif; ?>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<tr class="journal-top-sums">
|
|
<td></td>
|
|
<td colspan="4" style="text-align:right;"><strong>H</strong></td>
|
|
<?php foreach ($columns as $col): ?>
|
|
<td class="journal-col-amount journal-<?= $col['side'] ?>">
|
|
<?php if ($col['side'] === 'haben'): ?>
|
|
<strong><?= journal_format_amount($totals[$col['key']] ?? 0) ?></strong>
|
|
<?php endif; ?>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="journal-body">
|
|
<?php if (empty($entries)): ?>
|
|
<tr id="empty-row"><td colspan="<?= 6 + count($columns) ?>" style="color:var(--text-dim);padding:6px;">Keine Buchungen.</td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($entries as $entry_row): ?>
|
|
<tr data-id="<?= $entry_row['id'] ?>">
|
|
<td class="journal-col-action">
|
|
<a href="<?= url_for('journal_entry.php?id=' . $entry_row['id']) ?>" title="Bearbeiten">B</a>
|
|
<a href="#" onclick="deleteEntry(<?= $entry_row['id'] ?>);return false;" title="Löschen">X</a>
|
|
<a href="#" onclick="duplicateEntry(<?= $entry_row['id'] ?>);return false;" title="Duplizieren">D</a>
|
|
</td>
|
|
<td class="journal-col-date"><?= htmlspecialchars(date('d.m', strtotime($entry_row['entry_date']))) ?></td>
|
|
<td class="journal-col-att"><?= htmlspecialchars($entry_row['attachment_note'] ?? '') ?></td>
|
|
<td class="journal-col-text">
|
|
<?= htmlspecialchars($entry_row['description']) ?>
|
|
<?php if (($entry_row['source_type'] ?? 'manual') === 'invoice_payment' && !empty($entry_row['invoice_id'])): ?>
|
|
<a href="<?= url_for('invoice_pdf.php?id=' . $entry_row['invoice_id']) ?>" target="_blank" class="source-badge source-invoice" title="Automatisch aus Rechnungszahlung">RE</a>
|
|
<?php elseif (($entry_row['source_type'] ?? 'manual') === 'expense_payment' && !empty($entry_row['expense_id'])): ?>
|
|
<a href="<?= url_for('expenses.php?action=edit&id=' . $entry_row['expense_id']) ?>" class="source-badge source-expense" title="Automatisch aus Ausgabe">AU</a>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="journal-col-betrag"><?= journal_format_amount($entry_row['amount']) ?></td>
|
|
<?php
|
|
$row_amounts = [];
|
|
if (!empty($entry_row['accounts'])) {
|
|
foreach ($entry_row['accounts'] as $acct) {
|
|
$key = map_account_to_column_key($acct, $columns);
|
|
if ($key) {
|
|
$row_amounts[$key] = ($row_amounts[$key] ?? 0) + (float)$acct['amount'];
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<?php foreach ($columns as $col): ?>
|
|
<td class="journal-col-amount journal-<?= $col['side'] ?>">
|
|
<?= isset($row_amounts[$col['key']]) ? journal_format_amount($row_amounts[$col['key']]) : '' ?>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="journal-totals-row">
|
|
<td></td>
|
|
<td colspan="4"><strong>Summen</strong></td>
|
|
<?php foreach ($columns as $col): ?>
|
|
<td class="journal-col-amount journal-<?= $col['side'] ?>">
|
|
<strong><?= journal_format_amount($totals[$col['key']] ?? 0) ?></strong>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Inline-Eingabe -->
|
|
<div id="inline-editor" style="margin-bottom:6px;">
|
|
<div style="display:flex;gap:4px;align-items:center;margin-bottom:3px;">
|
|
<button type="button" onclick="toggleInlineForm()" id="btn-new">+ Neue Buchung</button>
|
|
<a href="<?= url_for('journal_entry.php?year_id=' . $year_id . '&month=' . $month) ?>" style="font-size:10px;color:var(--text-dim);">Vollformular</a>
|
|
</div>
|
|
<div id="inline-form" style="display:none;">
|
|
<form id="inline-entry-form" onsubmit="return saveInlineEntry(event);">
|
|
<input type="hidden" name="year_id" value="<?= $year_id ?>">
|
|
<input type="hidden" name="id" id="inline-id" value="">
|
|
<div style="display:flex;gap:4px;flex-wrap:wrap;margin-bottom:3px;">
|
|
<?php
|
|
$last_date = date('Y-m-d');
|
|
if (!empty($entries)) {
|
|
$last_entry = end($entries);
|
|
$last_date = $last_entry['entry_date'];
|
|
}
|
|
?>
|
|
<input type="date" name="entry_date" id="ie-date" value="<?= htmlspecialchars($last_date) ?>" required style="width:130px;">
|
|
<input type="text" name="description" id="ie-desc" placeholder="Text" required style="flex:2;min-width:150px;">
|
|
<input type="text" name="attachment_note" id="ie-att" placeholder="Beleg" style="width:60px;">
|
|
<input type="number" step="0.01" name="amount" id="ie-amount" placeholder="Betrag" style="width:80px;">
|
|
</div>
|
|
<div class="journal-inline-accounts" id="ie-accounts">
|
|
<table>
|
|
<thead><tr><th>Konto</th><th style="width:20px;">S/H</th><th style="width:80px;">Betrag</th><th style="width:80px;">Notiz</th><th style="width:20px;"></th></tr></thead>
|
|
<tbody id="ie-acct-body"></tbody>
|
|
</table>
|
|
<button type="button" onclick="addInlineAccountRow('','soll','','','','',true);" style="padding:1px 6px;font-size:9px;margin-top:2px;">+Konto</button>
|
|
</div>
|
|
<div style="display:flex;gap:4px;margin-top:3px;">
|
|
<button type="submit">Speichern</button>
|
|
<button type="button" onclick="saveAndNew();" class="secondary">Speichern+Neu</button>
|
|
<button type="button" onclick="cancelInline();" class="secondary">Abbrechen</button>
|
|
<span id="inline-status" style="font-size:10px;color:var(--text-dim);align-self:center;"></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Umsatzübersicht -->
|
|
<?php if ($summary_items): ?>
|
|
<section>
|
|
<h2>Umsatzübersicht <?= journal_month_name_full($month) ?></h2>
|
|
<div>
|
|
<?php
|
|
$er_cats = get_journal_revenue_categories('erloese', true);
|
|
foreach ($er_cats as $cat):
|
|
$cat_key = 'er_' . $cat['id'];
|
|
$cat_total = $totals[$cat_key] ?? 0;
|
|
?>
|
|
<span style="margin-right:12px;font-size:11px;">
|
|
<?= htmlspecialchars($cat['name']) ?>: <strong><?= journal_format_amount($cat_total) ?></strong>
|
|
</span>
|
|
<?php endforeach; ?>
|
|
|
|
<form method="post" style="margin-top:4px;">
|
|
<input type="hidden" name="form" value="summary_values">
|
|
<input type="hidden" name="year_id" value="<?= $year_id ?>">
|
|
<input type="hidden" name="month" value="<?= $month ?>">
|
|
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end;">
|
|
<?php foreach ($summary_items as $item): ?>
|
|
<label style="margin:0;font-size:10px;"><?= htmlspecialchars($item['name']) ?>:
|
|
<input type="number" step="0.01" name="summary_val[<?= $item['id'] ?>]"
|
|
value="<?= htmlspecialchars($summary_values[$item['id']] ?? '0.00') ?>"
|
|
style="max-width:80px;">
|
|
</label>
|
|
<?php endforeach; ?>
|
|
<button type="submit" style="padding:3px 8px;">Speichern</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<?php endif; ?>
|
|
</main>
|
|
|
|
<script src="assets/combobox.js"></script>
|
|
<script>
|
|
const accountOptions = <?= $acct_options_json ?>;
|
|
const yearId = <?= $year_id ?: 0 ?>;
|
|
const currentMonth = <?= $month ?>;
|
|
const lastEntryDate = '<?= $last_date ?>';
|
|
|
|
function addInlineAccountRow(type, side, amount, revId, expId, note, autoFocus) {
|
|
type = type || ''; side = side || 'soll'; amount = amount || '';
|
|
revId = revId || ''; expId = expId || ''; note = note || '';
|
|
|
|
const tbody = document.getElementById('ie-acct-body');
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML =
|
|
'<td><div class="pirp-combobox-wrap"></div>' +
|
|
'<input type="hidden" name="acct_type[]" class="h-type" value="' + type + '">' +
|
|
'<input type="hidden" name="acct_side[]" class="h-side" value="' + side + '">' +
|
|
'<input type="hidden" name="acct_rev_id[]" class="h-rev" value="' + revId + '">' +
|
|
'<input type="hidden" name="acct_exp_id[]" class="h-exp" value="' + expId + '">' +
|
|
'</td>' +
|
|
'<td class="side-label" style="text-align:center;font-weight:700;font-size:10px;">' +
|
|
(side === 'haben' ? 'H' : (type ? 'S' : '')) + '</td>' +
|
|
'<td><input type="number" step="0.01" name="acct_amount[]" value="' + amount + '" style="width:70px;"></td>' +
|
|
'<td><input type="text" name="acct_note[]" value="' + note + '" style="width:70px;"></td>' +
|
|
'<td><a href="#" onclick="this.closest(\'tr\').remove();return false;" style="color:var(--error);font-weight:700;font-size:10px;">X</a></td>';
|
|
tbody.appendChild(tr);
|
|
|
|
const wrap = tr.querySelector('.pirp-combobox-wrap');
|
|
const selectedValue = type ? (type + '|' + side + '|' + revId + '|' + expId) : '';
|
|
var cb = new PirpCombobox(wrap, accountOptions, {
|
|
placeholder: '--Konto--',
|
|
selectedValue: selectedValue,
|
|
onSelect: function(opt) {
|
|
tr.querySelector('.h-type').value = opt.value;
|
|
tr.querySelector('.h-side').value = opt.side;
|
|
tr.querySelector('.h-rev').value = opt.rev_id;
|
|
tr.querySelector('.h-exp').value = opt.exp_id;
|
|
tr.querySelector('.side-label').textContent = opt.side === 'haben' ? 'H' : 'S';
|
|
}
|
|
});
|
|
if (!type && autoFocus) cb.input.focus();
|
|
}
|
|
|
|
function toggleInlineForm() {
|
|
const form = document.getElementById('inline-form');
|
|
const tableWrap = document.querySelector('.journal-table-wrap');
|
|
if (form.style.display === 'none') {
|
|
form.style.display = 'block';
|
|
resetInlineForm();
|
|
// Formhöhe messen und Tabelle entsprechend verkleinern
|
|
const formHeight = form.offsetHeight;
|
|
tableWrap.style.maxHeight = 'calc(70vh - ' + formHeight + 'px)';
|
|
document.getElementById('ie-date').focus();
|
|
} else {
|
|
form.style.display = 'none';
|
|
tableWrap.style.maxHeight = '70vh';
|
|
}
|
|
}
|
|
|
|
function resetInlineForm() {
|
|
document.getElementById('inline-id').value = '';
|
|
document.getElementById('ie-date').value = lastEntryDate;
|
|
document.getElementById('ie-desc').value = '';
|
|
document.getElementById('ie-att').value = '';
|
|
document.getElementById('ie-amount').value = '';
|
|
document.getElementById('ie-acct-body').innerHTML = '';
|
|
addInlineAccountRow();
|
|
addInlineAccountRow();
|
|
document.getElementById('inline-status').textContent = '';
|
|
}
|
|
|
|
function cancelInline() {
|
|
document.getElementById('inline-form').style.display = 'none';
|
|
document.querySelector('.journal-table-wrap').style.maxHeight = '70vh';
|
|
}
|
|
|
|
function saveInlineEntry(e) {
|
|
if (e) e.preventDefault();
|
|
const form = document.getElementById('inline-entry-form');
|
|
const fd = new FormData(form);
|
|
fd.append('action', 'save_entry');
|
|
|
|
document.getElementById('inline-status').textContent = 'Speichert...';
|
|
|
|
fetch('<?= url_for("journal_api.php") ?>', { method: 'POST', body: fd })
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.ok) {
|
|
location.reload();
|
|
} else {
|
|
document.getElementById('inline-status').textContent = data.error || 'Fehler';
|
|
document.getElementById('inline-status').style.color = 'var(--error)';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
document.getElementById('inline-status').textContent = 'Netzwerkfehler';
|
|
document.getElementById('inline-status').style.color = 'var(--error)';
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function saveAndNew() {
|
|
const form = document.getElementById('inline-entry-form');
|
|
const fd = new FormData(form);
|
|
fd.append('action', 'save_entry');
|
|
|
|
document.getElementById('inline-status').textContent = 'Speichert...';
|
|
|
|
fetch('<?= url_for("journal_api.php") ?>', { method: 'POST', body: fd })
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.ok) {
|
|
// Seite neu laden mit Parameter um Formular offen zu halten
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('new', '1');
|
|
window.location.href = url.toString();
|
|
} else {
|
|
document.getElementById('inline-status').textContent = data.error || 'Fehler';
|
|
document.getElementById('inline-status').style.color = 'var(--error)';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
document.getElementById('inline-status').textContent = 'Netzwerkfehler';
|
|
});
|
|
}
|
|
|
|
function pirpConfirm(message, confirmLabel, confirmClass) {
|
|
confirmLabel = confirmLabel || 'OK';
|
|
confirmClass = confirmClass || 'danger';
|
|
return new Promise(function(resolve) {
|
|
var overlay = document.createElement('div');
|
|
overlay.className = 'pirp-confirm-overlay';
|
|
overlay.innerHTML =
|
|
'<div class="pirp-confirm-box">' +
|
|
'<p class="pirp-confirm-msg">' + message + '</p>' +
|
|
'<div class="pirp-confirm-btns">' +
|
|
'<button class="pirp-confirm-ok ' + confirmClass + '">' + confirmLabel + '</button>' +
|
|
'<button class="pirp-confirm-cancel secondary">Abbrechen</button>' +
|
|
'</div>' +
|
|
'</div>';
|
|
document.body.appendChild(overlay);
|
|
|
|
function cleanup(result) {
|
|
document.removeEventListener('keydown', onKey);
|
|
overlay.remove();
|
|
resolve(result);
|
|
}
|
|
|
|
overlay.querySelector('.pirp-confirm-ok').onclick = function() { cleanup(true); };
|
|
overlay.querySelector('.pirp-confirm-cancel').onclick = function() { cleanup(false); };
|
|
overlay.onclick = function(e) { if (e.target === overlay) cleanup(false); };
|
|
|
|
function onKey(e) {
|
|
if (e.key === 'Enter') cleanup(true);
|
|
if (e.key === 'Escape') cleanup(false);
|
|
}
|
|
document.addEventListener('keydown', onKey);
|
|
overlay.querySelector('.pirp-confirm-cancel').focus();
|
|
});
|
|
}
|
|
|
|
async function deleteEntry(id) {
|
|
const ok = await pirpConfirm('Buchung löschen?', 'Löschen', 'danger');
|
|
if (!ok) return;
|
|
const fd = new FormData();
|
|
fd.append('action', 'delete_entry');
|
|
fd.append('id', id);
|
|
|
|
fetch('<?= url_for("journal_api.php") ?>', { method: 'POST', body: fd })
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.ok) location.reload();
|
|
else alert(data.error || 'Fehler');
|
|
});
|
|
}
|
|
|
|
function duplicateEntry(id) {
|
|
window.location = '<?= url_for("journal_entry.php") ?>?duplicate=' + id;
|
|
}
|
|
|
|
// Sticky header: top-Werte dynamisch berechnen
|
|
(function() {
|
|
const thead = document.querySelector('#journal-table thead');
|
|
if (!thead) return;
|
|
const rows = thead.querySelectorAll('tr');
|
|
let top = 0;
|
|
rows.forEach(function(row) {
|
|
row.querySelectorAll('th, td').forEach(function(cell) {
|
|
cell.style.top = top + 'px';
|
|
});
|
|
top += row.offsetHeight;
|
|
});
|
|
})();
|
|
|
|
// Auto-open form wenn ?new=1 in URL (nach Speichern+Neu)
|
|
if (new URLSearchParams(window.location.search).get('new') === '1') {
|
|
// URL-Parameter entfernen ohne Reload
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete('new');
|
|
history.replaceState(null, '', url.toString());
|
|
// Formular öffnen
|
|
toggleInlineForm();
|
|
}
|
|
</script>
|
|
<script src="assets/command-palette.js"></script>
|
|
</body>
|
|
</html>
|