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 { onMount } from 'svelte';
|
||||||
import { auth } from '$lib/stores.js';
|
import { auth } from '$lib/stores.js';
|
||||||
|
|
||||||
|
let allAwards = [];
|
||||||
let awards = [];
|
let awards = [];
|
||||||
|
let categories = [];
|
||||||
|
let selectedCategory = 'all';
|
||||||
let loading = true;
|
let loading = true;
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
@@ -33,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load progress for each award
|
// Load progress for each award
|
||||||
awards = await Promise.all(
|
allAwards = await Promise.all(
|
||||||
data.awards.map(async (award) => {
|
data.awards.map(async (award) => {
|
||||||
try {
|
try {
|
||||||
const progressResponse = await fetch(`/api/awards/${award.id}/progress`, {
|
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) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Awards</h1>
|
<h1>Awards</h1>
|
||||||
<p class="subtitle">Track your ham radio award progress</p>
|
<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}
|
{#if loading}
|
||||||
<div class="loading">Loading awards...</div>
|
<div class="loading">Loading awards...</div>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
@@ -162,6 +197,45 @@
|
|||||||
margin-bottom: 2rem;
|
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,
|
.loading,
|
||||||
.error,
|
.error,
|
||||||
.empty {
|
.empty {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
let entities = [];
|
let entities = [];
|
||||||
let loading = true;
|
let loading = true;
|
||||||
let error = null;
|
let error = null;
|
||||||
let sort = 'name'; // name
|
|
||||||
let groupedData = [];
|
let groupedData = [];
|
||||||
let bands = [];
|
let bands = [];
|
||||||
|
|
||||||
@@ -175,15 +174,6 @@
|
|||||||
<a href="/awards" class="back-link">← Back to Awards</a>
|
<a href="/awards" class="back-link">← Back to Awards</a>
|
||||||
</div>
|
</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">
|
<div class="summary">
|
||||||
{#if entities.length > 0 && entities[0].points !== undefined}
|
{#if entities.length > 0 && entities[0].points !== undefined}
|
||||||
{@const earnedPoints = entities.reduce((sum, e) => sum + (e.confirmed ? e.points : 0), 0)}
|
{@const earnedPoints = entities.reduce((sum, e) => sum + (e.confirmed ? e.points : 0), 0)}
|
||||||
@@ -345,34 +335,6 @@
|
|||||||
text-decoration: underline;
|
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 {
|
.summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user