@@ -2,11 +2,12 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { apiService, type Score } from '$lib/api';
|
||||
|
||||
let scores: Score[] = [];
|
||||
let loading = true;
|
||||
let error = '';
|
||||
let sortBy: 'id' | 'name' | 'compositor' = 'id';
|
||||
let sortOrder: 'asc' | 'desc' = 'asc';
|
||||
let scores = $state<Score[]>([]);
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let sortBy: 'id' | 'name' | 'compositor' = $state('id');
|
||||
let sortOrder: 'asc' | 'desc' = $state('desc');
|
||||
let filter: string = $state('');
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
@@ -28,17 +29,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: sortedScores = [...scores].sort((a, b) => {
|
||||
function removeAccents(str: string): string {
|
||||
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
}
|
||||
|
||||
let filteredScores = $derived.by(() => {
|
||||
let result = [...scores];
|
||||
|
||||
// Filter
|
||||
if (filter.trim()) {
|
||||
const f = removeAccents(filter.toLowerCase());
|
||||
result = result.filter(s =>
|
||||
removeAccents(s.id.toLowerCase()).includes(f) ||
|
||||
removeAccents(s.name.toLowerCase()).includes(f) ||
|
||||
removeAccents(s.compositor || '').toLowerCase().includes(f)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort
|
||||
result.sort((a, b) => {
|
||||
let aVal = a[sortBy] || '';
|
||||
let bVal = b[sortBy] || '';
|
||||
let cmp: number;
|
||||
if (sortBy === 'id') {
|
||||
cmp = parseInt(a.id) - parseInt(b.id);
|
||||
} else {
|
||||
cmp = aVal.localeCompare(bVal);
|
||||
cmp = String(aVal).localeCompare(String(bVal));
|
||||
}
|
||||
return sortOrder === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -48,7 +70,7 @@
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-ohmj-primary">Répertoire</h1>
|
||||
<p class="text-gray-600 mt-2">{scores.length} partitions disponibles</p>
|
||||
<p class="text-gray-600 mt-2">{scores.length} partition{scores.length !== 1 ? 's' : ''} disponible{scores.length !== 1 ? 's' : ''}</p>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
@@ -60,32 +82,76 @@
|
||||
{error}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Filter & Sort Bar -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="flex-1">
|
||||
<div class="relative">
|
||||
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={filter}
|
||||
placeholder="Rechercher par n°, nom ou compositeur..."
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ohmj-primary focus:border-ohmj-primary transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sort Controls -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-500 whitespace-nowrap">Trier par :</span>
|
||||
<div class="flex rounded-lg border border-gray-300 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => sortScores('id')}
|
||||
class="px-3 py-2 text-sm font-medium transition-colors {sortBy === 'id' ? 'bg-ohmj-primary text-white' : 'bg-white text-gray-700 hover:bg-gray-50'}"
|
||||
>
|
||||
№ {sortBy === 'id' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => sortScores('name')}
|
||||
class="px-3 py-2 text-sm font-medium transition-colors {sortBy === 'name' ? 'bg-ohmj-primary text-white' : 'bg-white text-gray-700 hover:bg-gray-50'}"
|
||||
>
|
||||
Nom {sortBy === 'name' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => sortScores('compositor')}
|
||||
class="px-3 py-2 text-sm font-medium transition-colors {sortBy === 'compositor' ? 'bg-ohmj-primary text-white' : 'bg-white text-gray-700 hover:bg-gray-50'}"
|
||||
>
|
||||
Compositeur {sortBy === 'compositor' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if filter}
|
||||
<p class="text-sm text-gray-500 mt-2">{filteredScores.length} résultat{filteredScores.length !== 1 ? 's' : ''} pour "{filter}"</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Results Table -->
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||
<table class="min-w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 w-20"
|
||||
onclick={() => sortScores('id')}
|
||||
>
|
||||
№ {sortBy === 'id' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-20">
|
||||
№
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100"
|
||||
onclick={() => sortScores('name')}
|
||||
>
|
||||
Nom {sortBy === 'name' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nom
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100"
|
||||
onclick={() => sortScores('compositor')}
|
||||
>
|
||||
Compositeur {sortBy === 'compositor' ? (sortOrder === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Compositeur
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{#each sortedScores as score}
|
||||
{#each filteredScores as score}
|
||||
<tr class="hover:bg-ohmj-light transition-colors cursor-pointer" onclick={() => window.location.href = '/scores/' + score.id}>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{score.id}
|
||||
@@ -100,6 +166,12 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if filteredScores.length === 0}
|
||||
<div class="text-center py-12 text-gray-500">
|
||||
Aucune partition ne correspond à votre recherche
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user