345 lines
16 KiB
HTML
345 lines
16 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ league.name }} · myboker.org{% endblock %}
|
||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||
{% block page_class %}page--app{% endblock %}
|
||
{% block content %}
|
||
|
||
{# ── Header ── #}
|
||
<div class="db-header">
|
||
<div class="db-header__info">
|
||
<div class="db-header__chips">
|
||
<span class="db-chip">{{ league.visibility }}</span>
|
||
{% if is_owner %}<span class="db-chip db-chip--accent">Owner</span>{% elif can_manage %}<span class="db-chip db-chip--accent">Manager</span>{% endif %}
|
||
</div>
|
||
<h1 class="db-header__title">{{ league.name }}</h1>
|
||
{% if league.description %}<p class="db-header__sub">{{ league.description }}</p>{% endif %}
|
||
</div>
|
||
{% if can_manage %}
|
||
<div class="db-header__actions">
|
||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.league_settings', league_ref=league.url_ref) }}">Settings</a>
|
||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">New session</a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# ── Live session banner ── #}
|
||
{% if live_session %}
|
||
<div class="db-live-banner">
|
||
<div class="db-live-banner__top">
|
||
<div class="db-live-banner__status">
|
||
<span class="db-live-dot"></span>
|
||
<span class="db-live-label">Live now</span>
|
||
<span class="db-live-name">{{ session_label(live_session) }}</span>
|
||
{% if live_session.opened_at %}
|
||
<span class="db-live-since">open since {{ live_session.opened_at[:5] }}</span>
|
||
{% endif %}
|
||
</div>
|
||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.session_detail', league_ref=league.url_ref, session_id=live_session.session_id) }}">Open session →</a>
|
||
</div>
|
||
<div class="db-live-banner__stats">
|
||
<div class="db-live-stat">
|
||
<span class="db-live-stat__label">Seated</span>
|
||
<strong class="db-live-stat__val">{{ live_session.entries | length }}</strong>
|
||
</div>
|
||
<div class="db-live-stat">
|
||
<span class="db-live-stat__label">On the table</span>
|
||
<strong class="db-live-stat__val db-live-stat__val--accent">{{ live_session.total_real_cash_in_cents | money }}</strong>
|
||
</div>
|
||
<div class="db-live-stat">
|
||
<span class="db-live-stat__label">Buy-ins</span>
|
||
<strong class="db-live-stat__val">{{ live_session.total_buy_in_cents | money }}</strong>
|
||
</div>
|
||
{% if live_session.entries %}
|
||
<div class="db-live-stat db-live-stat--players">
|
||
<span class="db-live-stat__label">At the table</span>
|
||
<span class="db-live-stat__names">{{ live_session.entries | map(attribute='player_name') | join(' · ') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# ── Overview stats strip ── #}
|
||
<div class="db-overview-strip">
|
||
<div class="db-overview-stat db-overview-stat--players">
|
||
<span class="db-overview-stat__label">Players</span>
|
||
<strong class="db-overview-stat__val">{{ counts.players }}</strong>
|
||
<span class="db-overview-stat__sub">{{ counts.players }} active</span>
|
||
</div>
|
||
<div class="db-overview-stat db-overview-stat--sessions">
|
||
<span class="db-overview-stat__label">Sessions</span>
|
||
<strong class="db-overview-stat__val">{{ total_sessions }}</strong>
|
||
{% if counts.open_sessions > 0 %}<span class="db-overview-stat__sub" style="color:var(--pos);">{{ counts.open_sessions }} live now</span>{% else %}<span class="db-overview-stat__sub">all closed</span>{% endif %}
|
||
</div>
|
||
<div class="db-overview-stat db-overview-stat--cashin">
|
||
<span class="db-overview-stat__label">Cash in</span>
|
||
<strong class="db-overview-stat__val">{{ cash_stats.total_cash_in | money }}</strong>
|
||
<span class="db-overview-stat__sub">across {{ total_sessions }} sessions</span>
|
||
</div>
|
||
<div class="db-overview-stat db-overview-stat--paidout">
|
||
<span class="db-overview-stat__label">Paid out</span>
|
||
<strong class="db-overview-stat__val">{{ cash_stats.total_paid_out | money }}</strong>
|
||
<span class="db-overview-stat__sub">to {{ counts.players }} players</span>
|
||
</div>
|
||
<div class="db-overview-stat">
|
||
<span class="db-overview-stat__label">House balance</span>
|
||
<strong class="db-overview-stat__val {{ 'num-neg' if house_balance_cents > 0 else 'num-pos' if house_balance_cents < 0 else '' }}">
|
||
{{ '+' if house_balance_cents < 0 else ('–' if house_balance_cents > 0 else '') }}{{ house_balance_cents | abs | money }}
|
||
</strong>
|
||
<span class="db-overview-stat__sub">{{ 'paid out over cash in' if house_balance_cents > 0 else 'cash in over paid out' if house_balance_cents < 0 else 'balanced' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{# ── League averages ── #}
|
||
{% if cash_stats.has_data %}
|
||
<div class="db-averages">
|
||
<p class="db-averages__title">League averages</p>
|
||
<div class="db-averages__grid">
|
||
<div class="db-avg-row">
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Avg pot / session</span>
|
||
<strong class="db-avg-val">{{ cash_stats.avg_pot | money }}</strong>
|
||
</div>
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Avg players</span>
|
||
<strong class="db-avg-val">{{ cash_stats.avg_players }}</strong>
|
||
</div>
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Avg buy-in</span>
|
||
<strong class="db-avg-val">{{ cash_stats.avg_buyin | money }}</strong>
|
||
</div>
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Biggest session</span>
|
||
<strong class="db-avg-val">{{ cash_stats.biggest_pot | money }}</strong>
|
||
</div>
|
||
{% if cash_stats.avg_sessions_per_month > 0 %}
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Sessions / month</span>
|
||
<strong class="db-avg-val">{{ cash_stats.avg_sessions_per_month }}</strong>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="db-avg-row">
|
||
{% if cash_stats.total_fronts > 0 %}
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Fronts issued</span>
|
||
<strong class="db-avg-val">{{ cash_stats.total_fronts | money }}</strong>
|
||
</div>
|
||
{% endif %}
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Ledger events</span>
|
||
<strong class="db-avg-val">{{ counts.events }}</strong>
|
||
</div>
|
||
<div class="db-avg-item">
|
||
<span class="db-avg-label">Seasons</span>
|
||
<strong class="db-avg-val">{{ counts.seasons }}</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# ── Session history heatmap ── #}
|
||
{% if cash_stats.has_sessions %}
|
||
<div class="db-cal"
|
||
id="db-cal"
|
||
data-sessions='{{ cash_stats.session_map | tojson }}'
|
||
data-first-year="{{ cash_stats.first_session_year }}">
|
||
<div class="db-cal__hdr">
|
||
<div>
|
||
<span class="kicker" style="margin:0 0 2px;display:block;">Session history</span>
|
||
<span class="db-cal__meta">{{ total_sessions }} session{{ 's' if total_sessions != 1 }} · {{ cash_stats.total_cash_in | money }} in pots</span>
|
||
</div>
|
||
<div class="db-cal__nav-group">
|
||
<button class="db-cal__nav" id="db-cal-prev" aria-label="Previous year">‹</button>
|
||
<span class="db-cal__year" id="db-cal-year"></span>
|
||
<button class="db-cal__nav" id="db-cal-next" aria-label="Next year">›</button>
|
||
</div>
|
||
</div>
|
||
<div class="db-cal__scroll"><div class="db-cal__grid" id="db-cal-grid"></div></div>
|
||
<div class="db-cal__tip" id="db-cal-tip" hidden></div>
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
var cal = document.getElementById('db-cal');
|
||
if (!cal) return;
|
||
var sessions = JSON.parse(cal.dataset.sessions);
|
||
var firstYear = parseInt(cal.dataset.firstYear) || new Date().getFullYear();
|
||
var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||
var today = new Date(); today.setHours(0,0,0,0);
|
||
var todayStr = today.toISOString().slice(0,10);
|
||
var currentYear = today.getFullYear();
|
||
var year = currentYear;
|
||
var grid = document.getElementById('db-cal-grid');
|
||
var yearLbl = document.getElementById('db-cal-year');
|
||
var prevBtn = document.getElementById('db-cal-prev');
|
||
var nextBtn = document.getElementById('db-cal-next');
|
||
var tip = document.getElementById('db-cal-tip');
|
||
|
||
function ds(d) { return d.toISOString().slice(0,10); }
|
||
|
||
function buildCalendar(y) {
|
||
var start = new Date(y, 0, 1);
|
||
var dow = start.getDay();
|
||
start.setDate(start.getDate() - (dow === 0 ? 6 : dow - 1));
|
||
var end = new Date(y, 11, 31);
|
||
var edow = end.getDay();
|
||
if (edow !== 0) end.setDate(end.getDate() + (7 - edow));
|
||
var weeks = [], cur = new Date(start);
|
||
while (cur <= end) {
|
||
var days = [], monthLabel = '';
|
||
for (var i = 0; i < 7; i++) {
|
||
var d = new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + i);
|
||
var s = ds(d);
|
||
if (d.getDate() === 1 && !monthLabel) monthLabel = MONTHS[d.getMonth()];
|
||
days.push({ date: s, count: sessions[s] || 0, future: s > todayStr, out: d.getFullYear() !== y });
|
||
}
|
||
weeks.push({ days: days, month: monthLabel });
|
||
cur.setDate(cur.getDate() + 7);
|
||
}
|
||
return weeks;
|
||
}
|
||
|
||
function render(y) {
|
||
var weeks = buildCalendar(y);
|
||
grid.innerHTML = '';
|
||
weeks.forEach(function(week) {
|
||
var mc = document.createElement('div');
|
||
mc.className = 'db-cal__mc';
|
||
mc.textContent = week.month;
|
||
grid.appendChild(mc);
|
||
week.days.forEach(function(day) {
|
||
var cell = document.createElement('div');
|
||
var cls = 'db-cal__cell';
|
||
if (day.out) cls += ' db-cal__cell--out';
|
||
else if (day.future) cls += ' db-cal__cell--dim';
|
||
else if (day.count) cls += ' db-cal__cell--on' + (day.count > 1 ? ' db-cal__cell--multi' : '');
|
||
cell.className = cls;
|
||
cell.dataset.date = day.date;
|
||
cell.dataset.count = day.count;
|
||
grid.appendChild(cell);
|
||
});
|
||
});
|
||
yearLbl.textContent = y;
|
||
prevBtn.disabled = y <= firstYear;
|
||
nextBtn.disabled = y >= currentYear;
|
||
}
|
||
|
||
function showTip(cell, e) {
|
||
var d = cell.dataset.date;
|
||
if (!d) return;
|
||
var parts = d.split('-').map(Number);
|
||
var dateObj = new Date(parts[0], parts[1] - 1, parts[2]);
|
||
var label = dateObj.toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric', year:'numeric' });
|
||
var count = parseInt(cell.dataset.count) || 0;
|
||
tip.textContent = count ? label + ' · ' + count + ' session' + (count > 1 ? 's' : '') : label;
|
||
tip.hidden = false;
|
||
moveTip(e);
|
||
}
|
||
|
||
function moveTip(e) {
|
||
var tw = tip.offsetWidth, th = tip.offsetHeight;
|
||
var x = e.clientX - tw / 2, y = e.clientY - th - 10;
|
||
x = Math.max(8, Math.min(x, window.innerWidth - tw - 8));
|
||
if (y < 8) y = e.clientY + 14;
|
||
tip.style.left = x + 'px';
|
||
tip.style.top = y + 'px';
|
||
}
|
||
|
||
grid.addEventListener('mouseover', function(e) {
|
||
var cell = e.target.closest('.db-cal__cell');
|
||
if (cell && !cell.classList.contains('db-cal__cell--out')) showTip(cell, e);
|
||
else tip.hidden = true;
|
||
});
|
||
grid.addEventListener('mousemove', function(e) { if (!tip.hidden) moveTip(e); });
|
||
grid.addEventListener('mouseleave', function() { tip.hidden = true; });
|
||
prevBtn.addEventListener('click', function() { if (year > firstYear) render(--year); });
|
||
nextBtn.addEventListener('click', function() { if (year < currentYear) render(++year); });
|
||
|
||
render(year);
|
||
})();
|
||
</script>
|
||
{% endif %}
|
||
|
||
{# ── Recent sessions + Leaderboard ── #}
|
||
<div class="db-two-col">
|
||
|
||
<div class="db-panel">
|
||
<div class="db-panel__head">
|
||
<span class="db-panel__title">Recent sessions</span>
|
||
<a class="db-panel__link" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">All {{ total_sessions }} →</a>
|
||
</div>
|
||
{% if recent_sessions %}
|
||
<div class="db-table">
|
||
{% for s in recent_sessions %}
|
||
<a class="db-session-row{% if s.is_open %} db-session-row--live{% endif %}" href="{{ url_for('leagues.session_detail', league_ref=league.url_ref, session_id=s.session_id) }}">
|
||
<span class="db-session-row__num">{{ session_label(s) }}</span>
|
||
<span class="db-session-row__meta">{{ s.entries | length }} player{{ 's' if s.entries | length != 1 }}{% if s.is_open %} · <span class="db-live-tag">live</span>{% endif %}</span>
|
||
<strong class="db-session-row__pot {{ 'db-session-row__pot--live' if s.is_open }}">{{ s.total_real_cash_in_cents | money }}</strong>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<p class="db-panel__empty">No sessions yet.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="db-panel">
|
||
<div class="db-panel__head">
|
||
<span class="db-panel__title">Leaderboard <span class="db-panel__sub">Net</span></span>
|
||
<a class="db-panel__link" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">Full board →</a>
|
||
</div>
|
||
{% if leaderboard_top %}
|
||
<div class="db-table">
|
||
{% for player in leaderboard_top %}
|
||
<div class="db-leader-row db-leader-row--{{ loop.index }}">
|
||
<span class="db-leader-rank">{{ loop.index }}</span>
|
||
<span class="db-leader-name">{{ player.player_name }}</span>
|
||
<span class="db-leader-sess">{{ player.sessions_played }} sess</span>
|
||
<strong class="db-leader-net {{ 'num-pos' if player.total_net_cents > 0 else 'num-neg' if player.total_net_cents < 0 else 'num-muted' }}">
|
||
{{ '+' if player.total_net_cents > 0 else '' }}{{ player.total_net_cents | money }}
|
||
</strong>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<p class="db-panel__empty">No data yet.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{# ── Quick nav links (flat, no card background) ── #}
|
||
<div class="db-quicknav">
|
||
|
||
<a class="db-quicknav__item" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">
|
||
<div class="db-quicknav__title">Sessions {% if counts.open_sessions > 0 %}<span class="live-badge">{{ counts.open_sessions }} live</span>{% endif %} →</div>
|
||
<div class="db-quicknav__desc">Buy-ins, cashouts, live session ledger</div>
|
||
</a>
|
||
|
||
<a class="db-quicknav__item" href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">
|
||
<div class="db-quicknav__title">Players →</div>
|
||
<div class="db-quicknav__desc">Session history, net trend</div>
|
||
</a>
|
||
|
||
<a class="db-quicknav__item" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">
|
||
<div class="db-quicknav__title">Leaderboard →</div>
|
||
<div class="db-quicknav__desc">Net profit, win rate, rank delta</div>
|
||
</a>
|
||
|
||
<a class="db-quicknav__item" href="{{ url_for('leagues.seasons', league_ref=league.url_ref) }}">
|
||
<div class="db-quicknav__title">Seasons →</div>
|
||
<div class="db-quicknav__desc">Browse by season, standings</div>
|
||
</a>
|
||
|
||
{% if can_manage %}
|
||
<a class="db-quicknav__item" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
|
||
<div class="db-quicknav__title">Ledger →</div>
|
||
<div class="db-quicknav__desc">House balance, fronts, open items</div>
|
||
</a>
|
||
{% endif %}
|
||
|
||
</div>
|
||
|
||
{% endblock %}
|