213 lines
12 KiB
HTML
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 %}
|