feat: add award category filter and remove sort dropdown
- Add category filter dropdown to awards list page - Filters awards by category (dxcc, darc, was, etc.) - Remove unused "Sort by" dropdown from award detail page - Categories auto-extracted from award definitions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { auth } from '$lib/stores.js';
|
||||
|
||||
let allAwards = [];
|
||||
let awards = [];
|
||||
let categories = [];
|
||||
let selectedCategory = 'all';
|
||||
let loading = true;
|
||||
let error = null;
|
||||
|
||||
@@ -33,7 +36,7 @@
|
||||
}
|
||||
|
||||
// Load progress for each award
|
||||
awards = await Promise.all(
|
||||
allAwards = await Promise.all(
|
||||
data.awards.map(async (award) => {
|
||||
try {
|
||||
const progressResponse = await fetch(`/api/awards/${award.id}/progress`, {
|
||||
@@ -67,18 +70,50 @@
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Extract unique categories
|
||||
categories = ['all', ...new Set(allAwards.map(a => a.category).filter(Boolean))];
|
||||
|
||||
// Apply filter
|
||||
applyFilter();
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilter() {
|
||||
if (selectedCategory === 'all') {
|
||||
awards = allAwards;
|
||||
} else {
|
||||
awards = allAwards.filter(award => award.category === selectedCategory);
|
||||
}
|
||||
}
|
||||
|
||||
function onCategoryChange(event) {
|
||||
selectedCategory = event.target.value;
|
||||
applyFilter();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h1>Awards</h1>
|
||||
<p class="subtitle">Track your ham radio award progress</p>
|
||||
|
||||
{#if !loading && awards.length > 0}
|
||||
<div class="filters">
|
||||
<div class="filter-group">
|
||||
<label for="category-filter">Category:</label>
|
||||
<select id="category-filter" value={selectedCategory} on:change={onCategoryChange}>
|
||||
{#each categories as category}
|
||||
<option value={category}>{category === 'all' ? 'All Awards' : category.toUpperCase()}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div class="loading">Loading awards...</div>
|
||||
{:else if error}
|
||||
@@ -162,6 +197,45 @@
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-group select {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.filter-group select:hover {
|
||||
border-color: #4a90e2;
|
||||
}
|
||||
|
||||
.filter-group select:focus {
|
||||
outline: none;
|
||||
border-color: #4a90e2;
|
||||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error,
|
||||
.empty {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
let entities = [];
|
||||
let loading = true;
|
||||
let error = null;
|
||||
let sort = 'name'; // name
|
||||
let groupedData = [];
|
||||
let bands = [];
|
||||
|
||||
@@ -175,15 +174,6 @@
|
||||
<a href="/awards" class="back-link">← Back to Awards</a>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="sort-group">
|
||||
<label>Sort by:</label>
|
||||
<select bind:value={sort}>
|
||||
<option value="name">Name</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
{#if entities.length > 0 && entities[0].points !== undefined}
|
||||
{@const earnedPoints = entities.reduce((sum, e) => sum + (e.confirmed ? e.points : 0), 0)}
|
||||
@@ -345,34 +335,6 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-group,
|
||||
.sort-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user