feat(ui): tag/date filters and tag chips in the meetings list (T8.3, FR-SEARCH-2)
This commit is contained in:
1 parent
eb17cc68e0
commit
bf74a3cc92
1 file changed
+67
@@ -12,6 +12,28 @@
|
||||
searchTimer = setTimeout(() => meetings.search(query), 250);
|
||||
}
|
||||
|
||||
// Tag/date filters (T8.3, FR-SEARCH-2) apply to the plain list, not
|
||||
// full-text search — changing one drops out of search mode so the
|
||||
// filtered list is immediately visible rather than hidden behind results.
|
||||
let tagFilter = $state("");
|
||||
let fromFilter = $state("");
|
||||
let toFilter = $state("");
|
||||
|
||||
function toUnix(dateStr: string): number | undefined {
|
||||
if (!dateStr) return undefined;
|
||||
return Math.floor(new Date(dateStr).getTime() / 1000);
|
||||
}
|
||||
|
||||
function onFilterChange() {
|
||||
query = "";
|
||||
meetings.search(null);
|
||||
meetings.load({
|
||||
tag: tagFilter || undefined,
|
||||
from: toUnix(fromFilter),
|
||||
to: toUnix(toFilter),
|
||||
});
|
||||
}
|
||||
|
||||
// Unified view: full-text results (with snippets) while searching, else the
|
||||
// plain list. Same underlying fields either way.
|
||||
let displayItems = $derived<(MeetingListItem | SearchHit)[]>(
|
||||
@@ -61,6 +83,16 @@
|
||||
bind:value={query}
|
||||
oninput={onSearchInput}
|
||||
/>
|
||||
<div class="filters">
|
||||
<select aria-label="Filter by tag" bind:value={tagFilter} onchange={onFilterChange}>
|
||||
<option value="">All tags</option>
|
||||
{#each meetings.allTags as t (t)}
|
||||
<option value={t}>{t}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<input type="date" aria-label="From date" bind:value={fromFilter} onchange={onFilterChange} />
|
||||
<input type="date" aria-label="To date" bind:value={toFilter} onchange={onFilterChange} />
|
||||
</div>
|
||||
{#if meetings.searching}
|
||||
<p class="muted">Searching…</p>
|
||||
{:else if displayItems.length === 0 && meetings.loading}
|
||||
@@ -91,6 +123,13 @@
|
||||
{#if snippetOf(m)}
|
||||
<span class="snippet">{snippetOf(m)}</span>
|
||||
{/if}
|
||||
{#if m.tags.length > 0}
|
||||
<span class="row tags">
|
||||
{#each m.tags as t (t)}
|
||||
<span class="chip">{t}</span>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
<div class="row actions">
|
||||
{#if m.status === "recovering"}
|
||||
@@ -138,6 +177,34 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.filters select,
|
||||
.filters input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0.25rem;
|
||||
font-size: 0.78rem;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.tags {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.chip {
|
||||
font-size: 0.68rem;
|
||||
padding: 0.05rem 0.35rem;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--muted) 15%, transparent);
|
||||
color: var(--muted);
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
Reference in new issue
Block a user