-
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:
+222
-92
@@ -1,144 +1,274 @@
|
||||
<?php
|
||||
$remainingTone = $summary['remaining'] >= 0 ? 'green' : 'red';
|
||||
$progress = $summary['gross_resources'] > 0 ? min(100, max(0, $summary['expenses'] / $summary['gross_resources'] * 100)) : 0;
|
||||
$monthLabel = date('F Y', strtotime($month . '-01'));
|
||||
$daysInMonth = (int) date('t', strtotime($month . '-01'));
|
||||
$currentMonth = date('Y-m');
|
||||
$elapsedDays = $month < $currentMonth ? $daysInMonth : ($month > $currentMonth ? 0 : min($daysInMonth, (int) date('j')));
|
||||
$forecast = $elapsedDays > 0 ? $summary['expenses'] / $elapsedDays * $daysInMonth : 0;
|
||||
$spentPercent = $summary['gross_resources'] > 0 ? min(100, max(0, $summary['expenses'] / $summary['gross_resources'] * 100)) : 0;
|
||||
$remainingPercent = $summary['gross_resources'] > 0 ? min(100, max(0, $summary['remaining'] / $summary['gross_resources'] * 100)) : 0;
|
||||
$remainingDelta = $summary['remaining'] - $previousSummary['remaining'];
|
||||
$incomeRule = (string) $settings->get('dead.income_mode', 'percent');
|
||||
$incomeRuleValue = (float) $settings->get('dead.income_value', 0);
|
||||
$transactionRule = (string) $settings->get('dead.transaction_mode', 'fixed');
|
||||
$transactionRuleValue = (float) $settings->get('dead.transaction_value', 0);
|
||||
?>
|
||||
<section class="hero-grid">
|
||||
<article class="hero-balance card glow-purple">
|
||||
<div>
|
||||
<p class="eyebrow"><?= h(date('F Y', strtotime($month . '-01'))) ?> remaining</p>
|
||||
<div class="hero-amount <?= h($remainingTone) ?>"><?= h(Support::money($summary['remaining'], $symbol)) ?></div>
|
||||
<p class="muted"><?= number_format($progress, 1) ?>% of available resources spent</p>
|
||||
<section class="overview-hero card">
|
||||
<div class="hero-art" aria-hidden="true"></div>
|
||||
<div class="hero-copy">
|
||||
<div class="status-line">
|
||||
<span class="live-dot"></span>
|
||||
<span><?= h($monthLabel) ?> plan</span>
|
||||
<span class="status-divider"></span>
|
||||
<span><?= $plan['rolling_enabled'] ? 'Rolling budget on' : 'Manual rollover' ?></span>
|
||||
</div>
|
||||
<div class="balance-ring" style="--progress:<?= h((string) $progress) ?>">
|
||||
<span><?= number_format(100 - $progress, 0) ?>%</span><small>left</small>
|
||||
<p class="hero-label">Spendable balance</p>
|
||||
<div class="hero-value <?= $summary['remaining'] >= 0 ? '' : 'negative' ?>"><?= h(Support::money($summary['remaining'], $symbol)) ?></div>
|
||||
<p class="hero-summary">
|
||||
<?= h(Support::money($summary['gross_resources'], $symbol)) ?> available
|
||||
<span>·</span>
|
||||
<?= number_format($remainingPercent, 0) ?>% remains
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="button primary" href="<?= h(url('transactions', ['month' => $month, 'open' => 'expense-dialog'])) ?>"><?= icon('plus') ?> Record expense</a>
|
||||
<button class="button glass" type="button" data-dialog-open="plan-dialog"><?= icon('wallet') ?> Edit monthly plan</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-gauge">
|
||||
<div class="gauge-ring" style="--progress:<?= h((string) $remainingPercent) ?>">
|
||||
<span><?= number_format($remainingPercent, 0) ?>%</span>
|
||||
<small>remaining</small>
|
||||
</div>
|
||||
<div class="gauge-note">
|
||||
<span>Month forecast</span>
|
||||
<strong><?= h(Support::money($forecast, $symbol)) ?></strong>
|
||||
<small><?= $elapsedDays === 0 ? 'Begins when spending is recorded' : 'At the current daily pace' ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="metric-grid">
|
||||
<article class="metric-card card">
|
||||
<div class="metric-top"><span class="metric-icon green"><?= icon('wallet') ?></span><span class="trend-tag">Fresh funds</span></div>
|
||||
<p>Income this month</p>
|
||||
<strong><?= h(Support::money($summary['fresh_income'], $symbol)) ?></strong>
|
||||
<small><?= h(Support::money($summary['starting_income'], $symbol)) ?> starting + <?= h(Support::money($summary['additional_income'], $symbol)) ?> added</small>
|
||||
</article>
|
||||
<article class="metric-card card">
|
||||
<div class="metric-icon blue">↙</div><p>Gross resources</p><strong><?= h(Support::money($summary['gross_resources'], $symbol)) ?></strong>
|
||||
<small><?= h(Support::money($summary['starting_income'], $symbol)) ?> start + <?= h(Support::money($summary['rollover_in'], $symbol)) ?> rollover</small>
|
||||
<div class="metric-top"><span class="metric-icon pink"><?= icon('expense') ?></span><span class="trend-tag <?= $spentPercent > 80 ? 'warning' : '' ?>"><?= number_format($spentPercent, 0) ?>% used</span></div>
|
||||
<p>Total spent</p>
|
||||
<strong><?= h(Support::money($summary['expenses'], $symbol)) ?></strong>
|
||||
<small><?= count($items) ?> target<?= count($items) === 1 ? '' : 's' ?> · <?= h(Support::money($summary['planned'], $symbol)) ?> planned</small>
|
||||
</article>
|
||||
<article class="metric-card card">
|
||||
<div class="metric-icon pink">↗</div><p>Expenses</p><strong><?= h(Support::money($summary['expenses'], $symbol)) ?></strong>
|
||||
<small><?= count($items) ?> defined budget item<?= count($items) === 1 ? '' : 's' ?></small>
|
||||
<div class="metric-top"><span class="metric-icon purple"><?= icon('vault') ?></span><span class="trend-tag"><?= number_format($summary['savings_rate'], 1) ?>% rate</span></div>
|
||||
<p>Moved dormant</p>
|
||||
<strong><?= h(Support::money($summary['dead_deposits'], $symbol)) ?></strong>
|
||||
<small><?= h(Support::money($summary['dead_restorations'], $symbol)) ?> restored this month</small>
|
||||
</article>
|
||||
<article class="metric-card card">
|
||||
<div class="metric-icon orange">◇</div><p>Dormant balance</p>
|
||||
<strong><?= h(Support::money($deadBalance, $symbol)) ?></strong>
|
||||
<small><?= number_format($summary['savings_rate'], 1) ?>% of this month routed away</small>
|
||||
<div class="metric-top"><span class="metric-icon blue"><?= icon('data') ?></span><span class="trend-tag <?= $remainingDelta < 0 ? 'negative' : '' ?>"><?= $remainingDelta >= 0 ? '+' : '' ?><?= h(Support::money($remainingDelta, $symbol)) ?></span></div>
|
||||
<p>Previous remainder</p>
|
||||
<strong><?= h(Support::money($summary['rollover_in'], $symbol)) ?></strong>
|
||||
<small>Compared with <?= h(date('F', strtotime($month . '-01 -1 month'))) ?>’s closing balance</small>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="content-grid dashboard-grid">
|
||||
<div class="span-2 stack">
|
||||
<article class="card">
|
||||
<section class="dashboard-layout">
|
||||
<div class="dashboard-main stack">
|
||||
<article class="card panel">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Progressional tracking</p><h2>Budget targets</h2></div>
|
||||
<button class="button small" type="button" data-dialog-open="item-dialog">+ Add item</button>
|
||||
<div><p class="eyebrow">Progressional plan</p><h2>Budget targets</h2><p class="heading-note">Payments reduce the amount still owing in real time.</p></div>
|
||||
<button class="button small primary" type="button" data-dialog-open="item-dialog"><?= icon('plus') ?> New target</button>
|
||||
</div>
|
||||
<?php if ($items === []): ?>
|
||||
<div class="empty-state"><strong>No targets yet</strong><p>Add a bill or category target to begin progress tracking.</p></div>
|
||||
<div class="empty-state"><span class="empty-icon"><?= icon('chart') ?></span><strong>Build your first target</strong><p>Add rent, internet, groceries, or any amount you want to track.</p><button class="button small" type="button" data-dialog-open="item-dialog">Add a budget target</button></div>
|
||||
<?php else: ?>
|
||||
<div class="budget-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div class="budget-row">
|
||||
<span class="category-dot" style="--dot:<?= h($item['category_color'] ?: '#718096') ?>"></span>
|
||||
<div class="budget-main">
|
||||
<div class="budget-title">
|
||||
<strong><?= h($item['name']) ?></strong>
|
||||
<span><?= h($item['category_name'] ?: 'Uncategorized') ?><?= $item['due_date'] ? ' · due ' . h(date('M j', strtotime($item['due_date']))) : '' ?></span>
|
||||
</div>
|
||||
<div class="progress-track"><span style="width:<?= h((string) $item['progress']) ?>%;--bar:<?= h($item['category_color'] ?: '#7c5cff') ?>"></span></div>
|
||||
<div class="budget-meta">
|
||||
<span><?= h(Support::money($item['paid_amount'], $symbol)) ?> paid</span>
|
||||
<span><?= h(Support::money($item['remaining_amount'], $symbol)) ?> owing</span>
|
||||
</div>
|
||||
<div class="budget-identity">
|
||||
<span class="category-mark" style="--dot:<?= h($item['category_color'] ?: '#718096') ?>"><?= h(strtoupper(substr($item['name'], 0, 1))) ?></span>
|
||||
<div><strong><?= h($item['name']) ?></strong><span><?= h($item['category_name'] ?: 'Uncategorized') ?><?php if ($item['due_date']): ?> · due <?= h(date('M j', strtotime($item['due_date']))) ?><?php endif; ?></span></div>
|
||||
</div>
|
||||
<div class="budget-progress">
|
||||
<div class="progress-labels"><span><?= number_format((float) $item['progress'], 0) ?>% paid</span><strong><?= h(Support::money($item['remaining_amount'], $symbol)) ?> left</strong></div>
|
||||
<div class="progress-track"><span style="width:<?= h((string) $item['progress']) ?>%;--bar:<?= h($item['category_color'] ?: '#7c5cff') ?>"></span></div>
|
||||
<small><?= h(Support::money($item['paid_amount'], $symbol)) ?> of <?= h(Support::money($item['planned_amount'], $symbol)) ?></small>
|
||||
</div>
|
||||
<div class="row-actions">
|
||||
<button class="icon-button" type="button" data-dialog-open="item-edit-<?= (int) $item['id'] ?>" aria-label="Edit <?= h($item['name']) ?>"><?= icon('edit') ?></button>
|
||||
<form method="post" data-confirm="Remove this budget target? Existing expenses will remain.">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="item.delete"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $item['id'] ?>">
|
||||
<button class="icon-button danger" type="submit" aria-label="Delete <?= h($item['name']) ?>"><?= icon('trash') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
<strong class="target"><?= h(Support::money($item['planned_amount'], $symbol)) ?></strong>
|
||||
<form method="post" data-confirm="Remove this budget item? Existing expenses will remain.">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="item.delete"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $item['id'] ?>">
|
||||
<button class="icon-button danger" title="Delete item" type="submit">×</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<article class="card panel chart-panel">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Comparative view</p><h2>Six-month movement</h2></div>
|
||||
<a class="text-link" href="<?= h(url('comparisons', ['month' => $month])) ?>">Full analysis →</a>
|
||||
<div><p class="eyebrow">Comparative movement</p><h2>Six-month cash flow</h2><p class="heading-note">Resources include rollover for each individual month.</p></div>
|
||||
<a class="text-link" href="<?= h(url('comparisons', ['month' => $month])) ?>">Open analytics <span>→</span></a>
|
||||
</div>
|
||||
<canvas class="trend-chart" data-trend-chart='<?= h(json_encode($comparison)) ?>' data-symbol="<?= h($symbol) ?>" height="220"></canvas>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<article class="card accent-card">
|
||||
<div class="card-heading"><div><p class="eyebrow">Monthly inputs</p><h2>Starting point</h2></div></div>
|
||||
<form method="post" class="stack-form compact">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="plan.save"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<label>Manual starting income<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="starting_income" min="0" step="0.01" value="<?= h((string) $plan['starting_income']) ?>"></div></label>
|
||||
<label>Previous-month remainder<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="rollover_in" min="0" step="0.01" value="<?= h((string) $plan['rollover_in']) ?>"></div></label>
|
||||
<label class="check-row slim"><input type="checkbox" name="rolling_enabled" value="1" <?= $plan['rolling_enabled'] ? 'checked' : '' ?>><span>Carry positive remainder forward</span></label>
|
||||
<label>Monthly note<textarea name="notes" rows="2" placeholder="What matters this month?"><?= h($plan['notes']) ?></textarea></label>
|
||||
<button class="button primary" type="submit">Save starting point</button>
|
||||
</form>
|
||||
<form method="post" class="inline-form top-gap">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="plan.refresh_rollover"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<button class="text-link" type="submit">↻ Recalculate from <?= h(date('F', strtotime($month . '-01 -1 month'))) ?></button>
|
||||
</form>
|
||||
<canvas class="trend-chart" data-trend-chart='<?= h(json_encode($comparison)) ?>' data-symbol="<?= h($symbol) ?>" height="255"></canvas>
|
||||
</article>
|
||||
|
||||
<?php if ($settings->module('income')): ?>
|
||||
<article class="card">
|
||||
<div class="card-heading"><div><p class="eyebrow">Income stream</p><h2>Additional income</h2></div><button class="button small secondary" type="button" data-dialog-open="income-dialog">+ Add</button></div>
|
||||
<?php if ($incomes === []): ?>
|
||||
<p class="muted">No additional income recorded.</p>
|
||||
<article class="card panel">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Cash activity</p><h2>Recent movement</h2></div>
|
||||
<a class="text-link" href="<?= h(url('transactions', ['month' => $month])) ?>">All transactions <span>→</span></a>
|
||||
</div>
|
||||
<?php if ($recentActivity === []): ?>
|
||||
<div class="empty-state compact"><strong>No movement yet</strong><p>Income, expenses, and dormant transfers will collect here.</p></div>
|
||||
<?php else: ?>
|
||||
<div class="mini-list">
|
||||
<?php foreach (array_slice($incomes, 0, 5) as $income): ?>
|
||||
<div><span><strong><?= h($income['label']) ?></strong><small><?= h(date('M j', strtotime($income['received_on']))) ?> · <?= h(Support::money($income['withholding_amount'], $symbol)) ?> withheld</small></span><strong class="green">+<?= h(Support::money($income['amount'], $symbol)) ?></strong></div>
|
||||
<div class="activity-list">
|
||||
<?php foreach ($recentActivity as $activity): ?>
|
||||
<div class="activity-row">
|
||||
<span class="activity-icon <?= h($activity['type']) ?>"><?= icon($activity['type'] === 'expense' ? 'expense' : ($activity['type'] === 'saved' ? 'vault' : 'wallet')) ?></span>
|
||||
<div><strong><?= h($activity['title']) ?></strong><small><?= h($activity['detail']) ?> · <?= h(date('M j', strtotime($activity['date']))) ?></small></div>
|
||||
<b class="<?= $activity['amount'] >= 0 ? 'green' : '' ?>"><?= $activity['amount'] >= 0 ? '+' : '-' ?><?= h(Support::money(abs($activity['amount']), $symbol)) ?></b>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<aside class="dashboard-side stack">
|
||||
<article class="card panel plan-card">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Plan health</p><h2><?= h($monthLabel) ?></h2></div>
|
||||
<button class="icon-button" type="button" data-dialog-open="plan-dialog" aria-label="Edit monthly plan"><?= icon('edit') ?></button>
|
||||
</div>
|
||||
<div class="health-score">
|
||||
<span style="--score:<?= h((string) min(100, max(0, $summary['budget_coverage']))) ?>"><?= number_format($summary['budget_coverage'], 0) ?>%</span>
|
||||
<div><strong>Target coverage</strong><small>Spendable funds compared with planned targets</small></div>
|
||||
</div>
|
||||
<dl class="plan-facts">
|
||||
<div><dt>Starting point</dt><dd><?= h(Support::money($summary['starting_income'], $symbol)) ?></dd></div>
|
||||
<div><dt>Rollover field</dt><dd><?= h(Support::money($summary['rollover_in'], $symbol)) ?></dd></div>
|
||||
<div><dt>Still unassigned</dt><dd class="<?= $summary['unallocated'] < 0 ? 'red' : '' ?>"><?= h(Support::money($summary['unallocated'], $symbol)) ?></dd></div>
|
||||
</dl>
|
||||
<form method="post" class="rollover-refresh">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="plan.refresh_rollover"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<button class="text-link" type="submit">Refresh from <?= h(date('F', strtotime($month . '-01 -1 month'))) ?> <span>↻</span></button>
|
||||
</form>
|
||||
<div class="rule-summary">
|
||||
<span><?= icon('vault') ?></span>
|
||||
<p><strong>Automatic set-aside</strong><small><?= h((string) $incomeRuleValue) ?><?= $incomeRule === 'percent' ? '% of income' : ' per income' ?> · <?= h((string) $transactionRuleValue) ?><?= $transactionRule === 'percent' ? '% per expense' : ' per expense' ?></small></p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card panel">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Coming up</p><h2>Next bills</h2></div>
|
||||
<a class="text-link" href="<?= h(url('calendar', ['month' => $month])) ?>">Calendar <span>→</span></a>
|
||||
</div>
|
||||
<?php if ($upcomingBills === []): ?>
|
||||
<div class="empty-state compact"><strong>Nothing due</strong><p>Add due dates to targets to build the bill queue.</p></div>
|
||||
<?php else: ?>
|
||||
<div class="due-list">
|
||||
<?php foreach ($upcomingBills as $bill): ?>
|
||||
<?php $days = (int) floor((strtotime($bill['due_date']) - strtotime(date('Y-m-d'))) / 86400); ?>
|
||||
<div>
|
||||
<span class="date-tile"><b><?= h(date('d', strtotime($bill['due_date']))) ?></b><small><?= h(strtoupper(date('M', strtotime($bill['due_date'])))) ?></small></span>
|
||||
<p><strong><?= h($bill['name']) ?></strong><small><?= h($bill['category_name'] ?: 'Uncategorized') ?> · <?= $days < 0 ? abs($days) . 'd overdue' : ($days === 0 ? 'due today' : 'in ' . $days . 'd') ?></small></p>
|
||||
<b><?= h(Support::money($bill['remaining_amount'], $symbol)) ?></b>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
|
||||
<?php if ($settings->module('income')): ?>
|
||||
<article class="card panel">
|
||||
<div class="card-heading">
|
||||
<div><p class="eyebrow">Income stream</p><h2>Additional income</h2></div>
|
||||
<button class="button small" type="button" data-dialog-open="income-dialog"><?= icon('plus') ?> Add</button>
|
||||
</div>
|
||||
<?php if ($incomes === []): ?>
|
||||
<div class="empty-state compact"><strong>No additional income</strong><p>Your manual starting point still counts as the base income.</p></div>
|
||||
<?php else: ?>
|
||||
<div class="income-list">
|
||||
<?php foreach (array_slice($incomes, 0, 5) as $income): ?>
|
||||
<div>
|
||||
<span class="activity-icon income"><?= icon('wallet') ?></span>
|
||||
<p><strong><?= h($income['label']) ?></strong><small><?= h(date('M j', strtotime($income['received_on']))) ?> · <?= h(Support::money($income['withholding_amount'], $symbol)) ?> set aside</small></p>
|
||||
<b>+<?= h(Support::money($income['amount'], $symbol)) ?></b>
|
||||
<button class="micro-button" type="button" data-dialog-open="income-edit-<?= (int) $income['id'] ?>" aria-label="Edit <?= h($income['label']) ?>"><?= icon('edit') ?></button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
|
||||
<article class="card stats-card">
|
||||
<div class="card-heading"><div><p class="eyebrow">Usage stats</p><h2>Planner activity</h2></div></div>
|
||||
<div class="stat-trio"><div><strong><?= (int) $visitorStats['today'] ?></strong><span>today</span></div><div><strong><?= (int) $visitorStats['thirty_days'] ?></strong><span>30-day visitors</span></div><div><strong><?= (int) $visitorStats['views'] ?></strong><span>page views</span></div></div>
|
||||
<article class="usage-card card">
|
||||
<p class="eyebrow">Private usage stats</p>
|
||||
<div><span><strong><?= (int) $visitorStats['today'] ?></strong><small>today</small></span><span><strong><?= (int) $visitorStats['thirty_days'] ?></strong><small>30-day visitors</small></span><span><strong><?= (int) $visitorStats['views'] ?></strong><small>page views</small></span></div>
|
||||
</article>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<dialog id="plan-dialog">
|
||||
<form method="post" class="dialog-card">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="plan.save"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<div class="dialog-heading"><div><p class="eyebrow">Monthly foundation</p><h2>Edit <?= h($monthLabel) ?> plan</h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||||
<div class="form-grid">
|
||||
<label>Manual starting income<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="starting_income" min="0" step="0.01" value="<?= h((string) $plan['starting_income']) ?>"></div></label>
|
||||
<label>Previous-month remainder<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="rollover_in" min="0" step="0.01" value="<?= h((string) $plan['rollover_in']) ?>"></div></label>
|
||||
<label class="check-row span-2"><input type="checkbox" name="rolling_enabled" value="1" <?= $plan['rolling_enabled'] ? 'checked' : '' ?>><span><strong>Carry positive remainder forward</strong><small>The next month can use any money left at close.</small></span></label>
|
||||
<label class="span-2">Monthly note<textarea name="notes" rows="3" placeholder="Intent, constraints, or focus for this month"><?= h($plan['notes']) ?></textarea></label>
|
||||
</div>
|
||||
<div class="dialog-actions"><button class="button" type="button" data-dialog-close>Cancel</button><button class="button primary" type="submit">Save monthly plan</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="item-dialog">
|
||||
<form method="post" class="dialog-card">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="item.add"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<div class="card-heading"><div><p class="eyebrow">Define the target</p><h2>Add budget item</h2></div><button class="icon-button" type="button" data-dialog-close>×</button></div>
|
||||
<div class="form-grid">
|
||||
<label class="span-2">Name<input type="text" name="name" required placeholder="Internet, rent, groceries…"></label>
|
||||
<label>Target amount<div class="money-input"><span><?= h($symbol) ?></span><input type="number" name="planned_amount" min="0" step="0.01" required></div></label>
|
||||
<label>Category<select name="category_id"><option value="">Uncategorized</option><?php foreach ($categories as $category): ?><option value="<?= (int) $category['id'] ?>"><?= h($category['name']) ?></option><?php endforeach; ?></select></label>
|
||||
<label>Due date<input type="date" name="due_date" value="<?= h($month . '-01') ?>"></label>
|
||||
<label>Repeat<select name="recurrence"><option value="none">One time</option><option value="monthly">Monthly</option><option value="yearly">Yearly</option></select></label>
|
||||
<label class="span-2">Notes<textarea name="notes" rows="2"></textarea></label>
|
||||
</div>
|
||||
<button class="button primary full" type="submit">Add budget item</button>
|
||||
<div class="dialog-heading"><div><p class="eyebrow">Progressional target</p><h2>Add budget item</h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||||
<?php require APP_ROOT . '/views/partials/item-fields.php'; ?>
|
||||
<div class="dialog-actions"><button class="button" type="button" data-dialog-close>Cancel</button><button class="button primary" type="submit">Add target</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<?php foreach ($items as $item): ?>
|
||||
<dialog id="item-edit-<?= (int) $item['id'] ?>">
|
||||
<form method="post" class="dialog-card">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="item.update"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $item['id'] ?>">
|
||||
<div class="dialog-heading"><div><p class="eyebrow">Maintain target</p><h2>Edit <?= h($item['name']) ?></h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||||
<?php $editingItem = $item; require APP_ROOT . '/views/partials/item-fields.php'; unset($editingItem); ?>
|
||||
<p class="form-note">Changes apply to this month. Future recurring copies use the latest saved version.</p>
|
||||
<div class="dialog-actions"><button class="button" type="button" data-dialog-close>Cancel</button><button class="button primary" type="submit">Save target</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<dialog id="income-dialog">
|
||||
<form method="post" class="dialog-card">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="income.add"><input type="hidden" name="month" value="<?= h($month) ?>">
|
||||
<div class="card-heading"><div><p class="eyebrow">Beyond the starting point</p><h2>Add income</h2></div><button class="icon-button" type="button" data-dialog-close>×</button></div>
|
||||
<div class="form-grid">
|
||||
<label class="span-2">Label<input type="text" name="label" required placeholder="Paycheck, freelance, refund…"></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>
|
||||
<label>Received<input type="date" name="received_on" value="<?= h(date('Y-m-d')) ?>" required></label>
|
||||
<label class="span-2">Notes<textarea name="notes" rows="2"></textarea></label>
|
||||
</div>
|
||||
<p class="form-note">The configured income withholding will move automatically to the dormant account.</p>
|
||||
<button class="button primary full" type="submit">Record income</button>
|
||||
<div class="dialog-heading"><div><p class="eyebrow">Beyond the starting point</p><h2>Add income</h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||||
<?php require APP_ROOT . '/views/partials/income-fields.php'; ?>
|
||||
<p class="form-note">The configured withholding 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 income</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<?php foreach ($incomes as $income): ?>
|
||||
<dialog id="income-edit-<?= (int) $income['id'] ?>">
|
||||
<form method="post" class="dialog-card">
|
||||
<?= csrf_field() ?><input type="hidden" name="action" value="income.update"><input type="hidden" name="month" value="<?= h($month) ?>"><input type="hidden" name="id" value="<?= (int) $income['id'] ?>">
|
||||
<div class="dialog-heading"><div><p class="eyebrow">Maintain income</p><h2>Edit <?= h($income['label']) ?></h2></div><button class="icon-button" type="button" data-dialog-close><?= icon('close') ?></button></div>
|
||||
<?php $editingIncome = $income; require APP_ROOT . '/views/partials/income-fields.php'; unset($editingIncome); ?>
|
||||
<div class="dialog-actions">
|
||||
<button class="text-link danger-link push-left" type="submit" name="action" value="income.delete" formnovalidate data-confirm-button="Delete this income entry and reverse its automatic withholding?"><?= icon('trash') ?> Delete</button>
|
||||
<button class="button" type="button" data-dialog-close>Cancel</button>
|
||||
<button class="button primary" type="submit">Save income</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<?php endforeach; ?>
|
||||
|
||||
Reference in New Issue
Block a user