- Added banking mode with checking, savings, and charity accounts, including auto-split options for income. - Introduced banking transaction management, including transfers and charity outflows. - Updated family settings to allow configuration of banking features and interest rates. - Enhanced data export functionality to include bank transactions. - Improved user interface to display banking information and donation goals. - Updated documentation to reflect new banking features and settings.
440 lines
29 KiB
PHP
440 lines
29 KiB
PHP
<?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);
|
||
}
|
||
?>
|
||
|
||
<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>
|
||
|
||
<div class="row g-4">
|
||
<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>
|
||
|
||
<div class="col-lg-7">
|
||
<?php if ($bankingMode): ?>
|
||
<div class="card border-secondary mb-4">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span><i class="fa fa-arrow-right-arrow-left me-1"></i> Move your funds</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="bankTransferForm" class="row g-3">
|
||
<div class="col-md-4">
|
||
<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-md-4">
|
||
<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-md-4">
|
||
<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-md-4">
|
||
<label class="form-label" for="transfer_category">Category</label>
|
||
<input class="form-control" id="transfer_category" maxlength="50" value="transfer">
|
||
</div>
|
||
<div class="col-md-8">
|
||
<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">Submit transfer</button>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="alert d-none" id="bankTransferFeedback" role="status"></div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card border-secondary mb-4">
|
||
<div class="card-header"><i class="fa fa-heart me-1"></i> Monthly donation goal</div>
|
||
<div class="card-body">
|
||
<form id="donationGoalForm" class="row g-3">
|
||
<?php if ($canRecordExpense): ?>
|
||
<div class="col-md-6">
|
||
<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="<?= $canRecordExpense ? 'col-md-6' : 'col-md-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">Save donation goal</button>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="alert d-none" id="donationGoalFeedback" role="status"></div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($canRecordExpense): ?>
|
||
<div class="card border-secondary mb-4">
|
||
<div class="card-header">HoH checking credit/debit</div>
|
||
<div class="card-body">
|
||
<form id="bankAdjustForm" class="row g-3">
|
||
<div class="col-md-4">
|
||
<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-md-4">
|
||
<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-md-4">
|
||
<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-md-4">
|
||
<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-md-8">
|
||
<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">Save checking transaction</button></div>
|
||
<div class="col-12"><div class="alert d-none" id="bankAdjustFeedback" role="status"></div></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="card border-secondary mb-4">
|
||
<div class="card-header">HoH charity outflow</div>
|
||
<div class="card-body">
|
||
<form id="charityOutflowForm" class="row g-3">
|
||
<div class="col-md-4">
|
||
<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-md-4">
|
||
<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-md-4">
|
||
<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-md-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">Log charity outflow</button></div>
|
||
<div class="col-12"><div class="alert d-none" id="charityOutflowFeedback" role="status"></div></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?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 person’s 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; ?>
|
||
|
||
<div class="card">
|
||
<div class="card-header"><?= $bankingMode ? 'Recent bank transactions' : 'Recent expenses' ?></div>
|
||
<div class="card-body p-0">
|
||
<?php if ($bankingMode && count($recentBank) === 0): ?>
|
||
<p class="p-3 text-muted mb-0">No bank transactions recorded yet.</p>
|
||
<?php elseif (!$bankingMode && 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>
|
||
<?php if ($bankingMode): ?>
|
||
<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; ?>
|
||
<?php else: ?>
|
||
<th>Date</th>
|
||
<th>Title</th>
|
||
<th>To</th>
|
||
<th class="text-end">Amount</th>
|
||
<?php endif; ?>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ($bankingMode): ?>
|
||
<?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; ?>
|
||
<?php else: ?>
|
||
<?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; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php if ($bankingMode): ?>
|
||
<div class="card mt-4">
|
||
<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>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|