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.
This commit is contained in:
Ty Clifford
2026-06-15 10:33:55 -04:00
parent b7eaa81501
commit 51ee4ecb5a
18 changed files with 1568 additions and 559 deletions
+14 -9
View File
@@ -1,8 +1,9 @@
<section class="dead-hero card glow-blue">
<section class="dead-hero card">
<div>
<span class="metric-icon purple"><?= icon('vault') ?></span>
<p class="eyebrow">Dormant and disregarded</p>
<h2>Dead account balance</h2>
<p>Money here is excluded from usable monthly funds until you explicitly restore it.</p>
<h2>Your out-of-use reserve</h2>
<p>Money here is subtracted from spendable funds, remains fully traceable, and can later be restored to monthly income or earmarked for a target.</p>
</div>
<?php if ($hideBalance): ?>
<button class="concealed-balance" type="button" data-concealed-balance data-value="<?= h(Support::money($balance, $symbol)) ?>">
@@ -16,10 +17,14 @@
<section class="content-grid dead-grid">
<article class="card">
<div class="card-heading"><div><p class="eyebrow">Manual movement</p><h2>Deposit or restore</h2></div></div>
<form method="post" class="stack-form">
<form method="post" class="stack-form" data-dead-transfer-form>
<?= csrf_field() ?><input type="hidden" name="action" value="dead.move"><input type="hidden" name="month" value="<?= h($month) ?>">
<label>Direction<select name="direction"><option value="deposit">Move into dormant account</option><option value="restore">Restore to this months usable budget</option></select></label>
<label>Amount<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="amount" min="0.01" step="0.01" required></div></label>
<div class="form-grid" data-restore-fields hidden>
<label>Restore to<select name="destination_type" data-restore-type><option value="income">Monthly income</option><option value="item">Specific budget target</option></select></label>
<label data-restore-item hidden>Budget target<select name="destination_id"><option value="">Choose a target</option><?php foreach ($items as $item): ?><option value="<?= (int) $item['id'] ?>"><?= h($item['name']) ?> · <?= h(Support::money($item['remaining_amount'], $symbol)) ?> left</option><?php endforeach; ?></select></label>
</div>
<label>Reason<textarea name="notes" rows="3" placeholder="Emergency reserve, return to groceries…"></textarea></label>
<button class="button primary" type="submit">Record transfer</button>
</form>
@@ -27,9 +32,9 @@
<article class="card">
<div class="card-heading"><div><p class="eyebrow">Automatic rules</p><h2>How funds arrive here</h2></div><a class="text-link" href="<?= h(url('settings')) ?>">Configure →</a></div>
<div class="rule-list">
<div><span class="metric-icon blue"></span><p><strong>Income withholding</strong><small><?= h((string) $settings->get('dead.income_value', 0)) ?><?= $settings->get('dead.income_mode') === 'percent' ? '%' : ' fixed' ?> from each income source</small></p></div>
<div><span class="metric-icon pink"></span><p><strong>Transaction pull</strong><small><?= h((string) $settings->get('dead.transaction_value', 0)) ?><?= $settings->get('dead.transaction_mode') === 'percent' ? '%' : ' fixed' ?> whenever an expense is recorded</small></p></div>
<div><span class="metric-icon orange"></span><p><strong>Restorations</strong><small>Return to the selected month without changing historical expense records</small></p></div>
<div><span class="metric-icon blue"><?= icon('wallet') ?></span><p><strong>Income withholding</strong><small><?= h((string) $settings->get('dead.income_value', 0)) ?><?= $settings->get('dead.income_mode') === 'percent' ? '%' : ' fixed' ?> from each income source</small></p></div>
<div><span class="metric-icon pink"><?= icon('expense') ?></span><p><strong>Transaction pull</strong><small><?= h((string) $settings->get('dead.transaction_value', 0)) ?><?= $settings->get('dead.transaction_mode') === 'percent' ? '%' : ' fixed' ?> whenever an expense is recorded</small></p></div>
<div><span class="metric-icon orange"><?= icon('data') ?></span><p><strong>Restorations</strong><small>Return to monthly income or identify a specific target without rewriting history</small></p></div>
</div>
</article>
</section>
@@ -37,7 +42,7 @@
<article class="card">
<div class="card-heading">
<div><p class="eyebrow">Permanent paper trail</p><h2>Dormant ledger</h2></div>
<label class="search-box"><span>⌕</span><input type="search" placeholder="Search movements…" data-table-search="dead-table"></label>
<label class="search-box"><?= icon('search') ?><input type="search" placeholder="Search movements…" data-table-search="dead-table"></label>
</div>
<?php if ($entries === []): ?>
<div class="empty-state"><strong>The account is empty</strong><p>Automatic or manual transfers will appear here.</p></div>
@@ -51,7 +56,7 @@
<td><?= h(date('M j, Y g:i A', strtotime($entry['created_at']))) ?></td>
<td><a class="text-link" href="<?= h(url('dead-account', ['month' => $entry['month']])) ?>"><?= h($entry['month']) ?></a></td>
<td><strong><?= h(ucwords(str_replace('_', ' ', $entry['kind']))) ?></strong><?php if ($entry['notes']): ?><small><?= h($entry['notes']) ?></small><?php endif; ?></td>
<td><?= h(ucfirst((string) ($entry['source_type'] ?: 'manual'))) ?></td>
<td><?php if ($entry['source_type'] === 'restore_item'): ?>Target: <?= h($entry['destination_name'] ?: 'Archived target') ?><?php elseif ($entry['source_type'] === 'restore_income'): ?>Monthly income<?php else: ?><?= h(ucwords(str_replace('_', ' ', (string) ($entry['source_type'] ?: 'manual')))) ?><?php endif; ?></td>
<td class="<?= (float) $entry['amount'] >= 0 ? 'green' : 'orange' ?>"><strong><?= (float) $entry['amount'] >= 0 ? '+' : '' ?><?= h(Support::money($entry['amount'], $symbol)) ?></strong></td>
</tr>
<?php endforeach; ?>