Files
boker/templates/league_sessions.html
2026-06-27 14:14:57 -07:00

213 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Sessions · {{ league.name }} · myboker.org{% endblock %}
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
{% block page_class %}page--app{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1 class="page-header__title">Sessions</h1>
<p class="page-header__sub">All game sessions for {{ league.name }}</p>
</div>
</div>
<div class="stack">
{% if can_manage %}
<form class="panel form-card" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<p class="eyebrow">Add session</p>
<h2 class="panel__title" style="margin-top:4px;">New session</h2>
</div>
<input type="hidden" name="status" value="open">
<div class="session-create-row">
<label>
<span>Date <span class="info-tip" tabindex="0"><svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M6.5 6v3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="6.5" cy="4" r=".7" fill="currentColor"/></svg><span class="info-tip__bubble">The night the game was played. Same-day sessions automatically become S1, S2, etc.</span></span></span>
<input type="date" name="session_date" value="{{ form.session_date }}" required>
</label>
<label>
<span>Season <span class="form-opt-hint">(optional)</span></span>
<select name="season_id">
<option value="">No season</option>
{% for s in seasons %}
<option value="{{ s.id }}" {{ 'selected' if form.season_id == s.id else '' }}>{{ s.name }}</option>
{% endfor %}
</select>
</label>
<label>
<span>Label <span class="form-opt-hint">(optional)</span> <span class="info-tip" tabindex="0"><svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M6.5 6v3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="6.5" cy="4" r=".7" fill="currentColor"/></svg><span class="info-tip__bubble">A short name for this session — 'Main table', 'Side game', etc. Shown in the session list alongside the date.</span></span></span>
<input type="text" name="label" value="{{ form.label or '' }}" placeholder="e.g. Main table">
</label>
<label>
<span>Notes <span class="form-opt-hint">(optional)</span> <span class="info-tip" tabindex="0"><svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M6.5 6v3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="6.5" cy="4" r=".7" fill="currentColor"/></svg><span class="info-tip__bubble">Optional context visible in the session list — venue, occasion, anything worth noting.</span></span></span>
<input type="text" name="notes" value="{{ form.notes or '' }}" placeholder="Optional context">
</label>
</div>
<div>
<button class="btn btn--primary" type="submit">Create session</button>
</div>
</form>
{% endif %}
<div class="panel">
<div class="panel__head">
<div>
<p class="kicker" style="margin:0;">History</p>
<h2 class="panel__title">All sessions</h2>
</div>
<div style="display:flex;align-items:center;gap:8px;">
{% if seasons %}
<select class="session-filter-select" onchange="if(this.value) window.location=this.value;">
<option value="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}" {{ 'selected' if not selected_season else '' }}>All seasons</option>
{% for s in seasons %}
<option value="{{ url_for('leagues.sessions', league_ref=league.url_ref, season=s.id) }}" {{ 'selected' if selected_season_id == s.id else '' }}>{{ s.name }}</option>
{% endfor %}
</select>
{% if selected_season %}
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">Clear</a>
{% endif %}
{% endif %}
<span class="panel__tag" id="sess-count-label">{{ sessions|length }}{% if selected_season %} / {{ all_session_count }}{% endif %} total</span>
{% if can_manage and empty_count > 0 and not selected_season %}
<form method="post" action="{{ url_for('leagues.prune_empty_sessions', league_ref=league.url_ref) }}" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--ghost btn--sm" type="submit">Prune empty ({{ empty_count }})</button>
</form>
{% endif %}
</div>
</div>
{% if sessions %}
<div class="sessions-search-bar">
<svg class="sessions-search-bar__icon" width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="6.5" cy="6.5" r="5" stroke="currentColor" stroke-width="1.5"/>
<path d="M10.5 10.5L14 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<input class="sessions-search-bar__input" id="sess-search" type="search" placeholder="Search by name, date, or notes…" autocomplete="off">
</div>
<div id="sess-empty-search" class="empty-panel" style="display:none;">
<p class="muted-text">No sessions match your search.</p>
</div>
<div>
{% for session in sessions %}
{% set sm = db_id_to_summary.get(session.id) %}
{% set season_name = season_map[session.season_id].name if session.season_id and season_map.get(session.season_id) else '' %}
<div class="session-row session-row--{{ session.status }}"
data-search="{{ (session.display_label ~ ' ' ~ session.session_date.isoformat() ~ ' ' ~ session.session_date.strftime('%B %Y') ~ ' ' ~ (session.notes or '') ~ ' ' ~ season_name) | lower }}">
<div class="session-row__info">
<div class="session-date-block">
<span class="session-date-block__mon">{{ session.session_date.strftime('%b') }}</span>
<span class="session-date-block__day">{{ session.session_date.strftime('%-d') }}</span>
</div>
<div class="session-row__text">
<div class="session-row__title">
<a href="{{ url_for('leagues.session_public_view', league_ref=league.url_ref, session_id=session.id) }}">{{ session.display_label }}</a>
</div>
<div class="session-row__meta">
<span>S{{ session.sequence_on_date }}</span>
{% if session.season_id and season_map.get(session.season_id) %}
<span class="season-tag">{{ season_map[session.season_id].name }}</span>
{% endif %}
{% if sm and sm.entries %}
<span>· {{ sm.entries|length }} player{{ 's' if sm.entries|length != 1 else '' }}</span>
<span>· {{ sm.total_invested_cents | money }} pot</span>
{% if sm.total_current_due_to_house_cents > 0 or sm.total_current_due_to_player_cents > 0 %}
<span style="color:var(--warn);">· {{ (sm.total_current_due_to_house_cents + sm.total_current_due_to_player_cents) | money }} open</span>
{% endif %}
{% endif %}
{% if session.notes %}<span>· {{ session.notes }}</span>{% endif %}
</div>
</div>
</div>
<div class="session-row__actions">
<span class="status-pill status-pill--{{ session.status }}">{{ session.status }}</span>
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.session_public_view', league_ref=league.url_ref, session_id=session.id) }}">View</a>
{% if can_manage %}
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.session_detail', league_ref=league.url_ref, session_id=session.id) }}">Manage</a>
{% if session.status == 'open' %}
<form method="post" action="{{ url_for('leagues.close_league_session', league_ref=league.url_ref, session_id=session.id) }}" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--ghost btn--sm" type="submit">Close</button>
</form>
{% else %}
<button class="btn btn--outline btn--sm" type="button"
data-modal="modal-reopen-session"
data-reopen-action="{{ url_for('leagues.open_league_session', league_ref=league.url_ref, session_id=session.id) }}">Reopen</button>
{% endif %}
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-panel">
<h3 class="panel__title">No sessions yet</h3>
<p class="muted-text">Create the first session above. Same-day sessions will automatically become S1, S2, and so on.</p>
</div>
{% endif %}
</div>
</div>
<!-- Reopen confirmation modal -->
<div class="modal-backdrop" id="modal-reopen-session" hidden>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modal-reopen-title">
<div class="modal-card__head">
<span class="kicker" style="margin:0;">Settled session</span>
<h2 class="panel__title" id="modal-reopen-title">Reopen this session?</h2>
</div>
<p class="modal-card__text">This session has been closed and books are assumed to be settled. Reopening will allow new ledger events to be appended and may affect settlement totals.</p>
<div class="modal-card__actions">
<button class="btn btn--ghost btn--sm" type="button" data-close-modal>Cancel</button>
<form id="reopenForm" method="post" action="" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--primary btn--sm" type="submit">Reopen session</button>
</form>
</div>
</div>
</div>
<script>
var reopenModal = document.getElementById('modal-reopen-session');
var reopenForm = document.getElementById('reopenForm');
document.querySelectorAll('[data-modal="modal-reopen-session"]').forEach(function(btn) {
btn.addEventListener('click', function() {
if (reopenForm && this.dataset.reopenAction) {
reopenForm.action = this.dataset.reopenAction;
}
if (reopenModal) reopenModal.removeAttribute('hidden');
});
});
document.querySelectorAll('[data-close-modal]').forEach(function(btn) {
btn.addEventListener('click', function() {
this.closest('.modal-backdrop').setAttribute('hidden', '');
});
});
reopenModal && reopenModal.addEventListener('click', function(e) {
if (e.target === this) this.setAttribute('hidden', '');
});
(function () {
var input = document.getElementById('sess-search');
var countLabel = document.getElementById('sess-count-label');
var emptyMsg = document.getElementById('sess-empty-search');
if (!input) return;
var total = {{ sessions|length }};
input.addEventListener('input', function () {
var q = this.value.toLowerCase().trim();
var rows = document.querySelectorAll('.session-row[data-search]');
var visible = 0;
rows.forEach(function (row) {
var match = !q || row.dataset.search.includes(q);
row.style.display = match ? '' : 'none';
if (match) visible++;
});
if (countLabel) countLabel.textContent = q ? visible + ' of ' + total : total + ' total';
if (emptyMsg) emptyMsg.style.display = (q && visible === 0) ? '' : 'none';
});
})();
</script>
{% endblock %}