[FIX] Uge session of debugging and code coverage.
This commit is contained in:
@@ -3,42 +3,23 @@
|
||||
import { apiService } from '$lib/api';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { INSTRUMENTS, getInstrument } from '$lib/instruments';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
|
||||
const INSTRUMENTS = [
|
||||
{ code: 'dir', name: 'Direction', defaultKey: '' },
|
||||
{ code: 'pic', name: 'Piccolo', defaultKey: 'do' },
|
||||
{ code: 'flu', name: 'Flûte', defaultKey: 'do' },
|
||||
{ code: 'cla', name: 'Clarinette', defaultKey: 'sib' },
|
||||
{ code: 'clb', name: 'Clarinette Basse', defaultKey: 'sib' },
|
||||
{ code: 'sax', name: 'Saxophone Alto', defaultKey: 'mib' },
|
||||
{ code: 'sat', name: 'Saxophone Ténor', defaultKey: 'sib' },
|
||||
{ code: 'sab', name: 'Saxophone Baryton', defaultKey: 'mib' },
|
||||
{ code: 'coa', name: 'Cor Anglais', defaultKey: 'fa' },
|
||||
{ code: 'htb', name: 'Hautbois', defaultKey: 'do' },
|
||||
{ code: 'bas', name: 'Basson', defaultKey: 'do' },
|
||||
{ code: 'cor', name: 'Cor', defaultKey: 'fa' },
|
||||
{ code: 'trp', name: 'Trompette', defaultKey: 'sib' },
|
||||
{ code: 'crn', name: 'Cornet', defaultKey: 'sib' },
|
||||
{ code: 'trb', name: 'Trombone', defaultKey: 'do' },
|
||||
{ code: 'eup', name: 'Euphonium', defaultKey: 'sib' },
|
||||
{ code: 'tub', name: 'Tuba', defaultKey: 'do' },
|
||||
{ code: 'cba', name: 'Contrebasse', defaultKey: 'sib' },
|
||||
{ code: 'per', name: 'Percussions', defaultKey: '' },
|
||||
{ code: 'pia', name: 'Piano', defaultKey: '' },
|
||||
{ code: 'har', name: 'Harpe', defaultKey: '' },
|
||||
{ code: 'sup', name: 'Parties supplémentaires', defaultKey: '' }
|
||||
];
|
||||
|
||||
let scoreId = $derived($page.params.id || '');
|
||||
let scoreId = $derived(page.params.id || '');
|
||||
let scoreName = $state('');
|
||||
let scoreCompositor = $state('');
|
||||
let scoreRessource = $state('');
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let userRole = $state('');
|
||||
let userRole = $derived($auth.user?.role || '');
|
||||
let isAdmin = $derived(userRole === 'admin');
|
||||
|
||||
function focusOnMount(node: HTMLElement) {
|
||||
node.focus();
|
||||
}
|
||||
|
||||
// Edit mode
|
||||
let editingField = $state<string | null>(null);
|
||||
let editValue = $state('');
|
||||
@@ -88,7 +69,7 @@
|
||||
// Auto-set default key when instrument changes
|
||||
$effect(() => {
|
||||
if (uploadInstrument) {
|
||||
const inst = INSTRUMENTS.find(i => i.code === uploadInstrument);
|
||||
const inst = getInstrument(uploadInstrument);
|
||||
if (inst?.defaultKey) {
|
||||
uploadKey = inst.defaultKey;
|
||||
}
|
||||
@@ -147,7 +128,7 @@
|
||||
|
||||
function getExpectedFilename(): string {
|
||||
if (!uploadInstrument) return '';
|
||||
const inst = INSTRUMENTS.find(i => i.code === uploadInstrument);
|
||||
const inst = getInstrument(uploadInstrument);
|
||||
const instName = inst ? inst.name.toLowerCase().replace(/ /g, '_') : uploadInstrument;
|
||||
const key = uploadKey || inst?.defaultKey || '';
|
||||
const keyStr = key ? `_${key}` : '';
|
||||
@@ -159,18 +140,11 @@
|
||||
let expectedFilename = $derived(getExpectedFilename());
|
||||
|
||||
onMount(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const unsubscribe = auth.subscribe((state) => {
|
||||
userRole = state.user?.role || '';
|
||||
});
|
||||
|
||||
if (userRole !== 'admin') {
|
||||
unsubscribe();
|
||||
goto('/scores');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await loadScoreInfo();
|
||||
await handleScoreSelect();
|
||||
await loadFiles();
|
||||
@@ -311,12 +285,12 @@
|
||||
|
||||
<!-- Name -->
|
||||
<div class="flex items-center gap-4 py-3 border-b border-gray-100">
|
||||
<label class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Nom</label>
|
||||
<span class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Nom</span>
|
||||
{#if editingField === 'name'}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={editValue}
|
||||
autofocus
|
||||
use:focusOnMount
|
||||
onkeydown={(e) => { if (e.key === 'Enter') saveEdit('name'); if (e.key === 'Escape') cancelEdit(); }}
|
||||
class="flex-1 text-xl font-bold text-ohmj-primary border-b-2 border-ohmj-primary bg-transparent px-3 py-2 focus:outline-none"
|
||||
/>
|
||||
@@ -327,9 +301,9 @@
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="flex-1 text-xl font-bold text-ohmj-primary cursor-pointer hover:text-ohmj-secondary px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('name', scoreName)} title="Cliquer pour modifier">
|
||||
<button type="button" class="flex-1 text-left text-xl font-bold text-ohmj-primary cursor-pointer hover:text-ohmj-secondary px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('name', scoreName)} title="Cliquer pour modifier">
|
||||
{scoreName || 'Sans titre'}
|
||||
</span>
|
||||
</button>
|
||||
<button onclick={() => startEdit('name', scoreName)} class="text-gray-400 hover:text-ohmj-primary p-2 rounded-full hover:bg-gray-100" title="Modifier">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg>
|
||||
</button>
|
||||
@@ -338,12 +312,12 @@
|
||||
|
||||
<!-- Compositor -->
|
||||
<div class="flex items-center gap-4 py-3 border-b border-gray-100">
|
||||
<label class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Compositeur</label>
|
||||
<span class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Compositeur</span>
|
||||
{#if editingField === 'compositor'}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={editValue}
|
||||
autofocus
|
||||
use:focusOnMount
|
||||
placeholder="Compositeur"
|
||||
onkeydown={(e) => { if (e.key === 'Enter') saveEdit('compositor'); if (e.key === 'Escape') cancelEdit(); }}
|
||||
class="flex-1 text-lg text-gray-700 border-b-2 border-ohmj-primary bg-transparent px-3 py-2 focus:outline-none"
|
||||
@@ -355,9 +329,9 @@
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="flex-1 text-lg text-gray-700 cursor-pointer hover:text-ohmj-primary px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('compositor', scoreCompositor)} title="Cliquer pour modifier">
|
||||
<button type="button" class="flex-1 text-left text-lg text-gray-700 cursor-pointer hover:text-ohmj-primary px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('compositor', scoreCompositor)} title="Cliquer pour modifier">
|
||||
{scoreCompositor || 'Aucun compositeur'}
|
||||
</span>
|
||||
</button>
|
||||
<button onclick={() => startEdit('compositor', scoreCompositor)} class="text-gray-400 hover:text-ohmj-primary p-2 rounded-full hover:bg-gray-100" title="Modifier">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg>
|
||||
</button>
|
||||
@@ -366,12 +340,12 @@
|
||||
|
||||
<!-- Ressource -->
|
||||
<div class="flex items-center gap-4 py-3">
|
||||
<label class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Ressource</label>
|
||||
<span class="w-32 text-sm font-semibold text-gray-600 flex-shrink-0">Ressource</span>
|
||||
{#if editingField === 'ressource'}
|
||||
<input
|
||||
type="url"
|
||||
bind:value={editValue}
|
||||
autofocus
|
||||
use:focusOnMount
|
||||
placeholder="URL (YouTube, Google Drive, etc.)"
|
||||
onkeydown={(e) => { if (e.key === 'Enter') saveEdit('ressource'); if (e.key === 'Escape') cancelEdit(); }}
|
||||
class="flex-1 text-sm text-blue-600 border-b-2 border-ohmj-primary bg-transparent px-3 py-2 focus:outline-none"
|
||||
@@ -384,13 +358,13 @@
|
||||
</button>
|
||||
{:else}
|
||||
{#if scoreRessource}
|
||||
<span class="flex-1 text-sm text-blue-600 cursor-pointer hover:text-blue-800 px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('ressource', scoreRessource)} title="Cliquer pour modifier">
|
||||
<button type="button" class="flex-1 text-left text-sm text-blue-600 cursor-pointer hover:text-blue-800 px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('ressource', scoreRessource)} title="Cliquer pour modifier">
|
||||
🔗 {scoreRessource}
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="flex-1 text-sm text-gray-400 cursor-pointer hover:text-gray-600 px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('ressource', '')} title="Cliquer pour ajouter">
|
||||
<button type="button" class="flex-1 text-left text-sm text-gray-400 cursor-pointer hover:text-gray-600 px-3 py-2 rounded-lg hover:bg-gray-50" onclick={() => startEdit('ressource', '')} title="Cliquer pour ajouter">
|
||||
Aucune ressource
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
<button onclick={() => startEdit('ressource', scoreRessource)} class="text-gray-400 hover:text-ohmj-primary p-2 rounded-full hover:bg-gray-100" title="Modifier">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg>
|
||||
@@ -468,8 +442,9 @@
|
||||
<form onsubmit={(e) => { e.preventDefault(); uploadPdf(); }} class="space-y-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Instrument</label>
|
||||
<label for="up-instrument" class="block text-sm font-medium text-gray-700">Instrument</label>
|
||||
<select
|
||||
id="up-instrument"
|
||||
bind:value={uploadInstrument}
|
||||
required
|
||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white"
|
||||
@@ -496,8 +471,9 @@
|
||||
<div class="grid grid-cols-2 md:grid-cols-7 gap-4 p-3 bg-gray-100 rounded">
|
||||
{#if selectedScorePieceCount > 1}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Pièce</label>
|
||||
<label for="up-piece" class="block text-sm font-medium text-gray-700">Pièce</label>
|
||||
<select
|
||||
id="up-piece"
|
||||
bind:value={uploadPiece}
|
||||
required
|
||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white"
|
||||
@@ -509,8 +485,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Version</label>
|
||||
<label for="up-version" class="block text-sm font-medium text-gray-700">Version</label>
|
||||
<input
|
||||
id="up-version"
|
||||
type="number"
|
||||
min="1"
|
||||
bind:value={uploadVersion}
|
||||
@@ -520,19 +497,19 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Tonalité</label>
|
||||
<select bind:value={uploadKey} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<label for="up-key" class="block text-sm font-medium text-gray-700">Tonalité</label>
|
||||
<select id="up-key" bind:value={uploadKey} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<option value="">-</option>
|
||||
<option value="sib">Si♭ (sib)</option>
|
||||
<option value="mib">Mi♭ (mif)</option>
|
||||
<option value="fa">Fa (fa)</option>
|
||||
<option value="do">Do (ut)</option>
|
||||
<option value="sol">Sol</option>
|
||||
<option value="sib">Si♭ (B♭)</option>
|
||||
<option value="mib">Mi♭ (E♭)</option>
|
||||
<option value="fa">Fa (F)</option>
|
||||
<option value="do">Do (C/Ut)</option>
|
||||
<option value="sol">Sol (G)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Clé</label>
|
||||
<select bind:value={uploadClef} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<label for="up-clef" class="block text-sm font-medium text-gray-700">Clé</label>
|
||||
<select id="up-clef" bind:value={uploadClef} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<option value="">-</option>
|
||||
<option value="clesol">Sol</option>
|
||||
<option value="clefa">Fa</option>
|
||||
@@ -540,8 +517,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Partie</label>
|
||||
<select bind:value={uploadPart} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<label for="up-part" class="block text-sm font-medium text-gray-700">Partie</label>
|
||||
<select id="up-part" bind:value={uploadPart} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
@@ -549,8 +526,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Variante</label>
|
||||
<select bind:value={uploadVariant} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<label for="up-variant" class="block text-sm font-medium text-gray-700">Variante</label>
|
||||
<select id="up-variant" bind:value={uploadVariant} class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white">
|
||||
<option value="">-</option>
|
||||
<option value="solo">Solo</option>
|
||||
<option value="default">Default</option>
|
||||
@@ -569,7 +546,7 @@
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Fichier PDF</label>
|
||||
<label for="file-input" class="block text-sm font-medium text-gray-700">Fichier PDF</label>
|
||||
<div class="flex items-center gap-3 mt-1">
|
||||
<label class="relative inline-flex items-center cursor-pointer whitespace-nowrap">
|
||||
<input type="checkbox" bind:checked={uploadWatermark} class="sr-only peer" />
|
||||
@@ -682,32 +659,13 @@
|
||||
{/if}
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{#if deleteFilePath}
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" onclick={() => deleteFilePath = null}>
|
||||
<div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4" onclick={(e) => e.stopPropagation()}>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Confirmer la suppression</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action est irréversible.
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 mb-6 font-mono bg-gray-100 p-2 rounded">
|
||||
{deleteFilePath}
|
||||
</p>
|
||||
<div class="flex gap-3 justify-end">
|
||||
<button
|
||||
onclick={() => deleteFilePath = null}
|
||||
class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded transition-colors"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onclick={deleteFile}
|
||||
disabled={deletingFile}
|
||||
class="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{deletingFile ? 'Suppression...' : 'Supprimer'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<ConfirmDialog
|
||||
open={!!deleteFilePath}
|
||||
title="Confirmer la suppression"
|
||||
message="Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action est irréversible."
|
||||
detail={deleteFilePath ?? ''}
|
||||
busy={deletingFile}
|
||||
onconfirm={deleteFile}
|
||||
oncancel={() => (deleteFilePath = null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user