fix(ux): improve destructive action safety, accents, navigation and feedback
All checks were successful
Build and Publish Docker Images / Build Backend Image (push) Successful in 2m58s
Build and Publish Docker Images / Build Frontend Image (push) Successful in 3m19s
Build and Publish Docker Images / Build Summary (push) Successful in 3s

Replace window.confirm() with Modal dialogs for class deletion and grade
reset. Add unsaved-changes guards to AssessmentFormView. Warn before
deleting exercises/elements with existing grades. Surface invalid grades
in a detailed Modal after save. Replace GradingView local toasts with
global notification store. Fix missing French accents across CouncilView,
CouncilStudentDetail, and ConfigView. Make dashboard "À corriger" card
and student list rows clickable. Add visual hierarchy to assessment
detail actions. Add loading overlay to trimester switch. Simplify email
sending workflow by removing mode-switch pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 14:25:56 +01:00
parent a0ab7224e1
commit bb15933e69
12 changed files with 266 additions and 182 deletions

View File

@@ -202,80 +202,24 @@
</div>
</div>
<!-- Toolbar de sélection (mode sélection activé) -->
<div v-if="selectionMode && gradedStudentsWithEmail.length > 0" class="card mb-6 border-2 border-blue-500 bg-blue-50">
<div class="card-body">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h3 class="font-semibold text-blue-900">Mode Sélection Activé</h3>
<p class="text-sm text-blue-700">
Cliquez sur les cases pour sélectionner les élèves qui recevront leur bilan
<span v-if="selectedStudents.length > 0" class="font-semibold">
({{ selectedStudents.length }} sélectionné{{selectedStudents.length > 1 ? 's' : ''}})
</span>
</p>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<button
@click="toggleSelectAll"
class="btn btn-sm btn-secondary"
>
{{ allSelected ? 'Tout désélectionner' : 'Tout sélectionner' }}
</button>
<button
@click="cancelSelectionMode"
class="btn btn-sm btn-secondary"
>
<svg class="w-4 h-4 mr-1 inline" 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" />
</svg>
Annuler
</button>
<button
@click="openSendModal"
:disabled="selectedStudents.length === 0"
class="btn btn-primary shadow-lg"
:class="{ 'opacity-50 cursor-not-allowed': selectedStudents.length === 0 }"
>
<svg class="w-5 h-5 mr-2 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
Envoyer à {{ selectedStudents.length }} élève{{selectedStudents.length > 1 ? 's' : ''}}
</button>
</div>
</div>
</div>
</div>
<!-- Student scores table -->
<div class="card">
<div class="card-header flex justify-between items-center">
<h2 class="text-lg font-semibold">Détail par élève</h2>
<!-- Mode Normal : Bouton pour activer la sélection -->
<button
v-if="!selectionMode && gradedStudentsWithEmail.length > 0"
@click="activateSelectionMode"
class="btn btn-primary"
<button
v-if="gradedStudentsWithEmail.length > 0"
@click="toggleSelectAll"
class="btn btn-sm btn-secondary"
>
<svg class="w-5 h-5 mr-2 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
📧 Envoyer des bilans
{{ allSelected ? 'Tout désélectionner' : 'Tout sélectionner' }}
</button>
</div>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th v-if="selectionMode && gradedStudentsWithEmail.length > 0" class="w-16">
<th v-if="gradedStudentsWithEmail.length > 0" class="w-16">
<div class="flex items-center gap-1">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
@@ -295,10 +239,10 @@
:key="student.student_id"
:class="{
'bg-gray-50 opacity-60': !isStudentGraded(student),
'bg-blue-50 border-l-4 border-blue-500': selectionMode && isSelected(student.student_id)
'bg-blue-50 border-l-4 border-blue-500': isSelected(student.student_id)
}"
>
<td v-if="selectionMode && gradedStudentsWithEmail.length > 0" class="w-16">
<td v-if="gradedStudentsWithEmail.length > 0" class="w-16">
<input
v-if="isStudentGraded(student) && student.email"
type="checkbox"
@@ -379,6 +323,31 @@
</div>
</template>
<!-- Sticky bottom bar for sending emails -->
<Transition name="slide-up">
<div
v-if="selectedStudents.length > 0"
class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg z-40 px-6 py-3"
>
<div class="max-w-7xl mx-auto flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="text-sm text-gray-600">
<strong>{{ selectedStudents.length }}</strong> élève{{ selectedStudents.length > 1 ? 's' : '' }} sélectionné{{ selectedStudents.length > 1 ? 's' : '' }}
</span>
<button @click="selectedStudents = []" class="text-sm text-gray-500 hover:text-gray-700">
Effacer
</button>
</div>
<button
@click="openSendModal"
class="btn btn-primary"
>
Envoyer les bilans à {{ selectedStudents.length }} élève{{ selectedStudents.length > 1 ? 's' : '' }}
</button>
</div>
</div>
</Transition>
<!-- Modal d'envoi de bilans -->
<SendReportsModal
v-if="showSendModal"
@@ -409,7 +378,6 @@ const configStore = useConfigStore()
// État pour la sélection d'élèves et l'envoi d'emails
const selectedStudents = ref([])
const showSendModal = ref(false)
const selectionMode = ref(false) // Mode sélection activé/désactivé
// Color interpolation functions
function hexToRgb(hex) {
@@ -735,23 +703,6 @@ const selectedStudentsData = computed(() => {
}))
})
// Activer le mode sélection
function activateSelectionMode() {
selectionMode.value = true
selectedStudents.value = [] // Réinitialiser la sélection
}
// Annuler le mode sélection
function cancelSelectionMode() {
selectionMode.value = false
selectedStudents.value = []
}
// Vider la sélection
function clearSelection() {
selectedStudents.value = []
}
// Vérifier si un élève est sélectionné
function isSelected(studentId) {
return selectedStudents.value.includes(studentId)
@@ -776,7 +727,6 @@ function openSendModal() {
function handleReportsSent(result) {
showSendModal.value = false
selectedStudents.value = []
selectionMode.value = false // Désactiver le mode sélection après envoi
// Le modal affiche déjà les résultats, pas besoin de notification supplémentaire
}
@@ -801,3 +751,14 @@ onMounted(async () => {
}
})
</script>
<style scoped>
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.3s ease;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
}
</style>