update
This commit is contained in:
@@ -3,14 +3,14 @@
|
||||
<?= $this->section('content') ?>
|
||||
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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">Instrumenty</h1>
|
||||
<a href="<?= site_url('instruments/new') ?>" class="btn btn-primary">+ Nowy instrument</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<table class="table table-hover mb-0 align-middle instrument-history">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Instrument</th>
|
||||
@@ -25,17 +25,17 @@
|
||||
<?php if (empty($instruments)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Brak instrumentów.</td></tr>
|
||||
<?php else: foreach ($instruments as $i): ?>
|
||||
<tr>
|
||||
<td><?= esc($i['name']) ?></td>
|
||||
<td class="text-end"><?= $fmt($i['net_invested']) ?></td>
|
||||
<td class="text-end"><?= $i['has_value'] ? $fmt($i['value']) : '<span class="text-muted">brak wyceny</span>' ?></td>
|
||||
<td class="text-end <?= $i['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>">
|
||||
<tr class="instrument-row">
|
||||
<td data-label="Instrument"><?= esc($i['name']) ?></td>
|
||||
<td data-label="Wpłacono netto" class="text-end"><?= $fmt($i['net_invested']) ?></td>
|
||||
<td data-label="Wartość" class="text-end"><?= $i['has_value'] ? $fmt($i['value']) : '<span class="text-muted">brak wyceny</span>' ?></td>
|
||||
<td data-label="Zarobek" class="text-end <?= $i['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>">
|
||||
<?= $i['has_value'] ? $fmt($i['profit']) : '—' ?>
|
||||
</td>
|
||||
<td class="text-end <?= $i['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>">
|
||||
<td data-label="Procent" class="text-end <?= $i['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>">
|
||||
<?= $i['percent'] !== null && $i['has_value'] ? number_format($i['percent'], 2, ',', ' ') . '%' : '—' ?>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<td data-label="Akcje" class="text-end instrument-actions">
|
||||
<a href="<?= site_url('instruments/' . $i['id'] . '/edit') ?>" class="btn btn-sm btn-outline-secondary">Edytuj</a>
|
||||
<a href="<?= site_url('instruments/' . $i['id'] . '/delete') ?>" class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Usunąć instrument?')">Usuń</a>
|
||||
@@ -46,4 +46,33 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media (max-width: 767.98px) {
|
||||
.instrument-history thead { display: none; }
|
||||
.instrument-history, .instrument-history tbody,
|
||||
.instrument-history .instrument-row, .instrument-history .instrument-row td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.instrument-history .instrument-row { padding: .75rem; border-bottom: 1px solid var(--bs-border-color); }
|
||||
.instrument-history .instrument-row td {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .3rem 0;
|
||||
border: 0;
|
||||
text-align: right !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.instrument-history .instrument-row td::before {
|
||||
content: attr(data-label);
|
||||
flex: 0 0 auto;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .875em;
|
||||
text-align: left;
|
||||
}
|
||||
.instrument-history .instrument-actions { align-items: center; flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<?= $this->section('content') ?>
|
||||
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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">Operacje inwestycyjne</h1>
|
||||
<a href="<?= site_url('investments/operations/new') ?>" class="btn btn-primary">+ Nowa operacja</a>
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<table class="table table-hover mb-0 align-middle inv-operation-history">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
@@ -63,19 +63,19 @@
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Brak operacji dla wybranych filtrów.</td></tr>
|
||||
<?php else: foreach ($operations as $o): ?>
|
||||
<?php $isDep = $o['type'] === 'deposit'; ?>
|
||||
<tr>
|
||||
<td><?= esc($o['date']) ?></td>
|
||||
<td><?= esc($o['instrument_name'] ?? '—') ?></td>
|
||||
<td>
|
||||
<tr class="operation-row">
|
||||
<td data-label="Data"><?= esc($o['date']) ?></td>
|
||||
<td data-label="Instrument"><?= esc($o['instrument_name'] ?? '—') ?></td>
|
||||
<td data-label="Typ">
|
||||
<span class="badge <?= $isDep ? 'bg-success' : 'bg-secondary' ?>">
|
||||
<?= $isDep ? 'Wpłata' : 'Wypłata' ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted"><?= esc($o['description'] ?? '') ?></td>
|
||||
<td class="text-end <?= $isDep ? 'amount-income' : 'amount-expense' ?>">
|
||||
<td data-label="Opis" class="text-muted"><?= esc($o['description'] ?? '') ?></td>
|
||||
<td data-label="Kwota" class="text-end <?= $isDep ? 'amount-income' : 'amount-expense' ?>">
|
||||
<?= $isDep ? '+' : '−' ?><?= $fmt($o['amount']) ?>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<td data-label="Akcje" class="text-end operation-actions">
|
||||
<a href="<?= site_url('investments/operations/' . $o['id'] . '/edit') ?>" class="btn btn-sm btn-outline-secondary">Edytuj</a>
|
||||
<a href="<?= site_url('investments/operations/' . $o['id'] . '/delete') ?>" class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Usunąć operację?')">Usuń</a>
|
||||
@@ -86,4 +86,33 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media (max-width: 767.98px) {
|
||||
.inv-operation-history thead { display: none; }
|
||||
.inv-operation-history, .inv-operation-history tbody,
|
||||
.inv-operation-history .operation-row, .inv-operation-history .operation-row td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.inv-operation-history .operation-row { padding: .75rem; border-bottom: 1px solid var(--bs-border-color); }
|
||||
.inv-operation-history .operation-row td {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .3rem 0;
|
||||
border: 0;
|
||||
text-align: right !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.inv-operation-history .operation-row td::before {
|
||||
content: attr(data-label);
|
||||
flex: 0 0 auto;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .875em;
|
||||
text-align: left;
|
||||
}
|
||||
.inv-operation-history .operation-actions { align-items: center; flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -11,14 +11,14 @@ $badges = [
|
||||
];
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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">Import paragonów</h1>
|
||||
<a href="<?= site_url('receipts/new') ?>" class="btn btn-primary">+ Nowy import</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<table class="table table-hover mb-0 align-middle receipt-history">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
@@ -34,14 +34,14 @@ $badges = [
|
||||
<?php if (empty($receipts)): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">Brak zaimportowanych paragonów.</td></tr>
|
||||
<?php else: foreach ($receipts as $r): ?>
|
||||
<tr>
|
||||
<td><?= esc($r['receipt_date'] ?: mb_substr((string) $r['created_at'], 0, 10)) ?></td>
|
||||
<td><?= esc($r['merchant'] ?: '—') ?></td>
|
||||
<td class="text-end"><?= $r['total'] !== null ? $fmt($r['total']) : '—' ?></td>
|
||||
<td class="text-center"><?= (int) $r['items_count'] ?></td>
|
||||
<td class="text-center"><?= (int) $r['operations_count'] ?></td>
|
||||
<td><span class="badge bg-<?= $badges[$r['status']] ?? 'secondary' ?>"><?= esc($r['status']) ?></span></td>
|
||||
<td class="text-end">
|
||||
<tr class="receipt-row">
|
||||
<td data-label="Data"><?= esc($r['receipt_date'] ?: mb_substr((string) $r['created_at'], 0, 10)) ?></td>
|
||||
<td data-label="Sprzedawca"><?= esc($r['merchant'] ?: '—') ?></td>
|
||||
<td data-label="Suma" class="text-end"><?= $r['total'] !== null ? $fmt($r['total']) : '—' ?></td>
|
||||
<td data-label="Pozycje" class="text-center"><?= (int) $r['items_count'] ?></td>
|
||||
<td data-label="Operacje" class="text-center"><?= (int) $r['operations_count'] ?></td>
|
||||
<td data-label="Status"><span class="badge bg-<?= $badges[$r['status']] ?? 'secondary' ?>"><?= esc($r['status']) ?></span></td>
|
||||
<td data-label="Akcje" class="text-end receipt-actions">
|
||||
<?php if ($r['status'] === 'parsed'): ?>
|
||||
<a href="<?= site_url('receipts/' . $r['id'] . '/review') ?>" class="btn btn-sm btn-outline-primary">Przegląd</a>
|
||||
<?php else: ?>
|
||||
@@ -56,4 +56,30 @@ $badges = [
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media (max-width: 767.98px) {
|
||||
.receipt-history thead { display: none; }
|
||||
.receipt-history, .receipt-history tbody, .receipt-history .receipt-row, .receipt-history .receipt-row td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-history .receipt-row { padding: .75rem; border-bottom: 1px solid var(--bs-border-color); }
|
||||
.receipt-history .receipt-row td {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .3rem 0;
|
||||
border: 0;
|
||||
text-align: right !important;
|
||||
}
|
||||
.receipt-history .receipt-row td::before {
|
||||
content: attr(data-label);
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .875em;
|
||||
text-align: left;
|
||||
}
|
||||
.receipt-history .receipt-actions { align-items: center; flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
<div class="card-body">
|
||||
<?= form_open_multipart('receipts', ['class' => 'row g-3']) ?>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Plik paragonu</label>
|
||||
<label class="form-label">Zrób zdjęcie lub wybierz plik paragonu</label>
|
||||
<input type="file" name="receipt" class="form-control" required
|
||||
accept=".jpg,.jpeg,.png,.pdf,.json,image/*,application/pdf,application/json">
|
||||
accept=".jpg,.jpeg,.png,.pdf,.json,image/*,application/pdf,application/json"
|
||||
capture="environment">
|
||||
<div class="form-text">
|
||||
Obsługiwane formaty: skan JPG/PNG, PDF lub JSON. Maks. 10 MB.
|
||||
Na telefonie możesz zrobić zdjęcie paragonu. Obsługiwane formaty: JPG/PNG, PDF lub JSON. Maks. 10 MB.
|
||||
Po wgraniu AI odczyta pozycje i zaproponuje kategorie do zatwierdzenia.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<?= $this->section('content') ?>
|
||||
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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>
|
||||
@@ -23,7 +23,7 @@
|
||||
(podpowiedź zaznaczona; „— pomiń —” = nie księguj)
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<table class="table table-hover mb-0 align-middle receipt-items">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:40%">Pozycja</th>
|
||||
@@ -37,17 +37,17 @@
|
||||
<?php else: foreach ($items as $it): ?>
|
||||
<?php $sel = $it['chosen_category_id'] ?? $it['suggested_category_id']; ?>
|
||||
<tr>
|
||||
<td>
|
||||
<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>
|
||||
<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>
|
||||
<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): ?>
|
||||
@@ -71,12 +71,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<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') ?>
|
||||
@@ -104,7 +120,7 @@
|
||||
return;
|
||||
}
|
||||
preview.innerHTML = keys.map((k) =>
|
||||
`<li class="list-group-item d-flex justify-content-between"><span>${groups[k].label}</span><strong>${fmt(groups[k].sum)}</strong></li>`
|
||||
`<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('');
|
||||
}
|
||||
rows.forEach((row) => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<?= $this->section('content') ?>
|
||||
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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">Wyceny</h1>
|
||||
<a href="<?= site_url('investments/valuations/new') ?>" class="btn btn-primary">+ Nowa wycena</a>
|
||||
</div>
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<table class="table table-hover mb-0 align-middle valuation-history">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
@@ -44,11 +44,11 @@
|
||||
<?php if (empty($valuations)): ?>
|
||||
<tr><td colspan="4" class="text-center text-muted py-4">Brak wycen.</td></tr>
|
||||
<?php else: foreach ($valuations as $v): ?>
|
||||
<tr>
|
||||
<td><?= esc($v['date']) ?></td>
|
||||
<td><?= esc($v['instrument_name'] ?? '—') ?></td>
|
||||
<td class="text-end"><?= $fmt($v['value']) ?></td>
|
||||
<td class="text-end">
|
||||
<tr class="valuation-row">
|
||||
<td data-label="Data"><?= esc($v['date']) ?></td>
|
||||
<td data-label="Instrument"><?= esc($v['instrument_name'] ?? '—') ?></td>
|
||||
<td data-label="Wartość" class="text-end"><?= $fmt($v['value']) ?></td>
|
||||
<td data-label="Akcje" class="text-end valuation-actions">
|
||||
<a href="<?= site_url('investments/valuations/' . $v['id'] . '/edit') ?>" class="btn btn-sm btn-outline-secondary">Edytuj</a>
|
||||
<a href="<?= site_url('investments/valuations/' . $v['id'] . '/delete') ?>" class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Usunąć wycenę?')">Usuń</a>
|
||||
@@ -59,4 +59,33 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media (max-width: 767.98px) {
|
||||
.valuation-history thead { display: none; }
|
||||
.valuation-history, .valuation-history tbody,
|
||||
.valuation-history .valuation-row, .valuation-history .valuation-row td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.valuation-history .valuation-row { padding: .75rem; border-bottom: 1px solid var(--bs-border-color); }
|
||||
.valuation-history .valuation-row td {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .3rem 0;
|
||||
border: 0;
|
||||
text-align: right !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.valuation-history .valuation-row td::before {
|
||||
content: attr(data-label);
|
||||
flex: 0 0 auto;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .875em;
|
||||
text-align: left;
|
||||
}
|
||||
.valuation-history .valuation-actions { align-items: center; flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
Reference in New Issue
Block a user