Adds filters and sorts for Hunt view
This commit is contained in:
@@ -173,6 +173,46 @@
|
||||
const approved = $derived(Object.values(itemStatuses).filter(s => s.itemFoundStatus === 'APPROVED').length)
|
||||
const submitted = $derived(Object.values(itemStatuses).filter(s => s.itemFoundStatus === 'SUBMITTED').length)
|
||||
const rejected = $derived(Object.values(itemStatuses).filter(s => s.itemFoundStatus === 'REJECTED').length)
|
||||
|
||||
let searchQuery = $state('')
|
||||
let filterStatus = $state<string | null>(null)
|
||||
let filterPoints = $state<number | null>(null)
|
||||
let sortBy = $state<'name' | 'points'>('points')
|
||||
let sortDir = $state<'asc' | 'desc'>('asc')
|
||||
|
||||
const uniquePointValues = $derived([...new Set(items.map(i => i.points))].sort((a, b) => a - b))
|
||||
|
||||
const filteredItems = $derived(
|
||||
[...items]
|
||||
.filter(item => {
|
||||
if (searchQuery && !item.name.toLowerCase().includes(searchQuery.toLowerCase())) return false
|
||||
if (filterStatus && (itemStatuses[item.id]?.itemFoundStatus ?? 'NOT_FOUND') !== filterStatus) return false
|
||||
if (filterPoints !== null && item.points !== filterPoints) return false
|
||||
return true
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (sortBy === 'name') {
|
||||
const cmp = a.name.localeCompare(b.name)
|
||||
return sortDir === 'asc' ? cmp : -cmp
|
||||
}
|
||||
const ptsCmp = a.points - b.points
|
||||
if (ptsCmp !== 0) return sortDir === 'asc' ? ptsCmp : -ptsCmp
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
)
|
||||
|
||||
function toggleSort(field: 'name' | 'points') {
|
||||
if (sortBy === field) sortDir = sortDir === 'asc' ? 'desc' : 'asc'
|
||||
else { sortBy = field; sortDir = 'asc' }
|
||||
}
|
||||
|
||||
const statusFilters: { label: string; value: string | null }[] = [
|
||||
{ label: 'All', value: null },
|
||||
{ label: 'Not Found', value: 'NOT_FOUND' },
|
||||
{ label: 'Submitted', value: 'SUBMITTED' },
|
||||
{ label: 'Approved', value: 'APPROVED' },
|
||||
{ label: 'Rejected', value: 'REJECTED' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="pb-20">
|
||||
@@ -248,8 +288,55 @@
|
||||
|
||||
<!-- On a team: item list -->
|
||||
{:else}
|
||||
<!-- Filter & sort controls -->
|
||||
<div class="px-4 pt-3 pb-2 flex flex-col gap-2 border-b border-base-200">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search items…"
|
||||
class="input input-bordered input-sm w-full"
|
||||
bind:value={searchQuery}
|
||||
/>
|
||||
<div class="flex gap-2 overflow-x-auto pb-1 no-scrollbar">
|
||||
{#each statusFilters as f}
|
||||
<button
|
||||
class="btn btn-xs shrink-0"
|
||||
class:btn-primary={filterStatus === f.value}
|
||||
class:btn-outline={filterStatus !== f.value}
|
||||
onclick={() => filterStatus = f.value}
|
||||
>{f.label}</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<select
|
||||
class="select select-bordered select-sm flex-1"
|
||||
onchange={(e) => filterPoints = (e.target as HTMLSelectElement).value === '' ? null : Number((e.target as HTMLSelectElement).value)}
|
||||
>
|
||||
<option value="">All pts</option>
|
||||
{#each uniquePointValues as pts}
|
||||
<option value={pts}>{pts} pts</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button
|
||||
class="btn btn-sm gap-1"
|
||||
class:btn-primary={sortBy === 'points'}
|
||||
class:btn-outline={sortBy !== 'points'}
|
||||
onclick={() => toggleSort('points')}
|
||||
>
|
||||
Pts {sortBy === 'points' ? (sortDir === 'asc' ? '↑' : '↓') : '↕'}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm gap-1"
|
||||
class:btn-primary={sortBy === 'name'}
|
||||
class:btn-outline={sortBy !== 'name'}
|
||||
onclick={() => toggleSort('name')}
|
||||
>
|
||||
Name {sortBy === 'name' ? (sortDir === 'asc' ? '↑' : '↓') : '↕'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
{#each items as item}
|
||||
{#each filteredItems as item}
|
||||
{@const status = itemStatuses[item.id]}
|
||||
<button
|
||||
class="card bg-base-100 shadow-sm border border-base-200 text-left w-full hover:border-primary transition-colors"
|
||||
@@ -265,6 +352,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{:else}
|
||||
<p class="text-center text-base-content/50 py-8 text-sm">No items match your filters</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user