App: ledger - Page: budgets
PHP
budgets.php
|
HTML
budgets.pad
|
Result
|
<?php
// What each category of spending may cost a month. A budget of 0 takes the category's // budget away; the overview shows how far each month got.
if ( padPosted ( 'budgets' ) ) {
foreach ( (array) padRequest ( 'budget', [] ) as $id => $amount ) { if ( ! is_scalar ( $amount ) ) continue; $amount = (float) str_replace ( ',', '.', (string) $amount ); if ( $amount > 0 ) db ( "INSERT OR REPLACE INTO budgets (category_id, monthly) SELECT id, {1} FROM categories WHERE id={0} AND kind = 'expense'", [ (int) $id, round ( $amount, 2 ) ] ); else db ( "DELETE FROM budgets WHERE category_id={0}", [ (int) $id ] ); }
padFlash ( 'The budgets are saved.' ); padRedirect ( 'index', [ 'month' => $ledgerMonth ] );
}
$plan = array_values ( db ( "ARRAY c.id, c.name, c.color, coalesce(b.monthly, 0) AS monthly FROM categories c LEFT JOIN budgets b ON b.category_id = c.id WHERE c.kind = 'expense' ORDER BY c.name" ) ?: [] );
$planned = array_sum ( array_column ( $plan, 'monthly' ) ); $ledgerPage = 'Budgets';
?>
|
<div class="narrow">
<h1>Budgets</h1> <p class="lead">What each kind of spending may cost a month - {$planned | money} together. 0 leaves a category without one.</p>
{form 'budgets', class='budget-form'} {plan} <label class="budget-row" for="budget-{$id}"><span class="dot" style="background: {$color}"></span>{$name} <input id="budget-{$id}" name="budget[{$id}]" value="{$monthly}" inputmode="decimal" size="8"></label> {/plan} <div class="bar"><button class="button primary">Save</button><a class="button" href="{$padGo}index{?month}">Cancel</a></div> {/form}
</div>
|
|
App: ledger - Page: index
PHP
index.php
|
HTML
index.pad
|
Result
|
<?php
// The month at a glance: what came in, what went out, what is left; six months of it as a // chart, this month's spending by category, the budgets, the accounts, the latest entries // - and a quick entry that ledger.js posts as JSON, answered by quick.php.
$totals = ledgerTotals ( $ledgerMonth ); $history = ledgerHistory ( $ledgerMonth ); $spending = ledgerSpending ( $ledgerMonth ); $budgets = ledgerBudgets ( $ledgerMonth ); $accounts = ledgerAccounts (); $latest = array_slice ( array_values ( array_filter ( ledgerRows ( $ledgerMonth ), fn ( $r ) => $r ['kind'] != 'transfer' ) ), 0, 8 ); $spendable = array_values ( array_filter ( ledgerCategories (), fn ( $c ) => $c ['kind'] == 'expense' ) ); $monthName = ledgerMonthName ( $ledgerMonth ); $today = date ( 'Y-m-d' );
$ledgerPage = $monthName;
?>
|
<div class="head"> <h1>{$monthName}</h1> <span class="dim">{$totals.entries} entries</span> </div>
<section class="kpis" aria-label="This month"> <div class="kpi in"><span>Income</span><b data-kpi="income">{$totals.income | money}</b></div> <div class="kpi out"><span>Spending</span><b data-kpi="spent">{$totals.spent | money}</b></div> <div class="kpi"><span>Left</span><b data-kpi="left">{$totals.left | money}</b></div> <div class="kpi"><span>Kept</span><b data-kpi="kept">{$totals.kept}%</b></div> </section>
<div class="columns">
<div class="main">
<section class="card"> <h2>Six months</h2> {chart 'bar', data='history', label='month', value='income, spending', title='Income and spending, six months'} </section>
<section class="card"> <h2>Where it went</h2> {if $totals.spent gt 0} {chart 'donut', data='spending', label='category', value='spent', title='Spending by category'} {else} <p class="dim">Nothing spent this month.</p> {/if} </section>
<section class="card"> <h2>Budgets <a class="more" href="{$padGo}budgets{?month}">Change</a></h2> <ul class="budgets"> {budgets} <li class="{$state}"> <div class="line"><span class="dot" style="background: {$color}"></span><b>{$name}</b><span>{$spent | money} of {$monthly | money}</span></div> {progress $share, max=100, label=$name, color=$tone} </li> @else@ <li class="dim">No budgets yet.</li> {/budgets} </ul> </section>
</div>
<aside class="side">
<section class="card quick"> <h2>Quick entry</h2> {form 'quick', action='?quick', class='quick-form'} <input type="hidden" name="month" value="{$ledgerMonth}"> <label for="q-amount">Amount</label> <input id="q-amount" name="amount" inputmode="decimal" placeholder="12.50" required pattern="[0-9]+([.,][0-9][0-9]?)?"> <label for="q-category">Category</label> <select id="q-category" name="category">{spendable}<option value="{$id}">{$name}</option>{/spendable}</select> <label for="q-note">Note</label> <input id="q-note" name="note" placeholder="Coffee" maxlength="120"> <button class="button primary">Spend it</button> <p class="said" role="status"></p> {/form} </section>
<section class="card"> <h2>Accounts</h2> <ul class="accounts"> {accounts}<li><span>{$name}</span><b class="{if $balance lt 0}neg{/if}">{$balance | money}</b></li>{/accounts} </ul> </section>
<section class="card"> <h2>Latest <a class="more" href="{$padGo}transactions{?month}">All</a></h2> <ul class="latest"> {latest}<li><span class="day">{$day | date('j M')}</span><span class="what">{$note}<small>{$category}</small></span><b class="{if $amount lt 0}neg{else}pos{/if}">{$amount | money}</b></li> @else@<li class="dim">No entries this month.</li>{/latest} </ul> </section>
</aside>
</div>
|
|
App: ledger - Page: transactions
PHP
transactions.php
|
HTML
transactions.pad
|
Result
|
<?php
// A month's transactions as one table - sorted and paged on the server by {datatable} - // narrowed to a category, an account or a word in the note. The same rows answer as CSV // (?transactions&padFormat=csv) and JSON: $padExpose names them.
$only = (int) padRequest ( 'category', 0 ); $from = (int) padRequest ( 'account', 0 ); $word = trim ( ledgerText ( 'q' ) );
$rows = array_map ( fn ( $r ) => array_diff_key ( $r, [ 'id' => 1, 'kind' => 1 ] ), ledgerRows ( $ledgerMonth, $only, $from, $word ) ); $shown = count ( $rows ); $sum = round ( array_sum ( array_column ( $rows, 'amount' ) ), 2 );
$categoryList = array_map ( fn ( $c ) => $c + [ 'chosen' => $c ['id'] == $only ], ledgerCategories () ); $accountList = array_map ( fn ( $a ) => $a + [ 'chosen' => $a ['id'] == $from ], ledgerAccounts () ); $monthName = ledgerMonthName ( $ledgerMonth ); $csvName = "ledger-$ledgerMonth.csv"; $csvQuery = http_build_query ( [ 'month' => $ledgerMonth, 'category' => $only, 'account' => $from, 'q' => $word, 'padFormat' => 'csv' ] );
$padExpose = [ 'rows' ]; $ledgerPage = "Transactions, $monthName";
?>
|
<div class="head"> <h1>Transactions <span class="dim">{$monthName}</span></h1> <a class="button" href="{$padGo}transactions&{$csvQuery}" download="{$csvName}">⬇ CSV</a> </div>
<form class="filters" method="get"> <input type="hidden" name="transactions" value=""> <input type="hidden" name="month" value="{$ledgerMonth}"> <label>Category <select name="category"><option value="0">All</option>{categoryList}<option value="{$id}"{if $chosen} selected{/if}>{$name}</option>{/categoryList}</select> </label> <label>Account <select name="account"><option value="0">All</option>{accountList}<option value="{$id}"{if $chosen} selected{/if}>{$name}</option>{/accountList}</select> </label> <label>Note <input type="search" name="q" value="{$word}" placeholder="a word"></label> <button class="button">Show</button> </form>
<section class="card table"> {if $shown gt 0} {datatable data='rows', columns='day, category, account, note, amount', format="amount:money, day:date('D j M')", totals='amount', sortable, rows=15, striped} {else} <p class="dim">No transactions here.</p> {/if} <p class="dim sum">{$shown} transactions, together {$sum | money}.</p> </section>
|
|
|