feat(ui): collapse Downloads filters into compact bar with popover

Replaces two stacked filter rows (4 fields + superseded switch) with a
single "Filters" button that opens a popover containing all controls.
Active filters surface as dismissable chips on the bar alongside a
"Clear all" button, so what's currently applied stays visible without
eating vertical space above the table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 15:10:29 -04:00
parent c5ebbe5a76
commit 0426e91a84
+101 -17
View File
@@ -68,9 +68,19 @@
</v-col>
</v-row>
<!-- Filters -->
<v-row class="mb-4">
<v-col cols="12" md="3">
<!-- Filters (compact bar with popover) -->
<div class="d-flex align-center flex-wrap ga-2 mb-4">
<v-menu :close-on-content-click="false" location="bottom start">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" variant="outlined" size="small" prepend-icon="mdi-filter-variant">
Filters
<v-chip v-if="activeFilterCount" class="ml-2" size="x-small" color="primary">
{{ activeFilterCount }}
</v-chip>
<v-icon end>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-card min-width="320" class="pa-4">
<v-select
v-model="filterStatus"
:items="statusOptions"
@@ -78,9 +88,9 @@
density="compact"
variant="outlined"
clearable
class="mb-2"
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="filterSourceId"
:items="sourceOptions"
@@ -88,9 +98,9 @@
density="compact"
variant="outlined"
clearable
class="mb-2"
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="filterFromDate"
label="From Date"
@@ -98,9 +108,9 @@
density="compact"
variant="outlined"
clearable
class="mb-2"
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="filterToDate"
label="To Date"
@@ -108,13 +118,9 @@
density="compact"
variant="outlined"
clearable
class="mb-3"
hide-details
/>
</v-col>
</v-row>
<!-- Superseded filter -->
<v-row class="mb-4">
<v-col cols="12">
<v-switch
v-model="filterExcludeSuperseded"
label="Hide superseded failures"
@@ -122,8 +128,60 @@
hide-details
color="primary"
/>
</v-col>
</v-row>
</v-card>
</v-menu>
<!-- Active filter chips -->
<v-chip
v-if="filterStatus"
size="small"
closable
@click:close="filterStatus = null"
>
Status: {{ statusLabel(filterStatus) }}
</v-chip>
<v-chip
v-if="filterSourceId"
size="small"
closable
@click:close="filterSourceId = null"
>
Source: {{ sourceLabel(filterSourceId) }}
</v-chip>
<v-chip
v-if="filterFromDate"
size="small"
closable
@click:close="filterFromDate = null"
>
From: {{ filterFromDate }}
</v-chip>
<v-chip
v-if="filterToDate"
size="small"
closable
@click:close="filterToDate = null"
>
To: {{ filterToDate }}
</v-chip>
<v-chip
v-if="!filterExcludeSuperseded"
size="small"
closable
@click:close="filterExcludeSuperseded = true"
>
Showing superseded
</v-chip>
<v-btn
v-if="activeFilterCount"
size="small"
variant="text"
@click="clearAllFilters"
>
Clear all
</v-btn>
</div>
<!-- Downloads Table -->
<v-card>
@@ -334,6 +392,32 @@ const sourceOptions = computed(() =>
}))
)
const activeFilterCount = computed(() => {
let n = 0
if (filterStatus.value) n++
if (filterSourceId.value) n++
if (filterFromDate.value) n++
if (filterToDate.value) n++
if (!filterExcludeSuperseded.value) n++
return n
})
function statusLabel(value) {
return statusOptions.find(s => s.value === value)?.title || value
}
function sourceLabel(id) {
return sourceOptions.value.find(s => s.value === id)?.title || `#${id}`
}
function clearAllFilters() {
filterStatus.value = null
filterSourceId.value = null
filterFromDate.value = null
filterToDate.value = null
filterExcludeSuperseded.value = true
}
const headers = [
{ title: 'Status', key: 'status', width: 120 },
{ title: 'Source', key: 'source', width: 180 },