51ee4ecb5a
Finished the planner end to end. Corrected rollover and yearly accounting, recurring targets, savings rules, and targeted dormant restorations. Added editing/deletion for income, expenses, targets, and categories. Rebuilt dashboard, analytics, calendar, settings, exports, authentication, and responsive mobile UI. Added original artwork at [financial-flow.png](/Users/tyemeclifford/Documents/GH/budget/public/assets/images/financial-flow.png), generated with built-in Imagegen using an abstract financial-momentum landscape prompt. Added [self-test.php](/Users/tyemeclifford/Documents/GH/budget/scripts/self-test.php). Verification passed: automated accounting/export tests, full browser workflow, responsive 390px layout, light/dark themes, and no console errors. Tested with FrankenPHP v1.12.4. Live Mailgun delivery was not attempted without credentials.
67 lines
6.3 KiB
PHP
67 lines
6.3 KiB
PHP
<section class="page-intro">
|
||
<div><p class="eyebrow">Searchable expense ledger</p><h2>Every payment, one trail</h2><p>Link a payment to a target and its owing balance moves immediately.</p></div>
|
||
<button class="button primary" type="button" data-dialog-open="expense-dialog"><?= icon('plus') ?> Record expense</button>
|
||
</section>
|
||
|
||
<section class="summary-grid">
|
||
<article class="summary-card card"><span class="metric-icon blue"><?= icon('wallet') ?></span><div><small>Available</small><strong><?= h(Support::money($summary['gross_resources'], $symbol)) ?></strong></div></article>
|
||
<article class="summary-card card"><span class="metric-icon pink"><?= icon('expense') ?></span><div><small>Spent</small><strong><?= h(Support::money($summary['expenses'], $symbol)) ?></strong></div></article>
|
||
<article class="summary-card card"><span class="metric-icon purple"><?= icon('vault') ?></span><div><small>Set aside</small><strong><?= h(Support::money($summary['dead_deposits'], $symbol)) ?></strong></div></article>
|
||
<article class="summary-card card"><span class="metric-icon green"><?= icon('chart') ?></span><div><small>Remaining</small><strong class="<?= $summary['remaining'] < 0 ? 'red' : '' ?>"><?= h(Support::money($summary['remaining'], $symbol)) ?></strong></div></article>
|
||
</section>
|
||
|
||
<article class="card panel">
|
||
<div class="card-heading">
|
||
<div><p class="eyebrow"><?= h(date('F Y', strtotime($month . '-01'))) ?></p><h2>Transaction history</h2><p class="heading-note"><?= count($transactions) ?> recorded expense<?= count($transactions) === 1 ? '' : 's' ?></p></div>
|
||
<label class="search-box"><?= icon('search') ?><input type="search" placeholder="Search description, item, category…" data-table-search="expense-table"></label>
|
||
</div>
|
||
<?php if ($transactions === []): ?>
|
||
<div class="empty-state"><span class="empty-icon"><?= icon('expense') ?></span><strong>No expenses recorded</strong><p>Record a payment to start progressing this month’s targets.</p><button class="button small" type="button" data-dialog-open="expense-dialog">Record the first expense</button></div>
|
||
<?php else: ?>
|
||
<div class="table-wrap">
|
||
<table id="expense-table" class="data-table">
|
||
<thead><tr><th>Date</th><th>Description</th><th>Category / target</th><th>Amount</th><th>Dormant pull</th><th><span class="sr-only">Actions</span></th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ($transactions as $transaction): ?>
|
||
<tr>
|
||
<td data-label="Date"><span class="table-date"><?= h(date('M j', strtotime($transaction['transacted_on']))) ?><small><?= h(date('Y', strtotime($transaction['transacted_on']))) ?></small></span></td>
|
||
<td data-label="Description"><strong><?= h($transaction['merchant']) ?></strong><?php if ($transaction['notes']): ?><small><?= h($transaction['notes']) ?></small><?php endif; ?></td>
|
||
<td data-label="Category / target"><span class="pill"><i style="--dot:<?= h($transaction['category_color'] ?: '#718096') ?>"></i><?= h($transaction['category_name'] ?: 'Uncategorized') ?></span><?php if ($transaction['item_name']): ?><small><?= h($transaction['item_name']) ?></small><?php endif; ?></td>
|
||
<td data-label="Amount"><strong class="amount-negative">-<?= h(Support::money($transaction['amount'], $symbol)) ?></strong></td>
|
||
<td data-label="Dormant pull"><?= h(Support::money($transaction['dead_pull_amount'], $symbol)) ?></td>
|
||
<td class="table-actions">
|
||
<button class="icon-button" type="button" data-dialog-open="expense-edit-<?= (int) $transaction['id'] ?>" aria-label="Edit <?= h($transaction['merchant']) ?>"><?= icon('edit') ?></button>
|
||
<form method="post" data-confirm="Delete this expense and reverse its dormant transfer?">
|
||
<?= csrf_field() ?><input type="hidden" name="action" value="transaction.delete"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $transaction['id'] ?>">
|
||
<button class="icon-button danger" type="submit" aria-label="Delete <?= h($transaction['merchant']) ?>"><?= icon('trash') ?></button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
</article>
|
||
|
||
<dialog id="expense-dialog">
|
||
<form method="post" class="dialog-card">
|
||
<?= csrf_field() ?><input type="hidden" name="action" value="transaction.add"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Progress a target</p><h2>Record expense</h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||
<?php require APP_ROOT . '/views/partials/transaction-fields.php'; ?>
|
||
<p class="form-note">Your configured per-transaction dormant rule is applied automatically.</p>
|
||
<div class="dialog-actions"><button class="button" type="button" data-dialog-close>Cancel</button><button class="button primary" type="submit">Record expense</button></div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<?php foreach ($transactions as $transaction): ?>
|
||
<dialog id="expense-edit-<?= (int) $transaction['id'] ?>">
|
||
<form method="post" class="dialog-card">
|
||
<?= csrf_field() ?><input type="hidden" name="action" value="transaction.update"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $transaction['id'] ?>">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Maintain transaction</p><h2>Edit <?= h($transaction['merchant']) ?></h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||
<?php $editingTransaction = $transaction; require APP_ROOT . '/views/partials/transaction-fields.php'; unset($editingTransaction); ?>
|
||
<div class="dialog-actions"><button class="button" type="button" data-dialog-close>Cancel</button><button class="button primary" type="submit">Save expense</button></div>
|
||
</form>
|
||
</dialog>
|
||
<?php endforeach; ?>
|