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.
68 lines
5.4 KiB
PHP
68 lines
5.4 KiB
PHP
<section class="dead-hero card">
|
||
<div>
|
||
<span class="metric-icon purple"><?= icon('vault') ?></span>
|
||
<p class="eyebrow">Dormant and disregarded</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)) ?>">
|
||
<span class="masked">••••••</span><span class="revealed"></span><small>Hover, then click to confirm</small>
|
||
</button>
|
||
<?php else: ?>
|
||
<div class="visible-balance"><?= h(Support::money($balance, $symbol)) ?></div>
|
||
<?php endif; ?>
|
||
</section>
|
||
|
||
<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" 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 month’s 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>
|
||
</article>
|
||
<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"><?= 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>
|
||
|
||
<article class="card">
|
||
<div class="card-heading">
|
||
<div><p class="eyebrow">Permanent paper trail</p><h2>Dormant ledger</h2></div>
|
||
<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>
|
||
<?php else: ?>
|
||
<div class="table-wrap">
|
||
<table id="dead-table">
|
||
<thead><tr><th>Date</th><th>Budget month</th><th>Movement</th><th>Source</th><th>Amount</th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ($entries as $entry): ?>
|
||
<tr>
|
||
<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><?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; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
</article>
|