135 lines
6.4 KiB
PHP
135 lines
6.4 KiB
PHP
<?= $this->extend('layout/main') ?>
|
|
|
|
<?= $this->section('content') ?>
|
|
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
|
|
|
|
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-sm-center gap-2 mb-3">
|
|
<h1 class="h4 mb-0">Przegląd paragonu</h1>
|
|
<a href="<?= site_url('receipts') ?>" class="btn btn-link">← Historia importów</a>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-body row g-2">
|
|
<div class="col-md-4"><span class="text-muted small d-block">Sprzedawca</span><?= esc($receipt['merchant'] ?: '—') ?></div>
|
|
<div class="col-md-4"><span class="text-muted small d-block">Data</span><?= esc($receipt['receipt_date'] ?: '—') ?></div>
|
|
<div class="col-md-4"><span class="text-muted small d-block">Suma z paragonu (AI)</span><?= $receipt['total'] !== null ? $fmt($receipt['total']) : '—' ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<?= form_open('receipts/' . $receipt['id'] . '/confirm') ?>
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-header bg-white">
|
|
<strong>Pozycje</strong> — sprawdź i popraw nazwy/kwoty (AI potrafi się pomylić), przypisz kategorię
|
|
(podpowiedź zaznaczona; „— pomiń —” = nie księguj)
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0 align-middle receipt-items">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width:40%">Pozycja</th>
|
|
<th class="text-end" style="width:16%">Kwota (zł)</th>
|
|
<th>Kategoria</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($items)): ?>
|
|
<tr><td colspan="3" class="text-center text-muted py-4">AI nie odczytało żadnych pozycji.</td></tr>
|
|
<?php else: foreach ($items as $it): ?>
|
|
<?php $sel = $it['chosen_category_id'] ?? $it['suggested_category_id']; ?>
|
|
<tr>
|
|
<td data-label="Pozycja">
|
|
<input type="text" name="display[<?= $it['id'] ?>]" class="form-control form-control-sm"
|
|
value="<?= esc($it['display_name'] ?: $it['name']) ?>" maxlength="255">
|
|
<span class="text-muted small">z paragonu: <?= esc($it['name']) ?></span>
|
|
</td>
|
|
<td data-label="Kwota (zł)">
|
|
<input type="number" step="0.01" min="0" name="amount[<?= $it['id'] ?>]"
|
|
class="form-control form-control-sm text-end js-amount"
|
|
value="<?= esc(number_format((float) $it['amount'], 2, '.', '')) ?>">
|
|
</td>
|
|
<td data-label="Kategoria">
|
|
<select name="chosen_category_id[<?= $it['id'] ?>]" class="form-select form-select-sm js-cat">
|
|
<option value="">— pomiń —</option>
|
|
<?php foreach ($categories as $c): ?>
|
|
<option value="<?= $c['id'] ?>" <?= (string) $sel === (string) $c['id'] ? 'selected' : '' ?>>
|
|
<?= esc($c['path_label']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-header bg-white"><strong>Podgląd księgowania</strong> — jedna operacja na kategorię (suma)</div>
|
|
<div class="card-body">
|
|
<ul class="list-group" id="groupPreview"><li class="list-group-item text-muted">—</li></ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid d-sm-flex gap-2 mb-4">
|
|
<button type="submit" class="btn btn-primary">Zaksięguj</button>
|
|
<a href="<?= site_url('receipts/' . $receipt['id'] . '/delete') ?>" class="btn btn-outline-danger"
|
|
onclick="return confirm('Usunąć ten import?')">Usuń import</a>
|
|
</div>
|
|
<?= form_close() ?>
|
|
|
|
<style>
|
|
@media (max-width: 767.98px) {
|
|
.receipt-items thead { display: none; }
|
|
.receipt-items, .receipt-items tbody, .receipt-items tr, .receipt-items td { display: block; width: 100%; }
|
|
.receipt-items tr { padding: .75rem; border-bottom: 1px solid var(--bs-border-color); }
|
|
.receipt-items td { padding: .25rem 0; border: 0; }
|
|
.receipt-items td::before {
|
|
content: attr(data-label);
|
|
display: block;
|
|
margin-bottom: .25rem;
|
|
color: var(--bs-secondary-color);
|
|
font-size: .875em;
|
|
}
|
|
}
|
|
</style>
|
|
<?= $this->endSection() ?>
|
|
|
|
<?= $this->section('scripts') ?>
|
|
<script>
|
|
// Podglad grupowania po kategorii liczony na zywo z pol wiersza (vanilla, bez zaleznosci).
|
|
const fmt = (v) => v.toLocaleString('pl-PL', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' zł';
|
|
const rows = Array.from(document.querySelectorAll('tbody tr')).filter((r) => r.querySelector('.js-cat'));
|
|
const preview = document.getElementById('groupPreview');
|
|
|
|
function labelFor(sel) {
|
|
return sel.options[sel.selectedIndex].text.trim();
|
|
}
|
|
function render() {
|
|
const groups = {};
|
|
rows.forEach((row) => {
|
|
const sel = row.querySelector('.js-cat');
|
|
if (!sel || !sel.value) return;
|
|
const amt = parseFloat(row.querySelector('.js-amount').value) || 0;
|
|
if (!groups[sel.value]) groups[sel.value] = { label: labelFor(sel), sum: 0 };
|
|
groups[sel.value].sum += amt;
|
|
});
|
|
const keys = Object.keys(groups);
|
|
if (!keys.length) {
|
|
preview.innerHTML = '<li class="list-group-item text-muted">Brak przypisanych pozycji — nic nie zostanie zaksięgowane.</li>';
|
|
return;
|
|
}
|
|
const total = keys.reduce((sum, k) => sum + groups[k].sum, 0);
|
|
preview.innerHTML = keys.map((k) =>
|
|
`<li class="list-group-item d-flex flex-column flex-sm-row justify-content-between gap-1"><span>${groups[k].label}</span><strong>${fmt(groups[k].sum)}</strong></li>`
|
|
).join('') +
|
|
`<li class="list-group-item d-flex justify-content-between border-top"><strong>Razem</strong><strong>${fmt(total)}</strong></li>`;
|
|
}
|
|
rows.forEach((row) => {
|
|
row.querySelector('.js-cat').addEventListener('change', render);
|
|
row.querySelector('.js-amount').addEventListener('input', render);
|
|
});
|
|
render();
|
|
</script>
|
|
<?= $this->endSection() ?>
|