familyHub/tabs/currency.php
Louis Whittington 6d27eb62f8 Add banking overview UI and enhance currency tab functionality
- Introduced a new banking overview section with account details for checking, savings, and charity balances.
- Implemented a tabbed interface for the currency section, allowing users to switch between leaderboard and banking views.
- Enhanced user experience with animations and improved layout for better visibility of account information.
- Maintained existing functionality while integrating new banking features seamlessly.
2026-03-31 11:45:46 -05:00

435 lines
34 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/utils.php';
require_once __DIR__ . '/../includes/persona.php';
require_once __DIR__ . '/../includes/expense_helpers.php';
require_once __DIR__ . '/../includes/family_settings.php';
require_once __DIR__ . '/../includes/banking_helpers.php';
$sym = trim((string) ($familySettings['currency_symbol'] ?? '★'));
$name = trim((string) ($familySettings['currency_name'] ?? ''));
$tabTitle = currencyTabLabel($familySettings);
$bankingMode = bankingEnabled($familySettings);
$canRecordExpense = $activePerson !== null
&& ($activePerson['role'] ?? '') === ROLE_HEAD
&& isHohVerified();
$people = migrateAllPeople($people);
if ($bankingMode) {
$people = bankingApplySavingsInterestToPeople($people, $familySettings);
writeJsonFile('people.json', $people);
}
$leaderboard = $people;
usort($leaderboard, static function ($a, $b) {
$ba = is_numeric($a['checking_balance'] ?? null) ? (float) $a['checking_balance'] : (is_numeric($a['currency_balance'] ?? null) ? (float) $a['currency_balance'] : 0.0);
$bb = is_numeric($b['checking_balance'] ?? null) ? (float) $b['checking_balance'] : (is_numeric($b['currency_balance'] ?? null) ? (float) $b['currency_balance'] : 0.0);
$cmp = $bb <=> $ba;
if ($cmp !== 0) {
return $cmp;
}
return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
});
$nameById = [];
foreach ($people as $p) {
if (!empty($p['id'])) {
$nameById[(string) $p['id']] = (string) ($p['name'] ?? '');
}
}
$expenses = normalizeExpensesList(readJsonFile('expenses.json'));
usort($expenses, static function ($a, $b) {
$da = (string) ($a['date'] ?? '');
$db = (string) ($b['date'] ?? '');
if ($db !== $da) {
return strcmp($db, $da);
}
return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
});
$recentExpenses = array_slice($expenses, 0, 50);
$today = gmdate('Y-m-d');
$currentMonth = gmdate('Y-m');
$bankRows = readJsonFile('bank_transactions.json');
if (!is_array($bankRows)) {
$bankRows = [];
}
usort($bankRows, static function ($a, $b) {
return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
});
$recentBank = array_slice($bankRows, 0, 80);
$donatedByPersonThisMonth = [];
foreach ($bankRows as $row) {
if (!is_array($row)) {
continue;
}
if ((string) ($row['type'] ?? '') !== 'charity_outflow') {
continue;
}
$dateYmd = bankingYmd((string) ($row['created_at'] ?? ''));
if (strpos($dateYmd, $currentMonth . '-') !== 0) {
continue;
}
$pid = (string) ($row['person_id'] ?? '');
if ($pid === '') {
continue;
}
$donatedByPersonThisMonth[$pid] = ($donatedByPersonThisMonth[$pid] ?? 0) + (float) ($row['amount'] ?? 0);
}
$activePersonId = (string) ($activePerson['id'] ?? '');
$activeBankPerson = null;
foreach ($people as $p) {
if (($p['id'] ?? '') === $activePersonId) {
$activeBankPerson = $p;
break;
}
}
$activeChecking = is_numeric($activeBankPerson['checking_balance'] ?? null) ? (float) $activeBankPerson['checking_balance'] : 0.0;
$activeSavings = is_numeric($activeBankPerson['savings_balance'] ?? null) ? (float) $activeBankPerson['savings_balance'] : 0.0;
$activeCharityPending = is_numeric($activeBankPerson['charity_pending_balance'] ?? null) ? (float) $activeBankPerson['charity_pending_balance'] : 0.0;
$activeCharityDonated = is_numeric($activeBankPerson['charity_donated_total'] ?? null) ? (float) $activeBankPerson['charity_donated_total'] : 0.0;
?>
<div id="currency" class="tab-content">
<h2 class="mb-2"><?= htmlspecialchars($tabTitle, ENT_QUOTES, 'UTF-8') ?></h2>
<p class="text-muted small mb-4">Leaderboard and expenses use the family currency from Family settings.</p>
<?php if ($bankingMode): ?>
<ul class="nav nav-pills mb-3 gap-2" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="pill" data-bs-target="#currencyLeaderboardPane" type="button" role="tab" aria-selected="true">
<i class="fa fa-trophy me-1"></i>Leaderboard
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="pill" data-bs-target="#currencyBankingPane" type="button" role="tab" aria-selected="false">
<i class="fa fa-building-columns me-1"></i>Banking
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="currencyLeaderboardPane" role="tabpanel" tabindex="0">
<div class="card mb-4">
<div class="card-header"><i class="fa fa-trophy me-1"></i>Total stars leaderboard</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped mb-0 leaderboard-table">
<thead>
<tr>
<th scope="col" class="text-muted">#</th>
<th scope="col">Name</th>
<th scope="col" class="text-end">Total <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($leaderboard as $rank => $p): ?>
<?php
$pid = (string) ($p['id'] ?? '');
$total = is_numeric($p['checking_balance'] ?? null) ? (float) $p['checking_balance'] : (is_numeric($p['currency_balance'] ?? null) ? (float) $p['currency_balance'] : 0.0);
$isSelf = $activePerson && ($activePerson['id'] ?? '') === $pid;
?>
<tr class="<?= $isSelf ? 'table-primary' : '' ?>">
<td class="text-muted"><?= (int) $rank + 1 ?></td>
<td><?= sanitizeInput($p['name'] ?? '') ?><?php if ($isSelf): ?> <span class="badge text-bg-info">You</span><?php endif; ?></td>
<td class="text-end text-nowrap"><strong><?= htmlspecialchars(number_format($total, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?></strong> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="currencyBankingPane" role="tabpanel" tabindex="0">
<?php endif; ?>
<div class="<?= $bankingMode ? '' : 'row g-4' ?>">
<?php if (!$bankingMode): ?>
<div class="col-lg-5">
<div class="card h-100">
<div class="card-header">
<i class="fa fa-trophy me-1" aria-hidden="true"></i> Leaderboard
</div>
<div class="card-body p-0">
<?php if (count($leaderboard) === 0): ?>
<p class="p-3 text-muted mb-0">Add people in Family settings to see balances.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped mb-0 leaderboard-table">
<thead>
<tr>
<th scope="col" class="text-muted">#</th>
<th scope="col">Name</th>
<?php if ($bankingMode): ?>
<th scope="col" class="text-end">Checking</th>
<th scope="col" class="text-end">Savings</th>
<th scope="col" class="text-end">Charity pending</th>
<th scope="col" class="text-end">Donated total</th>
<th scope="col">Donation goal progress</th>
<?php else: ?>
<th scope="col" class="text-end">Balance</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($leaderboard as $rank => $p): ?>
<?php
$pid = (string) ($p['id'] ?? '');
$bal = is_numeric($p['currency_balance'] ?? null) ? (float) $p['currency_balance'] : 0.0;
$checkingBal = is_numeric($p['checking_balance'] ?? null) ? (float) $p['checking_balance'] : 0.0;
$savingsBal = is_numeric($p['savings_balance'] ?? null) ? (float) $p['savings_balance'] : 0.0;
$charityPending = is_numeric($p['charity_pending_balance'] ?? null) ? (float) $p['charity_pending_balance'] : 0.0;
$charityDonated = is_numeric($p['charity_donated_total'] ?? null) ? (float) $p['charity_donated_total'] : 0.0;
$goal = is_numeric($p['donation_goal_monthly'] ?? null) ? (float) $p['donation_goal_monthly'] : 0.0;
$donatedThisMonth = (float) ($donatedByPersonThisMonth[$pid] ?? 0);
$goalPct = $goal > 0 ? (int) max(0, min(100, round(($donatedThisMonth / $goal) * 100))) : 0;
$isSelf = $activePerson && ($activePerson['id'] ?? '') === $pid;
?>
<tr class="<?= $isSelf ? 'table-primary' : '' ?>">
<td class="text-muted"><?= (int) $rank + 1 ?></td>
<td><?= sanitizeInput($p['name'] ?? '') ?><?php if ($isSelf): ?> <span class="badge text-bg-info">You</span><?php endif; ?></td>
<?php if ($bankingMode): ?>
<td class="text-end text-nowrap"><strong><?= htmlspecialchars(number_format($checkingBal, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?></strong> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<td class="text-end text-nowrap"><?= htmlspecialchars(number_format($savingsBal, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<td class="text-end text-nowrap"><?= htmlspecialchars(number_format($charityPending, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<td class="text-end text-nowrap"><?= htmlspecialchars(number_format($charityDonated, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<td>
<?php if ($goal > 0): ?>
<div class="progress" role="progressbar" aria-label="Donation goal progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="<?= $goalPct ?>">
<div class="progress-bar" style="width: <?= $goalPct ?>%"></div>
</div>
<div class="small text-muted mt-1"><?= htmlspecialchars(number_format($donatedThisMonth, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> / <?= htmlspecialchars(number_format($goal, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></div>
<?php else: ?>
<span class="small text-muted">No goal set</span>
<?php endif; ?>
</td>
<?php else: ?>
<td class="text-end text-nowrap"><strong><?= htmlspecialchars(number_format($bal, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?></strong> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<div class="<?= $bankingMode ? '' : 'col-lg-7' ?>">
<?php if ($bankingMode): ?>
<div class="row g-4">
<div class="col-lg-8">
<div class="banking-hero mb-3">
<h3 class="h5 mb-1">Account overview for <?= sanitizeInput((string) ($activeBankPerson['name'] ?? 'current person')) ?></h3>
<p class="small text-muted mb-0">Track your accounts and move money with one tap.</p>
</div>
<div class="row g-3 mb-4">
<div class="col-sm-6">
<div class="card banking-overview-card banking-overview-checking"><div class="card-body"><div class="small text-muted">Checking</div><div class="h4 mb-0"><?= htmlspecialchars(number_format($activeChecking, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></div></div></div>
</div>
<div class="col-sm-6">
<div class="card banking-overview-card banking-overview-savings"><div class="card-body"><div class="small text-muted">Savings</div><div class="h4 mb-0"><?= htmlspecialchars(number_format($activeSavings, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></div></div></div>
</div>
<div class="col-sm-6">
<div class="card banking-overview-card banking-overview-charity"><div class="card-body"><div class="small text-muted">Charity pending</div><div class="h4 mb-0"><?= htmlspecialchars(number_format($activeCharityPending, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></div></div></div>
</div>
<div class="col-sm-6">
<div class="card banking-overview-card banking-overview-donated"><div class="card-body"><div class="small text-muted">Donated total</div><div class="h4 mb-0"><?= htmlspecialchars(number_format($activeCharityDonated, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></div></div></div>
</div>
</div>
<div class="card">
<div class="card-header">Recent bank transactions</div>
<div class="card-body p-0">
<?php if (count($recentBank) === 0): ?>
<p class="p-3 text-muted mb-0">No bank transactions recorded yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr><th>Date</th><th>Person</th><th>Type</th><th>Category</th><th>Note</th><th class="text-end">Amount</th><?php if ($canRecordExpense): ?><th></th><?php endif; ?></tr>
</thead>
<tbody>
<?php foreach ($recentBank as $row): ?>
<?php $pid = (string) ($row['person_id'] ?? ''); $rawAmt = (float) ($row['amount'] ?? 0); $type = (string) ($row['type'] ?? ''); $amtSign = in_array($type, ['manual_debit', 'charity_outflow'], true) ? '' : '+'; ?>
<tr>
<td class="text-nowrap"><?= sanitizeInput((string) ($row['created_at'] ?? '')) ?></td>
<td><?= sanitizeInput($nameById[$pid] ?? '') ?></td>
<td><?= sanitizeInput($type) ?></td>
<td><?= sanitizeInput((string) ($row['category'] ?? '')) ?></td>
<td><?= sanitizeInput((string) ($row['note'] ?? '')) ?></td>
<td class="text-end text-nowrap"><?= $amtSign ?><?= htmlspecialchars(number_format($rawAmt, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
<?php if ($canRecordExpense): ?><td class="text-end"><?php if (empty($row['reversed_by_transaction_id']) && ($row['type'] ?? '') !== 'reversal'): ?><button type="button" class="btn btn-sm btn-outline-danger btn-bank-reverse" data-id="<?= htmlspecialchars((string) ($row['id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">Reverse</button><?php endif; ?></td><?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="accordion" id="bankingActionsAccordion">
<div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bankActionTransfer">Move funds</button></h2>
<div id="bankActionTransfer" class="accordion-collapse collapse" data-bs-parent="#bankingActionsAccordion"><div class="accordion-body">
<form id="bankTransferForm" class="row g-3">
<div class="col-6"><label class="form-label" for="transfer_from_account">From</label><select class="form-select" id="transfer_from_account" required><option value="checking">Checking</option><option value="savings">Savings</option></select></div>
<div class="col-6"><label class="form-label" for="transfer_to_account">To</label><select class="form-select" id="transfer_to_account" required><option value="savings">Savings</option><option value="checking">Checking</option><option value="charity">Charity (irreversible)</option></select></div>
<div class="col-12"><label class="form-label" for="transfer_amount">Amount (<?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?>)</label><input type="number" step="0.01" min="0.01" class="form-control" id="transfer_amount" required></div>
<div class="col-12"><label class="form-label" for="transfer_category">Category</label><input class="form-control" id="transfer_category" maxlength="50" value="transfer"></div>
<div class="col-12"><label class="form-label" for="transfer_note">Note</label><input class="form-control" id="transfer_note" maxlength="200" placeholder="Optional note"></div>
<div class="col-12"><button type="submit" class="btn btn-primary w-100">Submit transfer</button></div>
<div class="col-12"><div class="alert d-none" id="bankTransferFeedback" role="status"></div></div>
</form>
</div></div>
</div>
<div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bankActionGoal">Donation goal</button></h2>
<div id="bankActionGoal" class="accordion-collapse collapse" data-bs-parent="#bankingActionsAccordion"><div class="accordion-body">
<form id="donationGoalForm" class="row g-3">
<?php if ($canRecordExpense): ?><div class="col-12"><label class="form-label" for="donation_goal_person">Person</label><select class="form-select" id="donation_goal_person"><?php foreach ($people as $p): ?><?php if (empty($p['id'])) { continue; } ?><option value="<?= htmlspecialchars((string) $p['id'], ENT_QUOTES, 'UTF-8') ?>" <?= (($activePerson['id'] ?? '') === ($p['id'] ?? '')) ? 'selected' : '' ?>><?= sanitizeInput($p['name'] ?? '') ?></option><?php endforeach; ?></select></div><?php else: ?><input type="hidden" id="donation_goal_person" value="<?= htmlspecialchars((string) ($activePerson['id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"><?php endif; ?>
<div class="col-12"><label class="form-label" for="donation_goal_monthly">Goal for month (<?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?>)</label><input type="number" step="0.01" min="0" class="form-control" id="donation_goal_monthly" value="<?= htmlspecialchars(number_format((float) ($activePerson['donation_goal_monthly'] ?? 0), 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>"></div>
<div class="col-12"><button type="submit" class="btn btn-outline-primary w-100">Save donation goal</button></div>
<div class="col-12"><div class="alert d-none" id="donationGoalFeedback" role="status"></div></div>
</form>
</div></div>
</div>
<?php if ($canRecordExpense): ?>
<div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bankActionAdjust">HoH checking credit/debit</button></h2>
<div id="bankActionAdjust" class="accordion-collapse collapse" data-bs-parent="#bankingActionsAccordion"><div class="accordion-body">
<form id="bankAdjustForm" class="row g-3">
<div class="col-12"><label class="form-label" for="bank_adjust_person">Person</label><select class="form-select" id="bank_adjust_person" required><?php foreach ($people as $p): ?><?php if (empty($p['id'])) { continue; } ?><option value="<?= htmlspecialchars((string) $p['id'], ENT_QUOTES, 'UTF-8') ?>"><?= sanitizeInput($p['name'] ?? '') ?></option><?php endforeach; ?></select></div>
<div class="col-6"><label class="form-label" for="bank_adjust_type">Type</label><select class="form-select" id="bank_adjust_type"><option value="credit">Credit</option><option value="debit">Debit</option></select></div>
<div class="col-6"><label class="form-label" for="bank_adjust_amount">Amount</label><input type="number" min="0.01" step="0.01" class="form-control" id="bank_adjust_amount" required></div>
<div class="col-12"><label class="form-label" for="bank_adjust_category">Category</label><input class="form-control" id="bank_adjust_category" maxlength="50" value="manual"></div>
<div class="col-12"><label class="form-label" for="bank_adjust_note">Note</label><input class="form-control" id="bank_adjust_note" maxlength="200"></div>
<div class="col-12"><button type="submit" class="btn btn-danger w-100">Save checking transaction</button></div>
<div class="col-12"><div class="alert d-none" id="bankAdjustFeedback" role="status"></div></div>
</form>
</div></div>
</div>
<div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bankActionCharity">HoH charity outflow</button></h2>
<div id="bankActionCharity" class="accordion-collapse collapse" data-bs-parent="#bankingActionsAccordion"><div class="accordion-body">
<form id="charityOutflowForm" class="row g-3">
<div class="col-12"><label class="form-label" for="charity_outflow_person">Person</label><select class="form-select" id="charity_outflow_person" required><?php foreach ($people as $p): ?><?php if (empty($p['id'])) { continue; } ?><option value="<?= htmlspecialchars((string) $p['id'], ENT_QUOTES, 'UTF-8') ?>"><?= sanitizeInput($p['name'] ?? '') ?></option><?php endforeach; ?></select></div>
<div class="col-6"><label class="form-label" for="charity_outflow_amount">Amount</label><input type="number" min="0.01" step="0.01" class="form-control" id="charity_outflow_amount" required></div>
<div class="col-6"><label class="form-label" for="charity_outflow_category">Category</label><input class="form-control" id="charity_outflow_category" maxlength="50" value="donation"></div>
<div class="col-12"><label class="form-label" for="charity_outflow_note">Note</label><input class="form-control" id="charity_outflow_note" maxlength="200"></div>
<div class="col-12"><button type="submit" class="btn btn-warning w-100">Log charity outflow</button></div>
<div class="col-12"><div class="alert d-none" id="charityOutflowFeedback" role="status"></div></div>
</form>
</div></div>
</div>
<?php endif; ?>
</div>
<div class="card mt-3">
<div class="card-header">Monthly statements</div>
<div class="card-body">
<p class="small text-muted mb-2">Download your monthly statement as JSON or CSV.</p>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($familyHubApiBase, ENT_QUOTES, 'UTF-8') ?>/bank_statement.php?month=<?= htmlspecialchars($currentMonth, ENT_QUOTES, 'UTF-8') ?>&format=json">Current month JSON</a>
<a class="btn btn-outline-secondary btn-sm" href="<?= htmlspecialchars($familyHubApiBase, ENT_QUOTES, 'UTF-8') ?>/bank_statement.php?month=<?= htmlspecialchars($currentMonth, ENT_QUOTES, 'UTF-8') ?>&format=csv">Current month CSV</a>
</div>
</div>
</div>
</div>
</div>
<?php elseif ($canRecordExpense): ?>
<div class="card border-secondary mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fa fa-minus-circle me-1"></i> Record expense</span>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#expenseFormCollapse" aria-expanded="false" aria-controls="expenseFormCollapse">
Show form
</button>
</div>
<div class="collapse" id="expenseFormCollapse">
<div class="card-body">
<p class="small text-muted">Deducts from one persons balance (e.g. spent allowance). Requires enough balance.</p>
<form id="expenseForm" class="row g-3">
<div class="col-md-6">
<label class="form-label" for="expense_title">Title</label>
<input class="form-control" id="expense_title" required maxlength="120">
</div>
<div class="col-md-6">
<label class="form-label" for="expense_value">Amount (<?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?>)</label>
<input type="number" class="form-control" id="expense_value" min="0.01" step="0.01" required>
</div>
<div class="col-md-6">
<label class="form-label" for="expense_date">Date</label>
<input type="date" class="form-control" id="expense_date" value="<?= htmlspecialchars($today, ENT_QUOTES, 'UTF-8') ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="expense_assignee">Charged to</label>
<select class="form-select" id="expense_assignee" required>
<option value="">Choose person…</option>
<?php foreach ($people as $p): ?>
<?php if (empty($p['id'])) { continue; } ?>
<option value="<?= htmlspecialchars((string) $p['id'], ENT_QUOTES, 'UTF-8') ?>"><?= sanitizeInput($p['name'] ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12">
<label class="form-label" for="expense_description">Description</label>
<textarea class="form-control" id="expense_description" rows="2"></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-danger">Apply expense</button>
</div>
<div class="col-12">
<div class="alert d-none" id="expenseFormFeedback" role="status"></div>
</div>
</form>
</div>
</div>
</div>
<?php elseif (count($people) > 0): ?>
<p class="text-muted small">Switch to a verified Head of household to record expenses.</p>
<?php endif; ?>
<?php if (!$bankingMode): ?>
<div class="card">
<div class="card-header">Recent expenses</div>
<div class="card-body p-0">
<?php if (count($recentExpenses) === 0): ?>
<p class="p-3 text-muted mb-0">No expenses recorded yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>To</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentExpenses as $ex): ?>
<tr>
<td class="text-nowrap"><?= sanitizeInput((string) ($ex['date'] ?? '')) ?></td>
<td><?= sanitizeInput((string) ($ex['title'] ?? '')) ?></td>
<td><?= sanitizeInput($nameById[(string) ($ex['assignee_id'] ?? '')] ?? '') ?></td>
<td class="text-end text-nowrap"><?= htmlspecialchars(number_format((float) ($ex['value'] ?? 0), 2, '.', ''), ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php if ($bankingMode): ?>
</div>
</div>
<?php endif; ?>
</div>