179 lines
9.7 KiB
PHP
179 lines
9.7 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';
|
||
|
||
$sym = trim((string) ($familySettings['currency_symbol'] ?? '★'));
|
||
$name = trim((string) ($familySettings['currency_name'] ?? ''));
|
||
$tabTitle = currencyTabLabel($familySettings);
|
||
|
||
$canRecordExpense = $activePerson !== null
|
||
&& ($activePerson['role'] ?? '') === ROLE_HEAD
|
||
&& isHohVerified();
|
||
|
||
$leaderboard = $people;
|
||
usort($leaderboard, static function ($a, $b) {
|
||
$ba = is_numeric($a['currency_balance'] ?? null) ? (float) $a['currency_balance'] : 0.0;
|
||
$bb = 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');
|
||
?>
|
||
|
||
<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>
|
||
<th scope="col" class="text-end">Balance</th>
|
||
</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;
|
||
$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($bal, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?></strong> <?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-lg-7">
|
||
<?php if ($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">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>
|
||
</div>
|
||
</div>
|
||
</div>
|