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:
1 parent
a5cb488b91
commit
0e34600714
3 files changed
+57
-3
No files matched your search
@@ -9,7 +9,7 @@ DATA_PATH = BASE_DIR / "data" / "entries.csv"
|
|||||||
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
|
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
|
||||||
|
|
||||||
ELIGIBLE_MIN_SESSIONS = 3
|
ELIGIBLE_MIN_SESSIONS = 3
|
||||||
APP_VERSION = "2.3.1"
|
APP_VERSION = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
def load_local_env(env_path: Path) -> None:
|
def load_local_env(env_path: Path) -> None:
|
||||||
|
|||||||
@@ -2346,6 +2346,29 @@ select.control { cursor: pointer; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Session create form — horizontal 3-col grid */
|
/* 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 {
|
.session-create-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<h2 class="panel__title">All sessions</h2>
|
<h2 class="panel__title">All sessions</h2>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;align-items:center;gap:8px;">
|
<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 %}
|
{% 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;">
|
<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() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
@@ -58,10 +58,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if sessions %}
|
{% 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>
|
<div>
|
||||||
{% for session in sessions %}
|
{% for session in sessions %}
|
||||||
{% set sm = db_id_to_summary.get(session.id) %}
|
{% 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-row__info">
|
||||||
<div class="session-date-block">
|
<div class="session-date-block">
|
||||||
<span class="session-date-block__mon">{{ session.session_date.strftime('%b') }}</span>
|
<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) {
|
reopenModal && reopenModal.addEventListener('click', function(e) {
|
||||||
if (e.target === this) this.setAttribute('hidden', '');
|
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>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in new issue
Block a user