Add leaderboard period functionality to currency tab
- Implemented a tabbed interface for the currency leaderboard, allowing users to switch between weekly, monthly, quarterly, and yearly views. - Enhanced the rendering of leaderboard data based on the selected period, improving user experience and data visibility. - Added JavaScript functionality to dynamically update the leaderboard and URL parameters based on user selection.
This commit is contained in:
parent
36ce29f7bb
commit
98c1649a32
@ -1157,6 +1157,53 @@
|
|||||||
window.alert(err.message || 'Could not reverse transaction');
|
window.alert(err.message || 'Could not reverse transaction');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var periodDataEl = document.getElementById('currencyLeaderboardPeriodData');
|
||||||
|
var periodBodyEl = document.getElementById('currencyLeaderboardPeriodBody');
|
||||||
|
var periodTabsEl = document.getElementById('currencyLeaderboardPeriodTabs');
|
||||||
|
if (periodDataEl && periodBodyEl && periodTabsEl) {
|
||||||
|
var periodData = {};
|
||||||
|
try {
|
||||||
|
periodData = JSON.parse(periodDataEl.textContent || '{}');
|
||||||
|
} catch (e) {
|
||||||
|
periodData = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLeaderboardPeriod(periodKey) {
|
||||||
|
var rows = Array.isArray(periodData[periodKey]) ? periodData[periodKey] : [];
|
||||||
|
var sym = periodBodyEl.getAttribute('data-currency-symbol') || '';
|
||||||
|
var html = '';
|
||||||
|
rows.forEach(function (row, idx) {
|
||||||
|
var total = Number(row.period_total || 0);
|
||||||
|
var isSelf = Boolean(row.is_self);
|
||||||
|
html += '<tr class="' + (isSelf ? 'table-primary' : '') + '">';
|
||||||
|
html += '<td class="text-muted">' + String(idx + 1) + '</td>';
|
||||||
|
html += '<td>' + escapeHtml(row.name || '') + (isSelf ? ' <span class="badge text-bg-info">You</span>' : '') + '</td>';
|
||||||
|
html += '<td class="text-end text-nowrap"><strong>' + escapeHtml(total.toFixed(2)) + '</strong> ' + escapeHtml(sym) + '</td>';
|
||||||
|
html += '</tr>';
|
||||||
|
});
|
||||||
|
periodBodyEl.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
periodTabsEl.querySelectorAll('[data-lb-period]').forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
var next = btn.getAttribute('data-lb-period') || 'weekly';
|
||||||
|
periodTabsEl.querySelectorAll('[data-lb-period]').forEach(function (b) {
|
||||||
|
b.classList.remove('active');
|
||||||
|
b.setAttribute('aria-selected', 'false');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
btn.setAttribute('aria-selected', 'true');
|
||||||
|
renderLeaderboardPeriod(next);
|
||||||
|
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
params.set('tab', 'currency');
|
||||||
|
params.set('lb_period', next);
|
||||||
|
var nextUrl = window.location.pathname + '?' + params.toString();
|
||||||
|
window.history.replaceState({}, '', nextUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindPeopleTable() {
|
function bindPeopleTable() {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ $bankingMode = bankingEnabled($familySettings);
|
|||||||
$canRecordExpense = $activePerson !== null
|
$canRecordExpense = $activePerson !== null
|
||||||
&& ($activePerson['role'] ?? '') === ROLE_HEAD
|
&& ($activePerson['role'] ?? '') === ROLE_HEAD
|
||||||
&& isHohVerified();
|
&& isHohVerified();
|
||||||
|
$activePersonId = (string) ($activePerson['id'] ?? '');
|
||||||
|
|
||||||
$people = migrateAllPeople($people);
|
$people = migrateAllPeople($people);
|
||||||
if ($bankingMode) {
|
if ($bankingMode) {
|
||||||
@ -32,6 +33,12 @@ usort($leaderboard, static function ($a, $b) {
|
|||||||
return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
|
return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$leaderboardPeriod = trim((string) ($_GET['lb_period'] ?? 'weekly'));
|
||||||
|
$allowedLeaderboardPeriods = ['weekly', 'monthly', 'quarterly', 'yearly'];
|
||||||
|
if (!in_array($leaderboardPeriod, $allowedLeaderboardPeriods, true)) {
|
||||||
|
$leaderboardPeriod = 'weekly';
|
||||||
|
}
|
||||||
|
|
||||||
$nameById = [];
|
$nameById = [];
|
||||||
foreach ($people as $p) {
|
foreach ($people as $p) {
|
||||||
if (!empty($p['id'])) {
|
if (!empty($p['id'])) {
|
||||||
@ -60,6 +67,90 @@ usort($bankRows, static function ($a, $b) {
|
|||||||
return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
|
return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
|
||||||
});
|
});
|
||||||
$recentBank = array_slice($bankRows, 0, 80);
|
$recentBank = array_slice($bankRows, 0, 80);
|
||||||
|
$leaderboardRowsByPeriod = [];
|
||||||
|
foreach ($allowedLeaderboardPeriods as $periodKey) {
|
||||||
|
$periodStart = new DateTimeImmutable('today');
|
||||||
|
if ($periodKey === 'monthly') {
|
||||||
|
$periodStart = $periodStart->modify('first day of this month');
|
||||||
|
} elseif ($periodKey === 'quarterly') {
|
||||||
|
$month = (int) $periodStart->format('n');
|
||||||
|
$quarterStartMonth = ((int) floor(($month - 1) / 3) * 3) + 1;
|
||||||
|
$periodStart = $periodStart->setDate((int) $periodStart->format('Y'), $quarterStartMonth, 1);
|
||||||
|
} elseif ($periodKey === 'yearly') {
|
||||||
|
$periodStart = $periodStart->setDate((int) $periodStart->format('Y'), 1, 1);
|
||||||
|
} else {
|
||||||
|
$periodStart = $periodStart->modify('monday this week');
|
||||||
|
}
|
||||||
|
$totalsByPerson = [];
|
||||||
|
foreach ($people as $p) {
|
||||||
|
$pid = (string) ($p['id'] ?? '');
|
||||||
|
if ($pid !== '') {
|
||||||
|
$totalsByPerson[$pid] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($bankRows as $row) {
|
||||||
|
if (!is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$pid = (string) ($row['person_id'] ?? '');
|
||||||
|
if ($pid === '' || !array_key_exists($pid, $totalsByPerson)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$createdAt = trim((string) ($row['created_at'] ?? ''));
|
||||||
|
if ($createdAt === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$rowDate = new DateTimeImmutable($createdAt);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($rowDate < $periodStart) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$amount = (float) ($row['amount'] ?? 0);
|
||||||
|
$type = (string) ($row['type'] ?? '');
|
||||||
|
if ($type === 'manual_credit' || $type === 'income_chore' || $type === 'reversal') {
|
||||||
|
$totalsByPerson[$pid] += $amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($type === 'manual_debit' || $type === 'charity_outflow') {
|
||||||
|
$totalsByPerson[$pid] -= $amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($type === 'transfer') {
|
||||||
|
$from = (string) ($row['from_account'] ?? '');
|
||||||
|
$to = (string) ($row['to_account'] ?? '');
|
||||||
|
if ($from === 'checking') {
|
||||||
|
$totalsByPerson[$pid] -= $amount;
|
||||||
|
} elseif ($to === 'checking') {
|
||||||
|
$totalsByPerson[$pid] += $amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rows = [];
|
||||||
|
foreach ($people as $p) {
|
||||||
|
$pid = (string) ($p['id'] ?? '');
|
||||||
|
if ($pid === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rows[] = [
|
||||||
|
'id' => $pid,
|
||||||
|
'name' => (string) ($p['name'] ?? ''),
|
||||||
|
'period_total' => bankingRoundMoney((float) ($totalsByPerson[$pid] ?? 0)),
|
||||||
|
'is_self' => $activePerson && ($activePerson['id'] ?? '') === $pid,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
usort($rows, static function ($a, $b) {
|
||||||
|
$cmp = ((float) ($b['period_total'] ?? 0)) <=> ((float) ($a['period_total'] ?? 0));
|
||||||
|
if ($cmp !== 0) {
|
||||||
|
return $cmp;
|
||||||
|
}
|
||||||
|
return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
|
||||||
|
});
|
||||||
|
$leaderboardRowsByPeriod[$periodKey] = $rows;
|
||||||
|
}
|
||||||
|
$leaderboardByPeriodRows = $leaderboardRowsByPeriod[$leaderboardPeriod] ?? [];
|
||||||
$donatedByPersonThisMonth = [];
|
$donatedByPersonThisMonth = [];
|
||||||
foreach ($bankRows as $row) {
|
foreach ($bankRows as $row) {
|
||||||
if (!is_array($row)) {
|
if (!is_array($row)) {
|
||||||
@ -78,7 +169,6 @@ foreach ($bankRows as $row) {
|
|||||||
}
|
}
|
||||||
$donatedByPersonThisMonth[$pid] = ($donatedByPersonThisMonth[$pid] ?? 0) + (float) ($row['amount'] ?? 0);
|
$donatedByPersonThisMonth[$pid] = ($donatedByPersonThisMonth[$pid] ?? 0) + (float) ($row['amount'] ?? 0);
|
||||||
}
|
}
|
||||||
$activePersonId = (string) ($activePerson['id'] ?? '');
|
|
||||||
$activeBankPerson = null;
|
$activeBankPerson = null;
|
||||||
foreach ($people as $p) {
|
foreach ($people as $p) {
|
||||||
if (($p['id'] ?? '') === $activePersonId) {
|
if (($p['id'] ?? '') === $activePersonId) {
|
||||||
@ -111,22 +201,33 @@ $activeCharityDonated = is_numeric($activeBankPerson['charity_donated_total'] ??
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane fade show active" id="currencyLeaderboardPane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade show active" id="currencyLeaderboardPane" role="tabpanel" tabindex="0">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header"><i class="fa fa-trophy me-1"></i>Total stars leaderboard</div>
|
<div class="card-header">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||||
|
<span><i class="fa fa-trophy me-1"></i>Leaderboard</span>
|
||||||
|
<div class="nav nav-pills nav-sm gap-1" id="currencyLeaderboardPeriodTabs" role="tablist" aria-label="Leaderboard period">
|
||||||
|
<button type="button" class="nav-link py-1 px-2 <?= $leaderboardPeriod === 'weekly' ? 'active' : '' ?>" data-lb-period="weekly">Weekly</button>
|
||||||
|
<button type="button" class="nav-link py-1 px-2 <?= $leaderboardPeriod === 'monthly' ? 'active' : '' ?>" data-lb-period="monthly">Monthly</button>
|
||||||
|
<button type="button" class="nav-link py-1 px-2 <?= $leaderboardPeriod === 'quarterly' ? 'active' : '' ?>" data-lb-period="quarterly">Quarterly</button>
|
||||||
|
<button type="button" class="nav-link py-1 px-2 <?= $leaderboardPeriod === 'yearly' ? 'active' : '' ?>" data-lb-period="yearly">Yearly</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
|
<script type="application/json" id="currencyLeaderboardPeriodData"><?= json_encode($leaderboardRowsByPeriod, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?></script>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped mb-0 leaderboard-table">
|
<table class="table table-striped mb-0 leaderboard-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-muted">#</th>
|
<th scope="col" class="text-muted">#</th>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col" class="text-end">Total <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></th>
|
<th scope="col" class="text-end">Net <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="currencyLeaderboardPeriodBody" data-currency-symbol="<?= htmlspecialchars($sym, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
<?php foreach ($leaderboard as $rank => $p): ?>
|
<?php foreach ($leaderboardByPeriodRows as $rank => $p): ?>
|
||||||
<?php
|
<?php
|
||||||
$pid = (string) ($p['id'] ?? '');
|
$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);
|
$total = (float) ($p['period_total'] ?? 0);
|
||||||
$isSelf = $activePerson && ($activePerson['id'] ?? '') === $pid;
|
$isSelf = $activePerson && ($activePerson['id'] ?? '') === $pid;
|
||||||
?>
|
?>
|
||||||
<tr class="<?= $isSelf ? 'table-primary' : '' ?>">
|
<tr class="<?= $isSelf ? 'table-primary' : '' ?>">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user