feat: client-side session search by name, date, and notes (2.4.0)

Add a search bar to the sessions list panel that filters rows in real time.
Searches display label, ISO date, month/year, and session notes. The count
badge updates to show matching/total; a no-results message appears when the
query has no matches.
This commit is contained in:
SowinskiBraeden committed 2026-06-26 21:30:22 -07:00
1 parent a5cb488b91
commit 0e34600714
3 files changed
+57 -3

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ DATA_PATH = BASE_DIR / "data" / "entries.csv"
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
ELIGIBLE_MIN_SESSIONS = 3
APP_VERSION = "2.3.1"
APP_VERSION = "2.4.0"
def load_local_env(env_path: Path) -> None:
+23
View File
@@ -2346,6 +2346,29 @@ select.control { cursor: pointer; }
}
/* Session create form — horizontal 3-col grid */
.sessions-search-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
background: var(--surface-sunk);
}
.sessions-search-bar__icon {
color: var(--faintest);
flex-shrink: 0;
}
.sessions-search-bar__input {
flex: 1;
background: none;
border: none;
outline: none;
font-size: .85rem;
color: var(--text);
font-family: inherit;
}
.sessions-search-bar__input::placeholder { color: var(--faintest); }
.session-create-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
+33 -2
View File
@@ -48,7 +48,7 @@
<h2 class="panel__title">All sessions</h2>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="panel__tag">{{ sessions|length }} total</span>
<span class="panel__tag" id="sess-count-label">{{ sessions|length }} total</span>
{% if can_manage and empty_count > 0 %}
<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() }}">
@@ -58,10 +58,21 @@
</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) %}
<div class="session-row session-row--{{ session.status }}">
<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 }}">
<div class="session-row__info">
<div class="session-date-block">
<span class="session-date-block__mon">{{ session.session_date.strftime('%b') }}</span>
@@ -152,6 +163,26 @@ document.querySelectorAll('[data-close-modal]').forEach(function(btn) {
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 %}