diff --git a/assets/js/main.js b/assets/js/main.js index 1059fd7..ef8df8d 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1157,6 +1157,53 @@ 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 += ''; + html += '' + String(idx + 1) + ''; + html += '' + escapeHtml(row.name || '') + (isSelf ? ' You' : '') + ''; + html += '' + escapeHtml(total.toFixed(2)) + ' ' + escapeHtml(sym) + ''; + html += ''; + }); + 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() { diff --git a/tabs/currency.php b/tabs/currency.php index 014ed7b..ad8ede0 100644 --- a/tabs/currency.php +++ b/tabs/currency.php @@ -14,6 +14,7 @@ $bankingMode = bankingEnabled($familySettings); $canRecordExpense = $activePerson !== null && ($activePerson['role'] ?? '') === ROLE_HEAD && isHohVerified(); +$activePersonId = (string) ($activePerson['id'] ?? ''); $people = migrateAllPeople($people); if ($bankingMode) { @@ -32,6 +33,12 @@ usort($leaderboard, static function ($a, $b) { 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 = []; foreach ($people as $p) { if (!empty($p['id'])) { @@ -60,6 +67,90 @@ usort($bankRows, static function ($a, $b) { return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? '')); }); $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 = []; foreach ($bankRows as $row) { if (!is_array($row)) { @@ -78,7 +169,6 @@ foreach ($bankRows as $row) { } $donatedByPersonThisMonth[$pid] = ($donatedByPersonThisMonth[$pid] ?? 0) + (float) ($row['amount'] ?? 0); } -$activePersonId = (string) ($activePerson['id'] ?? ''); $activeBankPerson = null; foreach ($people as $p) { if (($p['id'] ?? '') === $activePersonId) { @@ -111,22 +201,33 @@ $activeCharityDonated = is_numeric($activeBankPerson['charity_donated_total'] ??
-
Total stars leaderboard
+
+
+ Leaderboard + +
+
+
- + - - $p): ?> + + $p): ?>
# NameTotal Net