fix player win % and visual overhaul

This commit is contained in:
SowinskiBraeden committed 2026-09-05 19:19:46 -07:00
1 parent 1b9f9510ec
commit 5c09266d75
16 files changed
+3123 -1490

No files matched your search

+174 -176
View File
@@ -4,16 +4,17 @@
{% 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>{% endif %}
{% 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 is_owner %}
{% 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>
@@ -21,131 +22,141 @@
{% endif %}
</div>
{% if counts.open_sessions > 0 %}
<div class="db-live-alert">
<div class="db-live-alert__pulse"></div>
<span class="db-live-alert__text">
{{ counts.open_sessions }} session{{ 's' if counts.open_sessions != 1 else '' }} in progress
</span>
<a class="db-live-alert__link" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">View sessions →</a>
{# ── 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 %}
<div class="db-stat-grid">
<div class="db-stat">
<div class="db-stat__icon db-stat__icon--accent">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<circle cx="8" cy="7" r="3" stroke="currentColor" stroke-width="1.5"/>
<path d="M2 17c0-3.314 2.686-6 6-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="14" cy="8" r="2.5" stroke="currentColor" stroke-width="1.5"/>
<path d="M18 17c0-2.761-1.79-5-4-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div>
<span class="db-stat__num">{{ counts.players }}</span>
<span class="db-stat__label">Players</span>
</div>
{# ── 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-stat">
<div class="db-stat__icon db-stat__icon--pos">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<rect x="2.5" y="3.5" width="15" height="13" rx="2" stroke="currentColor" stroke-width="1.5"/>
<path d="M6 2v3M14 2v3M2.5 8h15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div>
<span class="db-stat__num">{{ counts.sessions }}</span>
<span class="db-stat__label">Sessions{% if counts.open_sessions > 0 %} · <span style="color:var(--pos);">{{ counts.open_sessions }} live</span>{% endif %}</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-stat">
<div class="db-stat__icon db-stat__icon--warn">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M5 3h10l3 3v11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M7 8h6M7 11h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div>
<span class="db-stat__num">{{ counts.events }}</span>
<span class="db-stat__label">Ledger events</span>
</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-stat">
<div class="db-stat__icon">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M10 2v3M10 15v3M2 10h3M15 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="10" cy="10" r="4.5" stroke="currentColor" stroke-width="1.5"/>
</svg>
</div>
<div>
<span class="db-stat__num">{{ counts.seasons }}</span>
<span class="db-stat__label">Seasons</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-cash">
<div class="db-cash__stat">
<span class="db-cash__label">Total cash in</span>
<span class="db-cash__val">{{ cash_stats.total_cash_in | money }}</span>
<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 class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Total paid out</span>
<span class="db-cash__val">{{ cash_stats.total_paid_out | money }}</span>
</div>
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Avg pot / session</span>
<span class="db-cash__val">{{ cash_stats.avg_pot | money }}</span>
</div>
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Avg players</span>
<span class="db-cash__val">{{ cash_stats.avg_players }}</span>
</div>
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Avg buy-in</span>
<span class="db-cash__val">{{ cash_stats.avg_buyin | money }}</span>
</div>
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Biggest session</span>
<span class="db-cash__val">{{ cash_stats.biggest_pot | money }}</span>
</div>
{% if cash_stats.avg_sessions_per_month > 0 %}
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Sessions / month</span>
<span class="db-cash__val">{{ cash_stats.avg_sessions_per_month }}</span>
</div>
{% endif %}
{% if cash_stats.total_fronts > 0 %}
<div class="db-cash__div"></div>
<div class="db-cash__stat">
<span class="db-cash__label">Fronts issued</span>
<span class="db-cash__val">{{ cash_stats.total_fronts | money }}</span>
</div>
{% endif %}
</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">
<span class="db-cal__title">Session history</span>
<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">&#8249;</button>
<span class="db-cal__year" id="db-cal-year"></span>
<button class="db-cal__nav" id="db-cal-next" aria-label="Next year">&#8250;</button>
</div>
</div>
<div class="db-cal__grid" id="db-cal-grid"></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>
@@ -250,94 +261,81 @@
</script>
{% endif %}
<div class="db-nav-grid">
{# ── Recent sessions + Leaderboard ── #}
<div class="db-two-col">
<a class="db-nav-tile db-nav-tile--sessions {% if counts.open_sessions > 0 %}db-nav-tile--live{% endif %}" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">
<div class="db-nav-tile__icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<rect x="3" y="4" width="18" height="16" rx="2.5" stroke="currentColor" stroke-width="1.6"/>
<path d="M3 9h18M8 3v2M16 3v2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="8" cy="14" r="1.5" fill="currentColor"/>
<circle cx="12" cy="14" r="1.5" fill="currentColor"/>
<circle cx="16" cy="14" r="1.5" fill="currentColor"/>
</svg>
<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>
<div class="db-nav-tile__body">
<div class="db-nav-tile__title-row">
<strong class="db-nav-tile__title">Sessions</strong>
{% if counts.open_sessions > 0 %}
<span class="live-badge">{{ counts.open_sessions }} live</span>
{% endif %}
{% 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>
<p class="db-nav-tile__desc">Open a session to start tracking buy-ins and cashouts in real time. Close it when everyone is done.</p>
{% endfor %}
</div>
<span class="db-nav-tile__arrow"></span>
{% 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-nav-tile db-nav-tile--leaderboard" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">
<div class="db-nav-tile__icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M3 20h18M6 20V13M10 20V9M14 20V11M18 20V5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</div>
<div class="db-nav-tile__body">
<div class="db-nav-tile__title-row">
<strong class="db-nav-tile__title">Leaderboard</strong>
</div>
<p class="db-nav-tile__desc">Net profit, ROI, win rate, and rank delta. Filter by eligible players or switch to recent form.</p>
</div>
<span class="db-nav-tile__arrow"></span>
<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-nav-tile db-nav-tile--players" href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">
<div class="db-nav-tile__icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="9" cy="8" r="3.5" stroke="currentColor" stroke-width="1.6"/>
<path d="M2.5 21c0-3.866 2.91-7 6.5-7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="16.5" cy="9.5" r="3" stroke="currentColor" stroke-width="1.6"/>
<path d="M21.5 21c0-3.314-2.239-6-5-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</div>
<div class="db-nav-tile__body">
<div class="db-nav-tile__title-row">
<strong class="db-nav-tile__title">Players</strong>
</div>
<p class="db-nav-tile__desc">View each player's full session history, net trend, and individual stats over time.</p>
</div>
<span class="db-nav-tile__arrow"></span>
<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-nav-tile db-nav-tile--seasons" href="{{ url_for('leagues.seasons', league_ref=league.url_ref) }}">
<div class="db-nav-tile__icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.6"/>
<path d="M12 7v5l3.5 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="db-nav-tile__body">
<div class="db-nav-tile__title-row">
<strong class="db-nav-tile__title">Seasons</strong>
</div>
<p class="db-nav-tile__desc">Browse sessions by season, view season standings, and manage date ranges.</p>
</div>
<span class="db-nav-tile__arrow"></span>
<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-nav-tile db-nav-tile--ledger" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
<div class="db-nav-tile__icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M5 3h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H9l-4-4V4a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.6"/>
<path d="M9 19v-4H5M8 9h8M8 13h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</div>
<div class="db-nav-tile__body">
<div class="db-nav-tile__title-row">
<strong class="db-nav-tile__title">League ledger</strong>
</div>
<p class="db-nav-tile__desc">Track what the house holds, what it owes, and outstanding fronts across all sessions.</p>
</div>
<span class="db-nav-tile__arrow"></span>
<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 %}