actually add seasons
This commit is contained in:
1 parent
cae60c9305
commit
48ad7fe1a6
11 files changed
+700
-10
No files matched your search
@@ -51,6 +51,15 @@
|
||||
<span>Leaderboard</span>
|
||||
</a>
|
||||
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.seasons' }}"
|
||||
href="{{ url_for('leagues.seasons', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.4"/>
|
||||
<path d="M8 4v4l3 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span>Seasons</span>
|
||||
</a>
|
||||
|
||||
{% if can_manage is defined and can_manage %}
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.ledger' }}"
|
||||
href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
|
||||
|
||||
@@ -307,6 +307,22 @@
|
||||
<span class="db-nav-tile__arrow">→</span>
|
||||
</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>
|
||||
|
||||
{% 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">
|
||||
|
||||
@@ -64,8 +64,20 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="toolbar__right">
|
||||
{% if seasons %}
|
||||
<form class="session-filter-form" method="get" style="margin-right:8px;">
|
||||
<input type="hidden" name="mode" value="{{ mode }}">
|
||||
<select class="session-filter-select" name="season" onchange="this.form.submit()">
|
||||
<option value="">All seasons</option>
|
||||
{% for s in seasons %}
|
||||
<option value="{{ s.id }}" {{ 'selected' if selected_season_id == s.id else '' }}>{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form class="session-filter-form" method="get">
|
||||
<input type="hidden" name="mode" value="{{ mode }}">
|
||||
{% if selected_season_id %}<input type="hidden" name="season" value="{{ selected_season_id }}">{% endif %}
|
||||
<select class="session-filter-select" name="through_session" onchange="this.form.submit()">
|
||||
<option value="">Latest session</option>
|
||||
{% for s in available_sessions %}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Seasons · {{ 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">Seasons</h1>
|
||||
<p class="page-header__sub">Organise sessions into named seasons for {{ league.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
|
||||
{% if can_manage %}
|
||||
{% set has_dated_seasons = active_seasons | selectattr('start_date') | list | length > 0 %}
|
||||
{% if has_dated_seasons %}
|
||||
<div class="panel" style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;">
|
||||
<div>
|
||||
<p class="panel__title" style="margin:0;">Auto-assign sessions</p>
|
||||
<p class="muted-text" style="margin:4px 0 0;font-size:13px;">Assign unassigned sessions to seasons based on their date. Sessions that match zero or multiple seasons are skipped.</p>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('leagues.auto_assign_seasons', league_ref=league.url_ref) }}" style="margin:0;flex-shrink:0;margin-left:20px;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--ghost btn--sm" type="submit">Auto-assign</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="panel form-card" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div>
|
||||
<p class="eyebrow">Seasons</p>
|
||||
<h2 class="panel__title" style="margin-top:4px;">New season</h2>
|
||||
</div>
|
||||
<div class="session-create-row">
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input type="text" name="name" value="{{ form.name or '' }}" placeholder="e.g. Season 1" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Start date <span class="form-opt-hint">(optional)</span></span>
|
||||
<input type="date" name="start_date" value="{{ form.start_date or '' }}">
|
||||
</label>
|
||||
<label>
|
||||
<span>End date <span class="form-opt-hint">(optional)</span></span>
|
||||
<input type="date" name="end_date" value="{{ form.end_date or '' }}">
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn--primary" type="submit">Create season</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}{# can_manage #}
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel__head">
|
||||
<div>
|
||||
<p class="kicker" style="margin:0;">Active</p>
|
||||
<h2 class="panel__title">Current seasons</h2>
|
||||
</div>
|
||||
<span class="panel__tag">{{ active_seasons|length }} season{{ 's' if active_seasons|length != 1 else '' }}</span>
|
||||
</div>
|
||||
{% if active_seasons %}
|
||||
<div>
|
||||
{% for season in active_seasons %}
|
||||
<div class="session-row session-row--closed">
|
||||
<div class="session-row__info">
|
||||
<div class="session-row__text">
|
||||
<div class="session-row__title">
|
||||
<a href="{{ url_for('leagues.sessions', league_ref=league.url_ref, season=season.id) }}">{{ season.name }}</a>
|
||||
</div>
|
||||
<div class="session-row__meta">
|
||||
{% if season.start_date %}
|
||||
<span>{{ season.start_date.strftime('%b %-d, %Y') }}</span>
|
||||
{% endif %}
|
||||
{% if season.start_date and season.end_date %}
|
||||
<span>—</span>
|
||||
{% endif %}
|
||||
{% if season.end_date %}
|
||||
<span>{{ season.end_date.strftime('%b %-d, %Y') }}</span>
|
||||
{% endif %}
|
||||
{% if not season.start_date and not season.end_date %}
|
||||
<span class="muted-text">No dates set</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if can_manage %}
|
||||
<div class="session-row__actions">
|
||||
<button class="btn btn--ghost btn--sm" type="button"
|
||||
data-modal="modal-edit-season"
|
||||
data-season-id="{{ season.id }}"
|
||||
data-season-name="{{ season.name }}"
|
||||
data-season-start="{{ season.start_date.isoformat() if season.start_date else '' }}"
|
||||
data-season-end="{{ season.end_date.isoformat() if season.end_date else '' }}">Edit</button>
|
||||
<form method="post" action="{{ url_for('leagues.archive_season', league_ref=league.url_ref, season_id=season.id) }}" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--ghost btn--sm" type="submit">Archive</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-panel">
|
||||
<p class="muted-text">No active seasons. Create one above.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if archived_seasons %}
|
||||
<div class="panel">
|
||||
<div class="panel__head">
|
||||
<div>
|
||||
<p class="kicker" style="margin:0;">Archived</p>
|
||||
<h2 class="panel__title">Past seasons</h2>
|
||||
</div>
|
||||
<span class="panel__tag">{{ archived_seasons|length }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{% for season in archived_seasons %}
|
||||
<div class="session-row session-row--closed" style="opacity:.7;">
|
||||
<div class="session-row__info">
|
||||
<div class="session-row__text">
|
||||
<div class="session-row__title">{{ season.name }}</div>
|
||||
<div class="session-row__meta">
|
||||
{% if season.start_date %}
|
||||
<span>{{ season.start_date.strftime('%b %-d, %Y') }}</span>
|
||||
{% endif %}
|
||||
{% if season.start_date and season.end_date %}
|
||||
<span>—</span>
|
||||
{% endif %}
|
||||
{% if season.end_date %}
|
||||
<span>{{ season.end_date.strftime('%b %-d, %Y') }}</span>
|
||||
{% endif %}
|
||||
{% if not season.start_date and not season.end_date %}
|
||||
<span class="muted-text">No dates set</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if can_manage %}
|
||||
<div class="session-row__actions">
|
||||
<form method="post" action="{{ url_for('leagues.unarchive_season', league_ref=league.url_ref, season_id=season.id) }}" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--ghost btn--sm" type="submit">Restore</button>
|
||||
</form>
|
||||
<button class="btn btn--ghost btn--sm" type="button"
|
||||
data-modal="modal-delete-season"
|
||||
data-season-id="{{ season.id }}"
|
||||
data-season-name="{{ season.name }}">Delete</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if can_manage %}
|
||||
<!-- Edit season modal -->
|
||||
<div class="modal-backdrop" id="modal-edit-season" hidden>
|
||||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modal-edit-season-title">
|
||||
<div class="modal-card__head">
|
||||
<h2 class="modal-card__title" id="modal-edit-season-title">Edit season</h2>
|
||||
<button class="modal-card__close" type="button" data-close-modal aria-label="Close">×</button>
|
||||
</div>
|
||||
<form method="post" id="form-edit-season" action="">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-card__body">
|
||||
<div class="form-card" style="padding:0;gap:14px;">
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input type="text" name="name" id="edit-season-name" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Start date <span class="form-opt-hint">(optional)</span></span>
|
||||
<input type="date" name="start_date" id="edit-season-start">
|
||||
</label>
|
||||
<label>
|
||||
<span>End date <span class="form-opt-hint">(optional)</span></span>
|
||||
<input type="date" name="end_date" id="edit-season-end">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-card__foot">
|
||||
<button class="btn btn--primary" type="submit">Save changes</button>
|
||||
<button class="btn btn--ghost" type="button" data-close-modal>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete season confirmation modal -->
|
||||
<div class="modal-backdrop" id="modal-delete-season" hidden>
|
||||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modal-delete-season-title">
|
||||
<div class="modal-card__head">
|
||||
<h2 class="modal-card__title" id="modal-delete-season-title">Delete season</h2>
|
||||
<button class="modal-card__close" type="button" data-close-modal aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-card__body">
|
||||
<p>Permanently delete <strong id="delete-season-name"></strong>? Sessions assigned to this season will not be deleted — they will simply become unassigned. This cannot be undone.</p>
|
||||
</div>
|
||||
<form method="post" id="form-delete-season" action="">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-card__foot">
|
||||
<button class="btn btn--destructive" type="submit">Delete season</button>
|
||||
<button class="btn btn--ghost" type="button" data-close-modal>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const leagueRef = {{ league.url_ref | tojson }};
|
||||
|
||||
function openModal(id) {
|
||||
document.getElementById(id).hidden = false;
|
||||
}
|
||||
function closeModal(el) {
|
||||
el.closest('.modal-backdrop').hidden = true;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.matches('[data-close-modal]') || e.target.closest('[data-close-modal]')) {
|
||||
closeModal(e.target.closest('[data-close-modal]') || e.target);
|
||||
}
|
||||
if (e.target.matches('.modal-backdrop')) {
|
||||
e.target.hidden = true;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-modal="modal-edit-season"]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const id = btn.dataset.seasonId;
|
||||
document.getElementById('edit-season-name').value = btn.dataset.seasonName;
|
||||
document.getElementById('edit-season-start').value = btn.dataset.seasonStart || '';
|
||||
document.getElementById('edit-season-end').value = btn.dataset.seasonEnd || '';
|
||||
document.getElementById('form-edit-season').action = '/l/' + leagueRef + '/seasons/' + id + '/update';
|
||||
openModal('modal-edit-season');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-modal="modal-delete-season"]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const id = btn.dataset.seasonId;
|
||||
document.getElementById('delete-season-name').textContent = btn.dataset.seasonName;
|
||||
document.getElementById('form-delete-season').action = '/l/' + leagueRef + '/seasons/' + id + '/delete';
|
||||
openModal('modal-delete-season');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endif %}{# can_manage #}
|
||||
|
||||
{% endblock %}
|
||||
@@ -271,6 +271,17 @@
|
||||
<span>Date</span>
|
||||
<input type="date" name="session_date" value="{{ session_model.session_date.isoformat() }}" required>
|
||||
</label>
|
||||
{% if seasons %}
|
||||
<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 session_model.season_id == s.id else '' }}>{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
<label>
|
||||
<span>Label <span class="form-opt-hint">(optional)</span></span>
|
||||
<input type="text" name="label" value="{{ session_model.label or '' }}" placeholder="e.g. Main table">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="hero" style="padding-top:0;">
|
||||
<p class="hero__kicker session-number">Session #{{ "%03d"|format(session_number) }}</p>
|
||||
<h1 class="hero__title">{{ session_model.display_label }}</h1>
|
||||
<p class="hero__sub">{{ session.entries|length }} player{{ 's' if session.entries|length != 1 else '' }} · <span class="status-badge status-{{ session_model.status }}">{{ session_model.status }}</span></p>
|
||||
<p class="hero__sub">{{ session.entries|length }} player{{ 's' if session.entries|length != 1 else '' }} · <span class="status-badge status-{{ session_model.status }}">{{ session_model.status }}</span>{% if season %} · <a class="season-tag" href="{{ url_for('leagues.sessions', league_ref=league.url_ref, season=session_model.season_id) }}">{{ season.name }}</a>{% endif %}</p>
|
||||
</div>
|
||||
|
||||
<!-- Recon cards -->
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
<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">
|
||||
@@ -48,8 +57,19 @@
|
||||
<h2 class="panel__title">All sessions</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<span class="panel__tag" id="sess-count-label">{{ sessions|length }} total</span>
|
||||
{% if can_manage and empty_count > 0 %}
|
||||
{% 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>
|
||||
@@ -71,8 +91,9 @@
|
||||
<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 '')) | lower }}">
|
||||
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>
|
||||
@@ -84,6 +105,9 @@
|
||||
</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>
|
||||
|
||||
Reference in new issue
Block a user