first commit

This commit is contained in:
2026-07-06 20:16:00 +02:00
commit 352e6d9e22
147 changed files with 12242 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<div class="row justify-content-center mt-5">
<div class="col-md-5 col-lg-4">
<div class="card shadow-sm">
<div class="card-body p-4">
<h1 class="h4 mb-4 text-center">finansePRO</h1>
<?= form_open('login') ?>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" required autofocus
value="<?= esc(old('email')) ?>">
</div>
<div class="mb-3">
<label class="form-label">Hasło</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">Zaloguj</button>
<?= form_close() ?>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+41
View File
@@ -0,0 +1,41 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<h1 class="h4 mb-3"><?= esc($title) ?></h1>
<div class="card shadow-sm">
<div class="card-body">
<?php $action = $cat ? site_url('categories/' . $cat['id']) : site_url('categories'); ?>
<?= form_open($action) ?>
<div class="mb-3">
<label class="form-label">Nazwa</label>
<input type="text" name="name" class="form-control" required
value="<?= esc(old('name', $cat['name'] ?? '')) ?>">
</div>
<div class="mb-3">
<label class="form-label">Typ</label>
<?php $t = old('type', $cat['type'] ?? 'expense'); ?>
<select name="type" class="form-select" required>
<option value="income" <?= $t === 'income' ? 'selected' : '' ?>>Przychód</option>
<option value="expense" <?= $t === 'expense' ? 'selected' : '' ?>>Wydatek</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Kategoria nadrzędna (opcjonalnie)</label>
<?php $p = old('parent_id', $cat['parent_id'] ?? ''); ?>
<select name="parent_id" class="form-select">
<option value="">— brak —</option>
<?php foreach ($parents as $pc): ?>
<?php if ($cat && $pc['id'] == $cat['id']) continue; ?>
<option value="<?= $pc['id'] ?>" <?= (string) $p === (string) $pc['id'] ? 'selected' : '' ?>>
<?= esc($pc['indent_label']) ?> (<?= $pc['type'] === 'income' ? 'przychód' : 'wydatek' ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="<?= site_url('categories') ?>" class="btn btn-link">Anuluj</a>
<?= form_close() ?>
</div>
</div>
<?= $this->endSection() ?>
+43
View File
@@ -0,0 +1,43 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Kategorie</h1>
<a href="<?= site_url('categories/new') ?>" class="btn btn-primary">+ Nowa kategoria</a>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Nazwa</th>
<th>Typ</th>
<th class="text-end">Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($categories)): ?>
<tr><td colspan="3" class="text-center text-muted py-4">Brak kategorii.</td></tr>
<?php else: foreach ($categories as $c): ?>
<tr>
<td><?= esc($c['indent_label']) ?></td>
<td>
<?php if ($c['type'] === 'income'): ?>
<span class="badge bg-success">Przychód</span>
<?php else: ?>
<span class="badge bg-danger">Wydatek</span>
<?php endif; ?>
</td>
<td class="text-end">
<a href="<?= site_url('categories/' . $c['id'] . '/edit') ?>" class="btn btn-sm btn-outline-secondary">Edytuj</a>
<a href="<?= site_url('categories/' . $c['id'] . '/delete') ?>" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Usunąć kategorię?')">Usuń</a>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+94
View File
@@ -0,0 +1,94 @@
<?= $this->extend('layout/main') ?>
<?= $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 flex-wrap gap-2">
<h1 class="h4 mb-0">Pulpit finansów</h1>
<?= form_open('finances', ['method' => 'get', 'class' => 'row g-2 align-items-end']) ?>
<div class="col-auto">
<label class="form-label small mb-1">Od</label>
<input type="date" name="from" class="form-control form-control-sm" value="<?= esc($from) ?>">
</div>
<div class="col-auto">
<label class="form-label small mb-1">Do</label>
<input type="date" name="to" class="form-control form-control-sm" value="<?= esc($to) ?>">
</div>
<div class="col-auto">
<button class="btn btn-sm btn-outline-primary">Pokaż</button>
</div>
<?= form_close() ?>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Przychody</div>
<div class="value amount-income"><?= $fmt($totals['income']) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Wydatki</div>
<div class="value amount-expense"><?= $fmt($totals['expense']) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Saldo</div>
<div class="value <?= $totals['balance'] >= 0 ? 'amount-income' : 'amount-expense' ?>"><?= $fmt($totals['balance']) ?></div>
</div></div></div>
</div>
<div class="row g-3">
<div class="col-12"><div class="card shadow-sm"><div class="card-body">
<h2 class="h6">Bilans miesięczny</h2>
<div id="chartMonthly"></div>
</div></div></div>
<div class="col-12"><div class="card shadow-sm"><div class="card-body">
<h2 class="h6">Wydatki wg kategorii</h2>
<div id="chartByCat"></div>
</div></div></div>
<div class="col-12"><div class="card shadow-sm"><div class="card-body">
<h2 class="h6">Saldo skumulowane</h2>
<div id="chartRunning"></div>
</div></div></div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
const monthly = <?= json_encode($monthly, JSON_UNESCAPED_UNICODE) ?>;
const byCat = <?= json_encode($byCat, JSON_UNESCAPED_UNICODE) ?>;
const running = <?= json_encode($running, JSON_UNESCAPED_UNICODE) ?>;
new ApexCharts(document.querySelector('#chartMonthly'), {
chart: { type: 'bar', height: 320 },
series: [
{ name: 'Przychody', data: monthly.map(r => Number(r.income)) },
{ name: 'Wydatki', data: monthly.map(r => Number(r.expense)) }
],
xaxis: { categories: monthly.map(r => r.ym) },
colors: ['#1a7f37', '#c62828'],
dataLabels: { enabled: false },
noData: { text: 'Brak danych w wybranym zakresie' }
}).render();
new ApexCharts(document.querySelector('#chartByCat'), {
chart: { type: 'bar', height: Math.max(320, byCat.length * 36) },
plotOptions: { bar: { horizontal: true, borderRadius: 3, distributed: true } },
series: [{ name: 'Wydatki', data: byCat.map(r => Number(r.total)) }],
xaxis: { categories: byCat.map(r => r.name) },
colors: ['#c62828', '#1565c0', '#2e7d32', '#f9a825', '#6a1b9a', '#00838f',
'#ef6c00', '#c2185b', '#5d4037', '#455a64', '#7cb342', '#0277bd'],
legend: { show: false },
dataLabels: { enabled: false },
noData: { text: 'Brak wydatków w zakresie' }
}).render();
new ApexCharts(document.querySelector('#chartRunning'), {
chart: { type: 'area', height: 300 },
series: [{ name: 'Saldo', data: running.map(r => Number(r.balance)) }],
xaxis: { categories: running.map(r => r.date), type: 'category' },
colors: ['#1565c0'],
stroke: { curve: 'smooth', width: 2 },
dataLabels: { enabled: false },
noData: { text: 'Brak danych w wybranym zakresie' }
}).render();
</script>
<?= $this->endSection() ?>
+52
View File
@@ -0,0 +1,52 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<?php $fmt = static fn ($v) => number_format((float) $v, 2, ',', ' ') . ' zł'; ?>
<h1 class="h4 mb-3">Pulpit</h1>
<div class="card shadow-sm mb-4 border-primary">
<div class="card-body text-center">
<div class="text-muted small">Majątek razem (saldo bieżące + wartość inwestycji)</div>
<div class="value <?= $net_worth >= 0 ? 'amount-income' : 'amount-expense' ?>" style="font-size:2rem;font-weight:700;">
<?= $fmt($net_worth) ?>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-6">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h6 mb-0">Finanse bieżące <span class="text-muted small">(rok <?= esc(substr($from, 0, 4)) ?>)</span></h2>
<a href="<?= site_url('finances') ?>" class="btn btn-sm btn-outline-primary">Szczegóły →</a>
</div>
<div class="row g-2">
<div class="col-4"><div class="text-muted small">Przychody</div>
<div class="fw-semibold amount-income"><?= $fmt($fin['income']) ?></div></div>
<div class="col-4"><div class="text-muted small">Wydatki</div>
<div class="fw-semibold amount-expense"><?= $fmt($fin['expense']) ?></div></div>
<div class="col-4"><div class="text-muted small">Saldo</div>
<div class="fw-semibold <?= $fin['balance'] >= 0 ? 'amount-income' : 'amount-expense' ?>"><?= $fmt($fin['balance']) ?></div></div>
</div>
</div></div>
</div>
<div class="col-lg-6">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h6 mb-0">Inwestycje</h2>
<a href="<?= site_url('investments') ?>" class="btn btn-sm btn-outline-primary">Szczegóły →</a>
</div>
<div class="row g-2">
<div class="col-4"><div class="text-muted small">Wpłacono netto</div>
<div class="fw-semibold"><?= $fmt($inv['net_invested']) ?></div></div>
<div class="col-4"><div class="text-muted small">Wartość</div>
<div class="fw-semibold"><?= $fmt($inv['value']) ?></div></div>
<div class="col-4"><div class="text-muted small">Zarobek</div>
<div class="fw-semibold <?= $inv['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>"><?= $fmt($inv['profit']) ?></div></div>
</div>
</div></div>
</div>
</div>
<?= $this->endSection() ?>
+7
View File
@@ -0,0 +1,7 @@
<?php
use CodeIgniter\CLI\CLI;
CLI::error('ERROR: ' . $code);
CLI::write($message);
CLI::newLine();
+65
View File
@@ -0,0 +1,65 @@
<?php
use CodeIgniter\CLI\CLI;
// The main Exception
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
CLI::write($message);
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
CLI::newLine();
$last = $exception;
while ($prevException = $last->getPrevious()) {
$last = $prevException;
CLI::write(' Caused by:');
CLI::write(' [' . $prevException::class . ']', 'red');
CLI::write(' ' . $prevException->getMessage());
CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
CLI::newLine();
}
// The backtrace
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
$backtraces = $last->getTrace();
if ($backtraces) {
CLI::write('Backtrace:', 'green');
}
foreach ($backtraces as $i => $error) {
$padFile = ' '; // 4 spaces
$padClass = ' '; // 7 spaces
$c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);
if (isset($error['file'])) {
$filepath = clean_path($error['file']) . ':' . $error['line'];
CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
} else {
CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
}
$function = '';
if (isset($error['class'])) {
$type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
$function .= $padClass . $error['class'] . $type . $error['function'];
} elseif (! isset($error['class']) && isset($error['function'])) {
$function .= $padClass . $error['function'];
}
$args = implode(', ', array_map(static fn ($value): string => match (true) {
is_object($value) => 'Object(' . $value::class . ')',
is_array($value) => $value !== [] ? '[...]' : '[]',
$value === null => 'null', // return the lowercased version
default => var_export($value, true),
}, array_values($error['args'] ?? [])));
$function .= '(' . $args . ')';
CLI::write($function);
CLI::newLine();
}
}
+5
View File
@@ -0,0 +1,5 @@
<?php
// On the CLI, we still want errors in productions
// so just use the exception template.
include __DIR__ . '/error_exception.php';
+194
View File
@@ -0,0 +1,194 @@
:root {
--main-bg-color: #fff;
--main-text-color: #555;
--dark-text-color: #222;
--light-text-color: #c7c7c7;
--brand-primary-color: #DC4814;
--light-bg-color: #ededee;
--dark-bg-color: #404040;
}
body {
height: 100%;
background: var(--main-bg-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
color: var(--main-text-color);
font-weight: 300;
margin: 0;
padding: 0;
}
h1 {
font-weight: lighter;
font-size: 3rem;
color: var(--dark-text-color);
margin: 0;
}
h1.headline {
margin-top: 20%;
font-size: 5rem;
}
.text-center {
text-align: center;
}
p.lead {
font-size: 1.6rem;
}
.container {
max-width: 75rem;
margin: 0 auto;
padding: 1rem;
}
.header {
background: var(--light-bg-color);
color: var(--dark-text-color);
margin-top: 2.17rem;
}
.header .container {
padding: 1rem;
}
.header h1 {
font-size: 2.5rem;
font-weight: 500;
}
.header p {
font-size: 1.2rem;
margin: 0;
line-height: 2.5;
}
.header a {
color: var(--brand-primary-color);
margin-left: 2rem;
display: none;
text-decoration: none;
}
.header:hover a {
display: inline;
}
.environment {
background: var(--brand-primary-color);
color: var(--main-bg-color);
text-align: center;
padding: calc(4px + 0.2083vw);
width: 100%;
top: 0;
position: fixed;
}
.source {
background: #343434;
color: var(--light-text-color);
padding: 0.5em 1em;
border-radius: 5px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 0.85rem;
margin: 0;
overflow-x: scroll;
}
.source span.line {
line-height: 1.4;
}
.source span.line .number {
color: #666;
}
.source .line .highlight {
display: block;
background: var(--dark-text-color);
color: var(--light-text-color);
}
.source span.highlight .number {
color: #fff;
}
.tabs {
list-style: none;
list-style-position: inside;
margin: 0;
padding: 0;
margin-bottom: -1px;
}
.tabs li {
display: inline;
}
.tabs a:link,
.tabs a:visited {
padding: 0 1rem;
line-height: 2.7;
text-decoration: none;
color: var(--dark-text-color);
background: var(--light-bg-color);
border: 1px solid rgba(0,0,0,0.15);
border-bottom: 0;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
display: inline-block;
}
.tabs a:hover {
background: var(--light-bg-color);
border-color: rgba(0,0,0,0.15);
}
.tabs a.active {
background: var(--main-bg-color);
color: var(--main-text-color);
}
.tab-content {
background: var(--main-bg-color);
border: 1px solid rgba(0,0,0,0.15);
}
.content {
padding: 1rem;
}
.hide {
display: none;
}
.alert {
margin-top: 2rem;
display: block;
text-align: center;
line-height: 3.0;
background: #d9edf7;
border: 1px solid #bcdff1;
border-radius: 5px;
color: #31708f;
}
table {
width: 100%;
overflow: hidden;
}
th {
text-align: left;
border-bottom: 1px solid #e7e7e7;
padding-bottom: 0.5rem;
}
td {
padding: 0.2rem 0.5rem 0.2rem 0;
}
tr:hover td {
background: #f1f1f1;
}
td pre {
white-space: pre-wrap;
}
.trace a {
color: inherit;
}
.trace table {
width: auto;
}
.trace tr td:first-child {
min-width: 5em;
font-weight: bold;
}
.trace td {
background: var(--light-bg-color);
padding: 0 1rem;
}
.trace td pre {
margin: 0;
}
.args {
display: none;
}
+116
View File
@@ -0,0 +1,116 @@
var tabLinks = new Array();
var contentDivs = new Array();
function init()
{
// Grab the tab links and content divs from the page
var tabListItems = document.getElementById('tabs').childNodes;
console.log(tabListItems);
for (var i = 0; i < tabListItems.length; i ++)
{
if (tabListItems[i].nodeName == "LI")
{
var tabLink = getFirstChildWithTagName(tabListItems[i], 'A');
var id = getHash(tabLink.getAttribute('href'));
tabLinks[id] = tabLink;
contentDivs[id] = document.getElementById(id);
}
}
// Assign onclick events to the tab links, and
// highlight the first tab
var i = 0;
for (var id in tabLinks)
{
tabLinks[id].onclick = showTab;
tabLinks[id].onfocus = function () {
this.blur()
};
if (i == 0)
{
tabLinks[id].className = 'active';
}
i ++;
}
// Hide all content divs except the first
var i = 0;
for (var id in contentDivs)
{
if (i != 0)
{
console.log(contentDivs[id]);
contentDivs[id].className = 'content hide';
}
i ++;
}
}
function showTab()
{
var selectedId = getHash(this.getAttribute('href'));
// Highlight the selected tab, and dim all others.
// Also show the selected content div, and hide all others.
for (var id in contentDivs)
{
if (id == selectedId)
{
tabLinks[id].className = 'active';
contentDivs[id].className = 'content';
}
else
{
tabLinks[id].className = '';
contentDivs[id].className = 'content hide';
}
}
// Stop the browser following the link
return false;
}
function getFirstChildWithTagName(element, tagName)
{
for (var i = 0; i < element.childNodes.length; i ++)
{
if (element.childNodes[i].nodeName == tagName)
{
return element.childNodes[i];
}
}
}
function getHash(url)
{
var hashPos = url.lastIndexOf('#');
return url.substring(hashPos + 1);
}
function toggle(elem)
{
elem = document.getElementById(elem);
if (elem.style && elem.style['display'])
{
// Only works with the "style" attr
var disp = elem.style['display'];
}
else if (elem.currentStyle)
{
// For MSIE, naturally
var disp = elem.currentStyle['display'];
}
else if (window.getComputedStyle)
{
// For most other browsers
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
}
// Toggle the state of the "display" style
elem.style.display = disp == 'block' ? 'none' : 'block';
return false;
}
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.badRequest') ?></title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>400</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryBadRequest') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.pageNotFound') ?></title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>404</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryCannotFind') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>
+429
View File
@@ -0,0 +1,429 @@
<?php
use CodeIgniter\HTTP\Header;
use CodeIgniter\CodeIgniter;
$errorId = uniqid('error', true);
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title><?= esc($title) ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
<script>
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
</script>
</head>
<body onload="init()">
<!-- Header -->
<div class="header">
<div class="environment">
Displayed at <?= esc(date('H:i:s')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
</div>
<div class="container">
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
<?= nl2br(esc($exception->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
</p>
</div>
</div>
<!-- Source -->
<div class="container">
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
<?php if (is_file($file)) : ?>
<div class="source">
<?= static::highlightFile($file, $line, 15); ?>
</div>
<?php endif; ?>
</div>
<div class="container">
<?php
$last = $exception;
while ($prevException = $last->getPrevious()) {
$last = $prevException;
?>
<pre>
Caused by:
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= nl2br(esc($prevException->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
</pre>
<?php
}
?>
</div>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
<div class="container">
<ul class="tabs" id="tabs">
<li><a href="#backtrace">Backtrace</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
</ul>
<div class="tab-content">
<!-- Backtrace -->
<div class="content" id="backtrace">
<ol class="trace">
<?php foreach ($trace as $index => $row) : ?>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
echo esc($row['function'] . ' ' . clean_path($row['file']));
} else {
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
}
?>
<?php else: ?>
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= esc($row['class'] . $row['type'] . $row['function']) ?>
<?php if (! empty($row['args'])) : ?>
<?php $argsId = $errorId . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
<div class="args" id="<?= esc($argsId, 'attr') ?>">
<table cellspacing="0">
<?php
$params = null;
// Reflection by name is not available for closure function
if (! str_ends_with($row['function'], '}')) {
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php else : ?>
()
<?php endif; ?>
<?php endif; ?>
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= esc($row['function']) ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<!-- Server -->
<div class="content" id="server">
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<h3>$<?= esc($var) ?></h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<!-- Constants -->
<?php $constants = get_defined_constants(true); ?>
<?php if (! empty($constants['user'])) : ?>
<h3>Constants</h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Request -->
<div class="content" id="request">
<?php $request = service('request'); ?>
<table>
<tbody>
<tr>
<td style="width: 10em">Path</td>
<td><?= esc($request->getUri()) ?></td>
</tr>
<tr>
<td>HTTP Method</td>
<td><?= esc($request->getMethod()) ?></td>
</tr>
<tr>
<td>IP Address</td>
<td><?= esc($request->getIPAddress()) ?></td>
</tr>
<tr>
<td style="width: 10em">Is AJAX Request?</td>
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is CLI Request?</td>
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is Secure Request?</td>
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>User Agent</td>
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
</tr>
</tbody>
</table>
<?php $empty = true; ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<?php $empty = false; ?>
<h3>$<?= esc($var) ?></h3>
<table style="width: 100%">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<?php if ($empty) : ?>
<div class="alert">
No $_GET, $_POST, or $_COOKIE Information to show.
</div>
<?php endif; ?>
<?php $headers = $request->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($value->getValueLine(), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Response -->
<?php
$response = service('response');
$response->setStatusCode(http_response_code());
?>
<div class="content" id="response">
<table>
<tr>
<td style="width: 15em">Response Status</td>
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
</tr>
</table>
<?php $headers = $response->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($response->getHeaderLine($name), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Files -->
<div class="content" id="files">
<?php $files = get_included_files(); ?>
<ol>
<?php foreach ($files as $file) :?>
<li><?= esc(clean_path($file)) ?></li>
<?php endforeach ?>
</ol>
</div>
<!-- Memory -->
<div class="content" id="memory">
<table>
<tbody>
<tr>
<td>Memory Usage</td>
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
</tr>
<tr>
<td style="width: 12em">Peak Memory Usage:</td>
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
</tr>
<tr>
<td>Memory Limit:</td>
<td><?= esc(ini_get('memory_limit')) ?></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /tab-content -->
</div> <!-- /container -->
<?php endif; ?>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title><?= lang('Errors.whoops') ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
</head>
<body>
<div class="container text-center">
<h1 class="headline"><?= lang('Errors.whoops') ?></h1>
<p class="lead"><?= lang('Errors.weHitASnag') ?></p>
</div>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<h1 class="h4 mb-3"><?= esc($title) ?></h1>
<div class="card shadow-sm">
<div class="card-body">
<?php $action = $instrument ? site_url('instruments/' . $instrument['id']) : site_url('instruments'); ?>
<?= form_open($action, ['class' => 'row g-3']) ?>
<div class="col-md-6">
<label class="form-label">Nazwa instrumentu</label>
<input type="text" name="name" class="form-control" required maxlength="120"
value="<?= esc(old('name', $instrument['name'] ?? '')) ?>"
placeholder="np. Obligacje, ETF">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="<?= site_url('instruments') ?>" class="btn btn-link">Anuluj</a>
</div>
<?= form_close() ?>
</div>
</div>
<?= $this->endSection() ?>
+49
View File
@@ -0,0 +1,49 @@
<?= $this->extend('layout/main') ?>
<?= $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">
<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">
<thead class="table-light">
<tr>
<th>Instrument</th>
<th class="text-end">Wpłacono netto</th>
<th class="text-end">Wartość</th>
<th class="text-end">Zarobek</th>
<th class="text-end">%</th>
<th class="text-end">Akcje</th>
</tr>
</thead>
<tbody>
<?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' ?>">
<?= $i['has_value'] ? $fmt($i['profit']) : '—' ?>
</td>
<td 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">
<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>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+52
View File
@@ -0,0 +1,52 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<h1 class="h4 mb-3"><?= esc($title) ?></h1>
<div class="card shadow-sm">
<div class="card-body">
<?php $action = $op ? site_url('investments/operations/' . $op['id']) : site_url('investments/operations'); ?>
<?= form_open($action, ['class' => 'row g-3']) ?>
<div class="col-md-4">
<label class="form-label">Instrument</label>
<?php $selIns = old('instrument_id', $op['instrument_id'] ?? ''); ?>
<select name="instrument_id" class="form-select" required>
<option value="">— wybierz —</option>
<?php foreach ($instruments as $i): ?>
<option value="<?= $i['id'] ?>" <?= (string) $selIns === (string) $i['id'] ? 'selected' : '' ?>>
<?= esc($i['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Data</label>
<input type="date" name="date" class="form-control" required
value="<?= esc(old('date', $op['date'] ?? date('Y-m-d'))) ?>">
</div>
<div class="col-md-4">
<label class="form-label">Typ</label>
<?php $t = old('type', $op['type'] ?? 'deposit'); ?>
<select name="type" class="form-select" required>
<option value="deposit" <?= $t === 'deposit' ? 'selected' : '' ?>>Wpłata</option>
<option value="withdraw" <?= $t === 'withdraw' ? 'selected' : '' ?>>Wypłata</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Kwota (zł)</label>
<input type="number" step="0.01" min="0.01" name="amount" class="form-control" required
value="<?= esc(old('amount', $op['amount'] ?? '')) ?>">
</div>
<div class="col-md-8">
<label class="form-label">Opis (opcjonalnie)</label>
<input type="text" name="description" class="form-control" maxlength="255"
value="<?= esc(old('description', $op['description'] ?? '')) ?>">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="<?= site_url('investments/operations') ?>" class="btn btn-link">Anuluj</a>
</div>
<?= form_close() ?>
</div>
</div>
<?= $this->endSection() ?>
+89
View File
@@ -0,0 +1,89 @@
<?= $this->extend('layout/main') ?>
<?= $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">
<h1 class="h4 mb-0">Operacje inwestycyjne</h1>
<a href="<?= site_url('investments/operations/new') ?>" class="btn btn-primary">+ Nowa operacja</a>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<?= form_open('investments/operations', ['method' => 'get', 'class' => 'row g-2 align-items-end']) ?>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Od</label>
<input type="date" name="from" class="form-control" value="<?= esc($filter['from'] ?? '') ?>">
</div>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Do</label>
<input type="date" name="to" class="form-control" value="<?= esc($filter['to'] ?? '') ?>">
</div>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Instrument</label>
<select name="instrument_id" class="form-select">
<option value="">— wszystkie —</option>
<?php foreach ($instruments as $i): ?>
<option value="<?= $i['id'] ?>" <?= (string) ($filter['instrument_id'] ?? '') === (string) $i['id'] ? 'selected' : '' ?>>
<?= esc($i['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">Typ</label>
<select name="type" class="form-select">
<option value="">— oba —</option>
<option value="deposit" <?= ($filter['type'] ?? '') === 'deposit' ? 'selected' : '' ?>>Wpłata</option>
<option value="withdraw" <?= ($filter['type'] ?? '') === 'withdraw' ? 'selected' : '' ?>>Wypłata</option>
</select>
</div>
<div class="col-md-1">
<button class="btn btn-outline-primary w-100">Filtruj</button>
</div>
<?= form_close() ?>
</div>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Data</th>
<th>Instrument</th>
<th>Typ</th>
<th>Opis</th>
<th class="text-end">Kwota</th>
<th class="text-end">Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($operations)): ?>
<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>
<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' ?>">
<?= $isDep ? '+' : '' ?><?= $fmt($o['amount']) ?>
</td>
<td class="text-end">
<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>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+88
View File
@@ -0,0 +1,88 @@
<?= $this->extend('layout/main') ?>
<?= $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 flex-wrap gap-2">
<h1 class="h4 mb-0">Pulpit inwestycji</h1>
<div class="btn-group">
<a href="<?= site_url('investments/operations/new') ?>" class="btn btn-sm btn-outline-primary">+ Wpłata / wypłata</a>
<a href="<?= site_url('investments/valuations/new') ?>" class="btn btn-sm btn-outline-primary">+ Wycena</a>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Wpłacono netto</div>
<div class="value"><?= $fmt($net_invested) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Bieżąca wartość</div>
<div class="value"><?= $fmt($value) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Zarobek</div>
<div class="value <?= $profit >= 0 ? 'amount-income' : 'amount-expense' ?>"><?= $fmt($profit) ?></div>
</div></div></div>
</div>
<div class="card shadow-sm mb-4"><div class="card-body">
<h2 class="h6">Portfel w czasie</h2>
<div id="chartPortfolio"></div>
</div></div>
<div class="card shadow-sm"><div class="card-body">
<h2 class="h6">Rozbicie na instrumenty</h2>
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Instrument</th>
<th class="text-end">Wpłacono netto</th>
<th class="text-end">Wartość</th>
<th class="text-end">Zarobek</th>
<th class="text-end">% zwrotu</th>
</tr>
</thead>
<tbody>
<?php if (empty($instruments)): ?>
<tr><td colspan="5" 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' ?>">
<?= $i['has_value'] ? $fmt($i['profit']) : '—' ?>
</td>
<td class="text-end <?= $i['profit'] >= 0 ? 'amount-income' : 'amount-expense' ?>">
<?= $i['percent'] !== null && $i['has_value'] ? number_format($i['percent'], 2, ',', ' ') . '%' : '—' ?>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div></div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
const timeline = <?= json_encode($timeline, JSON_UNESCAPED_UNICODE) ?>;
new ApexCharts(document.querySelector('#chartPortfolio'), {
chart: { type: 'line', height: 340 },
series: [
{ name: 'Wpłacono netto', data: timeline.map(r => Number(r.net)) },
{ name: 'Wartość', data: timeline.map(r => Number(r.value)) },
{ name: 'Zarobek', data: timeline.map(r => Number(r.profit)) }
],
xaxis: { categories: timeline.map(r => r.date), type: 'category' },
colors: ['#455a64', '#1565c0', '#1a7f37'],
stroke: { curve: 'smooth', width: 2 },
markers: { size: 4 },
dataLabels: { enabled: false },
noData: { text: 'Brak wycen — dodaj wpłaty i wycenę, aby zobaczyć wykres' }
}).render();
</script>
<?= $this->endSection() ?>
+66
View File
@@ -0,0 +1,66 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= esc($title ?? 'finansePRO') ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background:#f5f6f8; }
.stat-card .value { font-size:1.6rem; font-weight:600; }
.amount-income { color:#1a7f37; }
.amount-expense { color:#c62828; }
</style>
</head>
<body>
<?php if (session()->get('logged_in')): ?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container">
<a class="navbar-brand fw-bold" href="<?= site_url('dashboard') ?>">finansePRO</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link" href="<?= site_url('dashboard') ?>">Pulpit</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navFinance" role="button" data-bs-toggle="dropdown">Finanse bieżące</a>
<ul class="dropdown-menu" aria-labelledby="navFinance">
<li><a class="dropdown-item" href="<?= site_url('finances') ?>">Pulpit finansów</a></li>
<li><a class="dropdown-item" href="<?= site_url('operations') ?>">Operacje</a></li>
<li><a class="dropdown-item" href="<?= site_url('categories') ?>">Kategorie</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navInvest" role="button" data-bs-toggle="dropdown">Inwestycje</a>
<ul class="dropdown-menu" aria-labelledby="navInvest">
<li><a class="dropdown-item" href="<?= site_url('investments') ?>">Pulpit inwestycji</a></li>
<li><a class="dropdown-item" href="<?= site_url('investments/operations') ?>">Wpłaty / wypłaty</a></li>
<li><a class="dropdown-item" href="<?= site_url('investments/valuations') ?>">Wyceny</a></li>
<li><a class="dropdown-item" href="<?= site_url('instruments') ?>">Instrumenty</a></li>
</ul>
</li>
</ul>
<span class="navbar-text text-light me-3 small"><?= esc(session()->get('user_email')) ?></span>
<a class="btn btn-outline-light btn-sm" href="<?= site_url('logout') ?>">Wyloguj</a>
</div>
</div>
</nav>
<?php endif; ?>
<main class="container pb-5">
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?= $this->renderSection('content') ?>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts@3.54.1/dist/apexcharts.min.js"></script>
<?= $this->renderSection('scripts') ?>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<h1 class="h4 mb-3"><?= esc($title) ?></h1>
<div class="card shadow-sm">
<div class="card-body">
<?php $action = $op ? site_url('operations/' . $op['id']) : site_url('operations'); ?>
<?= form_open($action, ['class' => 'row g-3']) ?>
<div class="col-md-4">
<label class="form-label">Data</label>
<input type="date" name="date" class="form-control" required
value="<?= esc(old('date', $op['date'] ?? date('Y-m-d'))) ?>">
</div>
<div class="col-md-4">
<label class="form-label">Kwota (zł)</label>
<input type="number" step="0.01" min="0.01" name="amount" class="form-control" required
value="<?= esc(old('amount', $op['amount'] ?? '')) ?>">
</div>
<div class="col-md-4">
<label class="form-label">Typ</label>
<?php $t = old('type', $op['type'] ?? 'expense'); ?>
<select name="type" id="type" class="form-select" required>
<option value="income" <?= $t === 'income' ? 'selected' : '' ?>>Przychód</option>
<option value="expense" <?= $t === 'expense' ? 'selected' : '' ?>>Wydatek</option>
</select>
<div class="form-text">Wybór kategorii ustawia typ automatycznie.</div>
</div>
<div class="col-md-6">
<label class="form-label">Kategoria</label>
<?php $selCat = old('category_id', $op['category_id'] ?? ''); ?>
<select name="category_id" id="category_id" class="form-select">
<option value="" data-type="">— bez kategorii —</option>
<?php foreach ($categories as $c): ?>
<option value="<?= $c['id'] ?>" data-type="<?= $c['type'] ?>"
<?= (string) $selCat === (string) $c['id'] ? 'selected' : '' ?>>
<?= esc($c['indent_label']) ?> (<?= $c['type'] === 'income' ? 'przychód' : 'wydatek' ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Opis (opcjonalnie)</label>
<input type="text" name="description" class="form-control" maxlength="255"
value="<?= esc(old('description', $op['description'] ?? '')) ?>">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="<?= site_url('operations') ?>" class="btn btn-link">Anuluj</a>
</div>
<?= form_close() ?>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// Kategoria narzuca typ operacji (spojnosc ze zrodlem prawdy po stronie serwera).
const cat = document.getElementById('category_id');
const type = document.getElementById('type');
function syncType() {
const opt = cat.options[cat.selectedIndex];
const t = opt.getAttribute('data-type');
if (t) { type.value = t; type.setAttribute('disabled', 'disabled'); }
else { type.removeAttribute('disabled'); }
}
cat.addEventListener('change', syncType);
syncType();
// disabled select nie wysyla wartosci -> odblokuj przy submit (serwer i tak wymusi typ z kategorii)
type.form.addEventListener('submit', () => type.removeAttribute('disabled'));
</script>
<?= $this->endSection() ?>
+107
View File
@@ -0,0 +1,107 @@
<?= $this->extend('layout/main') ?>
<?= $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">
<h1 class="h4 mb-0">Operacje</h1>
<a href="<?= site_url('operations/new') ?>" class="btn btn-primary">+ Nowa operacja</a>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<?= form_open('operations', ['method' => 'get', 'class' => 'row g-2 align-items-end']) ?>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Od</label>
<input type="date" name="from" class="form-control" value="<?= esc($filter['from'] ?? '') ?>">
</div>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Do</label>
<input type="date" name="to" class="form-control" value="<?= esc($filter['to'] ?? '') ?>">
</div>
<div class="col-6 col-md-3">
<label class="form-label small mb-1">Kategoria</label>
<select name="category_id" class="form-select">
<option value="">— wszystkie —</option>
<?php foreach ($categories as $c): ?>
<option value="<?= $c['id'] ?>" <?= (string) ($filter['category_id'] ?? '') === (string) $c['id'] ? 'selected' : '' ?>>
<?= esc($c['indent_label']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">Typ</label>
<select name="type" class="form-select">
<option value="">— oba —</option>
<option value="income" <?= ($filter['type'] ?? '') === 'income' ? 'selected' : '' ?>>Przychód</option>
<option value="expense" <?= ($filter['type'] ?? '') === 'expense' ? 'selected' : '' ?>>Wydatek</option>
</select>
</div>
<div class="col-md-1">
<button class="btn btn-outline-primary w-100">Filtruj</button>
</div>
<?= form_close() ?>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Przychody</div>
<div class="value amount-income"><?= $fmt($totals['income']) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Wydatki</div>
<div class="value amount-expense"><?= $fmt($totals['expense']) ?></div>
</div></div></div>
<div class="col-md-4"><div class="card stat-card shadow-sm"><div class="card-body">
<div class="text-muted small">Saldo</div>
<div class="value <?= $totals['balance'] >= 0 ? 'amount-income' : 'amount-expense' ?>"><?= $fmt($totals['balance']) ?></div>
</div></div></div>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Data</th>
<th>Kategoria</th>
<th>Opis</th>
<th class="text-end">Kwota</th>
<th class="text-end">Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($operations)): ?>
<tr><td colspan="5" class="text-center text-muted py-4">Brak operacji dla wybranych filtrów.</td></tr>
<?php else: foreach ($operations as $o): ?>
<tr>
<td><?= esc($o['date']) ?></td>
<td>
<?php
if (! empty($o['category_name'])) {
echo esc(! empty($o['parent_name'])
? $o['parent_name'] . ' → ' . $o['category_name']
: $o['category_name']);
} else {
echo '(bez kategorii)';
}
?>
</td>
<td class="text-muted"><?= esc($o['description'] ?? '') ?></td>
<td class="text-end <?= $o['type'] === 'income' ? 'amount-income' : 'amount-expense' ?>">
<?= $o['type'] === 'income' ? '+' : '' ?><?= $fmt($o['amount']) ?>
</td>
<td class="text-end">
<a href="<?= site_url('operations/' . $o['id'] . '/edit') ?>" class="btn btn-sm btn-outline-secondary">Edytuj</a>
<a href="<?= site_url('operations/' . $o['id'] . '/delete') ?>" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Usunąć operację?')">Usuń</a>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+39
View File
@@ -0,0 +1,39 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<h1 class="h4 mb-3"><?= esc($title) ?></h1>
<div class="card shadow-sm">
<div class="card-body">
<?php $action = $val ? site_url('investments/valuations/' . $val['id']) : site_url('investments/valuations'); ?>
<?= form_open($action, ['class' => 'row g-3']) ?>
<div class="col-md-5">
<label class="form-label">Instrument</label>
<?php $selIns = old('instrument_id', $val['instrument_id'] ?? ''); ?>
<select name="instrument_id" class="form-select" required>
<option value="">— wybierz —</option>
<?php foreach ($instruments as $i): ?>
<option value="<?= $i['id'] ?>" <?= (string) $selIns === (string) $i['id'] ? 'selected' : '' ?>>
<?= esc($i['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Data</label>
<input type="date" name="date" class="form-control" required
value="<?= esc(old('date', $val['date'] ?? date('Y-m-d'))) ?>">
</div>
<div class="col-md-3">
<label class="form-label">Wartość (zł)</label>
<input type="number" step="0.01" min="0" name="value" class="form-control" required
value="<?= esc(old('value', $val['value'] ?? '')) ?>">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="<?= site_url('investments/valuations') ?>" class="btn btn-link">Anuluj</a>
</div>
<?= form_close() ?>
</div>
</div>
<?= $this->endSection() ?>
+62
View File
@@ -0,0 +1,62 @@
<?= $this->extend('layout/main') ?>
<?= $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">
<h1 class="h4 mb-0">Wyceny</h1>
<a href="<?= site_url('investments/valuations/new') ?>" class="btn btn-primary">+ Nowa wycena</a>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<?= form_open('investments/valuations', ['method' => 'get', 'class' => 'row g-2 align-items-end']) ?>
<div class="col-8 col-md-4">
<label class="form-label small mb-1">Instrument</label>
<select name="instrument_id" class="form-select">
<option value="">— wszystkie —</option>
<?php foreach ($instruments as $i): ?>
<option value="<?= $i['id'] ?>" <?= (string) ($filter['instrument_id'] ?? '') === (string) $i['id'] ? 'selected' : '' ?>>
<?= esc($i['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4 col-md-2">
<button class="btn btn-outline-primary w-100">Filtruj</button>
</div>
<?= form_close() ?>
</div>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Data</th>
<th>Instrument</th>
<th class="text-end">Wartość</th>
<th class="text-end">Akcje</th>
</tr>
</thead>
<tbody>
<?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">
<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>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
File diff suppressed because one or more lines are too long