feat: improve focus mode
This commit is contained in:
@@ -538,25 +538,8 @@ class AutoSaveManager {
|
||||
}
|
||||
|
||||
bindManualSaveButtons() {
|
||||
const saveButtons = document.querySelectorAll('[data-save-manual]');
|
||||
const finalizeButtons = document.querySelectorAll('[data-finalize]');
|
||||
|
||||
saveButtons.forEach(button => {
|
||||
const studentId = button.dataset.saveManual;
|
||||
button.addEventListener('click', () => {
|
||||
const textarea = document.querySelector(`[data-appreciation-textarea][data-student-id="${studentId}"]`);
|
||||
if (textarea) {
|
||||
this.queueSave(studentId, textarea.value, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
finalizeButtons.forEach(button => {
|
||||
const studentId = button.dataset.finalize;
|
||||
button.addEventListener('click', () => {
|
||||
this.finalizeAppreciation(studentId);
|
||||
});
|
||||
});
|
||||
// Boutons supprimés de l'interface - auto-sauvegarde uniquement
|
||||
console.log('📝 Auto-sauvegarde activée - Pas de boutons manuels');
|
||||
}
|
||||
|
||||
setupCharacterCounter(textarea, studentId) {
|
||||
@@ -1077,6 +1060,18 @@ class FocusManager {
|
||||
detailsSection.classList.remove('hidden');
|
||||
detailsSection.style.height = 'auto';
|
||||
detailsSection.style.opacity = '1';
|
||||
detailsSection.style.flex = '1';
|
||||
detailsSection.style.display = 'block';
|
||||
detailsSection.style.overflowY = 'auto';
|
||||
|
||||
// S'assurer que le contenu interne utilise Flexbox
|
||||
const innerContent = detailsSection.querySelector('.px-6.py-6.space-y-6');
|
||||
if (innerContent) {
|
||||
innerContent.style.display = 'flex';
|
||||
innerContent.style.flexDirection = 'column';
|
||||
innerContent.style.height = '100%';
|
||||
innerContent.style.gap = '1.5rem';
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter une classe spéciale pour le mode focus
|
||||
@@ -1094,6 +1089,9 @@ class FocusManager {
|
||||
// Réattacher TOUS les événements pour le nouvel élément focus
|
||||
this.bindFocusStudentEvents(clonedStudent, studentId);
|
||||
|
||||
// Assurer que toutes les sections sont visibles
|
||||
this.ensureAllSectionsVisible(clonedStudent);
|
||||
|
||||
// Focus automatique sur le textarea de l'appréciation
|
||||
this.focusAppreciationTextarea(clonedStudent);
|
||||
|
||||
@@ -1133,36 +1131,49 @@ class FocusManager {
|
||||
this.parent.autoSaveManager.setupCharacterCounter(textarea, studentId);
|
||||
}
|
||||
|
||||
// 2. Boutons de sauvegarde manuelle
|
||||
const saveButton = clonedStudent.querySelector(`[data-save-manual="${studentId}"]`);
|
||||
if (saveButton) {
|
||||
saveButton.addEventListener('click', () => {
|
||||
const textareaValue = clonedStudent.querySelector(`[data-appreciation-textarea][data-student-id="${studentId}"]`)?.value || '';
|
||||
console.log(`💾 Sauvegarde manuelle en focus pour élève ${studentId}`);
|
||||
this.saveFocusAppreciation(studentId, textareaValue, true);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Bouton de finalisation
|
||||
const finalizeButton = clonedStudent.querySelector(`[data-finalize="${studentId}"]`);
|
||||
if (finalizeButton) {
|
||||
finalizeButton.addEventListener('click', () => {
|
||||
const textareaValue = clonedStudent.querySelector(`[data-appreciation-textarea][data-student-id="${studentId}"]`)?.value || '';
|
||||
if (!textareaValue.trim()) {
|
||||
this.parent.showToast('Veuillez saisir une appréciation avant de finaliser', 'warning');
|
||||
return;
|
||||
}
|
||||
if (confirm('Finaliser cette appréciation ? Elle ne pourra plus être modifiée.')) {
|
||||
console.log(`✅ Finalisation en focus pour élève ${studentId}`);
|
||||
this.saveFocusAppreciation(studentId, textareaValue, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 2. Boutons supprimés - auto-sauvegarde uniquement
|
||||
console.log(`🔧 Mode focus: Auto-sauvegarde configurée pour élève ${studentId}`);
|
||||
|
||||
// 4. Gestion des barres de progression
|
||||
this.setupProgressBars(clonedStudent);
|
||||
}
|
||||
|
||||
ensureAllSectionsVisible(clonedStudent) {
|
||||
console.log('🔍 Vérification de la visibilité de toutes les sections en mode focus');
|
||||
|
||||
// S'assurer que les sections compétences/domaines sont visibles
|
||||
const competenceSection = clonedStudent.querySelector('.competence-domain-section');
|
||||
if (competenceSection) {
|
||||
competenceSection.style.display = 'block';
|
||||
competenceSection.style.minHeight = '200px';
|
||||
competenceSection.style.flexShrink = '0';
|
||||
console.log('✅ Section compétences/domaines visible');
|
||||
}
|
||||
|
||||
// S'assurer que les barres de progression sont configurées
|
||||
const progressBars = clonedStudent.querySelectorAll('.progress-bar-container');
|
||||
progressBars.forEach(bar => {
|
||||
bar.style.display = 'block';
|
||||
});
|
||||
|
||||
// Section info supprimée - maintenant intégrée dans la zone d'appréciation
|
||||
console.log('✅ Informations intégrées dans la zone d\'appréciation');
|
||||
|
||||
// S'assurer que les résultats d'évaluation sont visibles
|
||||
const evaluationResults = clonedStudent.querySelector('.evaluation-results');
|
||||
if (evaluationResults) {
|
||||
evaluationResults.style.display = 'block';
|
||||
console.log('✅ Section résultats d\'évaluation visible');
|
||||
}
|
||||
|
||||
// S'assurer que la section progress-bars est visible
|
||||
const progressBarsSection = clonedStudent.querySelector('.progress-bars');
|
||||
if (progressBarsSection) {
|
||||
progressBarsSection.style.display = 'block';
|
||||
console.log('✅ Section barres de progression visible');
|
||||
}
|
||||
}
|
||||
|
||||
setupProgressBars(clonedStudent) {
|
||||
// Configure les interactions avec les barres de progression des compétences et domaines
|
||||
|
||||
@@ -1746,16 +1757,41 @@ class FocusManager {
|
||||
const student = focusContainer.querySelector('.focus-mode-student');
|
||||
if (!student) return;
|
||||
|
||||
// Calculer la hauteur disponible
|
||||
const windowHeight = window.innerHeight;
|
||||
const headerHeight = 200; // Approximation header + navigation + contrôles
|
||||
const maxHeight = windowHeight - headerHeight;
|
||||
|
||||
// Ajuster la hauteur de la carte
|
||||
student.style.maxHeight = `${maxHeight}px`;
|
||||
// Forcer explicitement la hauteur complète
|
||||
student.style.height = '100%';
|
||||
student.style.minHeight = '100%';
|
||||
student.style.maxHeight = 'none';
|
||||
student.style.display = 'flex';
|
||||
student.style.flexDirection = 'column';
|
||||
student.style.overflow = 'hidden';
|
||||
|
||||
// S'assurer que le header garde sa taille et ne grandit pas
|
||||
const headerContainer = student.querySelector('.px-6.py-4');
|
||||
if (headerContainer) {
|
||||
headerContainer.style.flexShrink = '0';
|
||||
headerContainer.style.flex = 'none';
|
||||
}
|
||||
|
||||
// S'assurer que la section détails utilise tout l'espace restant
|
||||
const detailsSection = student.querySelector('[data-student-details]');
|
||||
if (detailsSection) {
|
||||
detailsSection.style.flex = '1 1 0';
|
||||
detailsSection.style.height = '0'; // Force flexbox
|
||||
detailsSection.style.display = 'block';
|
||||
detailsSection.style.overflowY = 'auto';
|
||||
detailsSection.style.minHeight = '0';
|
||||
}
|
||||
|
||||
// Scroll vers le haut si nécessaire
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
// Debug des hauteurs
|
||||
const containerHeight = focusContainer.offsetHeight;
|
||||
const studentHeight = student.offsetHeight;
|
||||
console.log(`🎯 Mode focus optimisé:`);
|
||||
console.log(` Container height: ${containerHeight}px`);
|
||||
console.log(` Student height: ${studentHeight}px`);
|
||||
console.log(` Window height: ${window.innerHeight}px`);
|
||||
}
|
||||
|
||||
preserveJsonDataBeforeCloning(originalStudent) {
|
||||
|
||||
@@ -233,122 +233,97 @@
|
||||
data-performance-status="{{ summary.performance_status }}"
|
||||
data-has-appreciation="{{ 'true' if summary.has_appreciation else 'false' }}">
|
||||
|
||||
{# Header cliquable #}
|
||||
<div class="px-6 py-4 cursor-pointer flex items-center justify-between"
|
||||
data-toggle-student="{{ summary.student.id }}">
|
||||
<div class="flex items-center space-x-4">
|
||||
{# Avatar avec initiales #}
|
||||
<div class="w-12 h-12 rounded-full flex items-center justify-center text-white font-bold text-sm
|
||||
{% if summary.performance_status == 'excellent' %}bg-gradient-to-r from-green-500 to-green-600{% endif %}
|
||||
{% if summary.performance_status == 'good' %}bg-gradient-to-r from-blue-500 to-blue-600{% endif %}
|
||||
{% if summary.performance_status == 'average' %}bg-gradient-to-r from-yellow-500 to-yellow-600{% endif %}
|
||||
{% if summary.performance_status == 'struggling' %}bg-gradient-to-r from-red-500 to-red-600{% endif %}
|
||||
{% if summary.performance_status == 'no_data' %}bg-gradient-to-r from-gray-500 to-gray-600{% endif %}">
|
||||
{{ summary.student.first_name[0] }}{{ summary.student.last_name[0] }}
|
||||
</div>
|
||||
|
||||
{# Informations élève #}
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold text-gray-900 text-lg">{{ summary.student.last_name }}, {{ summary.student.first_name }}</h3>
|
||||
|
||||
{# Ligne 1: Info de base #}
|
||||
<div class="flex items-center space-x-4 text-sm text-gray-600 mb-2">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
{{ summary.assessment_count }} évaluation(s)
|
||||
</span>
|
||||
{% if summary.performance_status == 'struggling' %}
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-red-100 text-red-800 font-medium">
|
||||
⚠️ Attention requise
|
||||
</span>
|
||||
{% endif %}
|
||||
{# Header avec layout en deux colonnes principales #}
|
||||
<div class="px-6 py-4">
|
||||
{# Layout principal : Informations élève + Appréciation + Moyenne #}
|
||||
<div class="flex items-start space-x-6">
|
||||
{# Colonne 1: Informations élève avec avatar #}
|
||||
<div class="flex items-center space-x-4 cursor-pointer" data-toggle-student="{{ summary.student.id }}">
|
||||
{# Avatar avec initiales #}
|
||||
<div class="w-12 h-12 rounded-full flex items-center justify-center text-white font-bold text-sm
|
||||
{% if summary.performance_status == 'excellent' %}bg-gradient-to-r from-green-500 to-green-600{% endif %}
|
||||
{% if summary.performance_status == 'good' %}bg-gradient-to-r from-blue-500 to-blue-600{% endif %}
|
||||
{% if summary.performance_status == 'average' %}bg-gradient-to-r from-yellow-500 to-yellow-600{% endif %}
|
||||
{% if summary.performance_status == 'struggling' %}bg-gradient-to-r from-red-500 to-red-600{% endif %}
|
||||
{% if summary.performance_status == 'no_data' %}bg-gradient-to-r from-gray-500 to-gray-600{% endif %}">
|
||||
{{ summary.student.first_name[0] }}{{ summary.student.last_name[0] }}
|
||||
</div>
|
||||
|
||||
{# NOUVEAU - Ligne 2: Aperçu rapide des dernières évaluations #}
|
||||
{% if summary.grades_by_assessment %}
|
||||
<div class="assessment-preview-mobile-hide">
|
||||
<div class="flex items-center space-x-1 text-xs text-gray-500 mb-1">Dernières évaluations :</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
{% set recent_assessments = summary.grades_by_assessment.items() | list | sort(attribute='1.date', reverse=True) %}
|
||||
{% for assessment_id, assessment_data in recent_assessments[:4] %}
|
||||
<div class="assessment-preview-pills flex items-center space-x-1 bg-gray-50 px-2 py-1 rounded text-xs cursor-help
|
||||
{% if assessment_data.score / assessment_data.max >= 0.8 %}text-green-700 bg-green-50{% endif %}
|
||||
{% if assessment_data.score / assessment_data.max < 0.5 %}text-red-700 bg-red-50{% endif %}"
|
||||
title="{{ assessment_data.title }} - {{ assessment_data.date.strftime('%d/%m') if assessment_data.date else 'Date inconnue' }}">
|
||||
<span class="font-medium">{{ "%.1f"|format(assessment_data.score) }}</span>
|
||||
<span class="text-gray-400">/</span>
|
||||
<span>{{ assessment_data.max }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if summary.grades_by_assessment | length > 4 %}
|
||||
<span class="text-xs text-gray-400 cursor-help" title="Cliquez pour voir toutes les évaluations">+{{ summary.grades_by_assessment | length - 4 }}</span>
|
||||
{# Informations de base élève #}
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-semibold text-gray-900 text-lg">{{ summary.student.last_name }}, {{ summary.student.first_name }}</h3>
|
||||
|
||||
{# Info de base avec chevron #}
|
||||
<div class="flex items-center space-x-4 text-sm text-gray-600">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
{{ summary.assessment_count }} évaluation(s)
|
||||
</span>
|
||||
{% if summary.performance_status == 'struggling' %}
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-red-100 text-red-800 font-medium">
|
||||
⚠️ Attention requise
|
||||
</span>
|
||||
{% endif %}
|
||||
{# Chevron d'expansion #}
|
||||
<svg class="w-4 h-4 text-gray-400 transform transition-transform duration-300"
|
||||
data-toggle-icon fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Moyenne et statut - REORGANISÉ pour plus de clarté #}
|
||||
<div class="flex flex-col items-end space-y-2 text-right">
|
||||
{# Ligne 1: Moyenne principale #}
|
||||
<div class="flex items-center space-x-3">
|
||||
{% if summary.overall_average %}
|
||||
<span class="text-xl font-bold px-4 py-2 rounded-lg
|
||||
{% if summary.performance_status == 'excellent' %}bg-green-100 text-green-800{% endif %}
|
||||
{% if summary.performance_status == 'good' %}bg-blue-100 text-blue-800{% endif %}
|
||||
{% if summary.performance_status == 'average' %}bg-yellow-100 text-yellow-800{% endif %}
|
||||
{% if summary.performance_status == 'struggling' %}bg-red-100 text-red-800{% endif %}">
|
||||
{{ "%.1f"|format(summary.overall_average) }}/20
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-sm text-gray-500 px-3 py-1 bg-gray-100 rounded-lg">
|
||||
Pas de données
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{# Chevron d'expansion #}
|
||||
<svg class="w-5 h-5 text-gray-400 transform transition-transform duration-300"
|
||||
data-toggle-icon fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{# Ligne 2: Indicateurs de statut #}
|
||||
<div class="flex items-center space-x-2">
|
||||
{# NOUVEAU - Indicateur de tendance #}
|
||||
{% if summary.overall_average and summary.grades_by_assessment | length > 1 %}
|
||||
{% set recent_assessments = summary.grades_by_assessment.values() | list | sort(attribute='date') %}
|
||||
{% set trend_recent = (recent_assessments[-1].score / recent_assessments[-1].max * 20) if recent_assessments | length > 0 else 0 %}
|
||||
{% set trend_previous = (recent_assessments[-2].score / recent_assessments[-2].max * 20) if recent_assessments | length > 1 else trend_recent %}
|
||||
{% if trend_recent > trend_previous + 1 %}
|
||||
<span class="inline-flex items-center text-xs text-green-600" title="Tendance positive">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L10 4.414 4.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
+{{ "%.1f"|format(trend_recent - trend_previous) }}
|
||||
</span>
|
||||
{% elif trend_recent < trend_previous - 1 %}
|
||||
<span class="inline-flex items-center text-xs text-red-600" title="Tendance négative">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L10 15.586l5.293-5.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
{{ "%.1f"|format(trend_recent - trend_previous) }}
|
||||
{# Colonne 2: Zone d'appréciation intégrée #}
|
||||
<div class="flex-1 min-w-0">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 flex items-center">
|
||||
<svg class="w-4 h-4 text-purple-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Appréciation du conseil de classe
|
||||
</label>
|
||||
<textarea
|
||||
data-appreciation-textarea
|
||||
data-student-id="{{ summary.student.id }}"
|
||||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none transition-colors"
|
||||
rows="2"
|
||||
placeholder="Saisir l'appréciation générale pour le bulletin...">{% if summary.appreciation and summary.appreciation.general_appreciation %}{{ summary.appreciation.general_appreciation }}{% endif %}</textarea>
|
||||
<div class="mt-1 flex justify-between items-center text-xs text-gray-500">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span>Sauvegarde automatique</span>
|
||||
<div class="hidden" data-save-indicator="{{ summary.student.id }}"></div>
|
||||
{% if summary.appreciation %}
|
||||
<span class="text-gray-400">•</span>
|
||||
<span data-last-modified="{{ summary.student.id }}">{{ summary.appreciation.last_modified.strftime('%d/%m à %H:%M') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span data-char-counter>0 caractères</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Colonne 3: Moyenne et statuts #}
|
||||
<div class="flex flex-col items-end space-y-2 text-right min-w-max">
|
||||
{# Moyenne principale #}
|
||||
<div class="flex items-center space-x-2">
|
||||
{% if summary.overall_average %}
|
||||
<span class="text-xl font-bold px-4 py-2 rounded-lg
|
||||
{% if summary.performance_status == 'excellent' %}bg-green-100 text-green-800{% endif %}
|
||||
{% if summary.performance_status == 'good' %}bg-blue-100 text-blue-800{% endif %}
|
||||
{% if summary.performance_status == 'average' %}bg-yellow-100 text-yellow-800{% endif %}
|
||||
{% if summary.performance_status == 'struggling' %}bg-red-100 text-red-800{% endif %}">
|
||||
{{ "%.1f"|format(summary.overall_average) }}/20
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="inline-flex items-center text-xs text-gray-500" title="Stable">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5 10a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Stable
|
||||
<span class="text-sm text-gray-500 px-3 py-1 bg-gray-100 rounded-lg">
|
||||
Pas de données
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Indicateur d'appréciation #}
|
||||
<div class="flex items-center">
|
||||
{# Indicateurs de statut compacts #}
|
||||
<div class="flex items-center space-x-2">
|
||||
{# Indicateur d'appréciation #}
|
||||
{% if summary.has_appreciation %}
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
|
||||
<span class="w-2 h-2 bg-green-400 rounded-full mr-1"></span>
|
||||
@@ -360,10 +335,27 @@
|
||||
À rédiger
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{# Indicateur de tendance condensé #}
|
||||
{% if summary.overall_average and summary.grades_by_assessment | length > 1 %}
|
||||
{% set recent_assessments = summary.grades_by_assessment.values() | list | sort(attribute='date') %}
|
||||
{% set trend_recent = (recent_assessments[-1].score / recent_assessments[-1].max * 20) if recent_assessments | length > 0 else 0 %}
|
||||
{% set trend_previous = (recent_assessments[-2].score / recent_assessments[-2].max * 20) if recent_assessments | length > 1 else trend_recent %}
|
||||
{% if trend_recent > trend_previous + 1 %}
|
||||
<span class="inline-flex items-center text-xs text-green-600" title="Tendance positive: +{{ '%.1f'|format(trend_recent - trend_previous) }}">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L10 4.414 4.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</span>
|
||||
{% elif trend_recent < trend_previous - 1 %}
|
||||
<span class="inline-flex items-center text-xs text-red-600" title="Tendance négative: {{ '%.1f'|format(trend_recent - trend_previous) }}">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L10 15.586l5.293-5.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Indicateur de sauvegarde #}
|
||||
<div class="hidden" data-save-indicator="{{ summary.student.id }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -372,199 +364,155 @@
|
||||
<div class="hidden border-t border-gray-200" data-student-details="{{ summary.student.id }}">
|
||||
<div class="px-6 py-6 space-y-6">
|
||||
|
||||
{# Détail des évaluations #}
|
||||
{% if summary.grades_by_assessment %}
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 text-blue-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
Résultats par évaluation
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{% for assessment_id, assessment_data in summary.grades_by_assessment.items() %}
|
||||
<div class="bg-blue-50 px-4 py-3 rounded-lg border border-blue-100">
|
||||
<div class="font-medium text-blue-900 text-sm mb-1">{{ assessment_data.title }}</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-blue-700 font-bold">{{ "%.1f"|format(assessment_data.score) }}/{{ assessment_data.max }}</span>
|
||||
<span class="text-xs text-blue-600">Coeff. {{ assessment_data.coefficient }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Section Compétences et Domaines #}
|
||||
{# Section Compétences et Domaines - Layout horizontal #}
|
||||
{% if summary.competence_domain_breakdown and (summary.competence_domain_breakdown.competences or summary.competence_domain_breakdown.domains) %}
|
||||
<div class="competence-domain-section">
|
||||
<h4 class="font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 text-indigo-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
Progression par compétence et domaine
|
||||
</h4>
|
||||
|
||||
{# Barres de compétences #}
|
||||
{% if summary.competence_domain_breakdown.competences %}
|
||||
<div class="mb-4">
|
||||
<h5 class="text-sm font-medium text-purple-700 mb-3 flex items-center">
|
||||
<svg class="w-3 h-3 text-purple-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
||||
</svg>
|
||||
Compétences
|
||||
</h5>
|
||||
<div class="space-y-2">
|
||||
{% for competence in summary.competence_domain_breakdown.competences %}
|
||||
<div class="competence-progress-bar">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm font-medium text-gray-700">{{ competence.name }}</span>
|
||||
<span class="text-sm font-bold" style="color: {{ competence.color }}">
|
||||
{{ competence.percentage }}% ({{ competence.earned_points }}/{{ competence.total_points }})
|
||||
</span>
|
||||
</div>
|
||||
<div class="progress-bar-container segmented-progress"
|
||||
data-competence-name="{{ competence.name }}"
|
||||
data-assessments="{{ competence.assessments | tojson | e }}"
|
||||
role="progressbar"
|
||||
aria-valuenow="{{ competence.percentage }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Progression de la compétence {{ competence.name }}: {{ competence.percentage }}%"
|
||||
tabindex="0">
|
||||
<div class="segmented-progress-bar" data-expanded="true">
|
||||
<!-- Segments pour chaque évaluation -->
|
||||
{% for assessment in competence.assessments %}
|
||||
<div class="progress-segment"
|
||||
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
|
||||
data-assessment-id="{{ assessment.id }}"
|
||||
data-assessment-title="{{ assessment.title }}"
|
||||
data-assessment-performance="{{ assessment.performance }}"
|
||||
data-earned-this="{{ assessment.earned_this }}"
|
||||
data-max-this="{{ assessment.max_this }}"
|
||||
data-earned-cumulative="{{ assessment.earned_cumulative }}"
|
||||
data-max-cumulative="{{ assessment.max_cumulative }}"
|
||||
data-contribution-percentage="{{ assessment.percentage_contribution }}"
|
||||
title="{{ assessment.title }}: {{ assessment.earned_this }} pts ({{ assessment.performance }}%)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
|
||||
<span class="segment-label">{{ assessment.title }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{# Layout horizontal: Résultats + Barres #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
{# Colonne gauche: Résultats par évaluation (si disponibles) #}
|
||||
{% if summary.grades_by_assessment %}
|
||||
<div class="evaluation-results">
|
||||
<h5 class="text-sm font-medium text-blue-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 text-blue-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
Résultats par évaluation
|
||||
</h5>
|
||||
<div class="space-y-1.5">
|
||||
{% for assessment_id, assessment_data in summary.grades_by_assessment.items() %}
|
||||
<div class="flex items-center justify-between p-2 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<span class="text-sm font-medium text-blue-900 truncate">{{ assessment_data.title }}</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm font-bold text-blue-700">{{ "%.1f"|format(assessment_data.score) }}/{{ assessment_data.max }}</span>
|
||||
{% if assessment_data.coefficient != 1 %}
|
||||
<span class="text-xs text-blue-600 bg-blue-200 px-1 rounded">×{{ assessment_data.coefficient }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Barres de domaines #}
|
||||
{% if summary.competence_domain_breakdown.domains %}
|
||||
<div>
|
||||
<h5 class="text-sm font-medium text-orange-700 mb-3 flex items-center">
|
||||
<svg class="w-3 h-3 text-orange-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 01-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Domaines
|
||||
</h5>
|
||||
<div class="space-y-2">
|
||||
{% for domain in summary.competence_domain_breakdown.domains %}
|
||||
<div class="domain-progress-bar">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm font-medium text-gray-700">{{ domain.name }}</span>
|
||||
<span class="text-sm font-bold" style="color: {{ domain.color }}">
|
||||
{{ domain.percentage }}% ({{ domain.earned_points }}/{{ domain.total_points }})
|
||||
</span>
|
||||
</div>
|
||||
<div class="progress-bar-container segmented-progress"
|
||||
data-domain-name="{{ domain.name }}"
|
||||
data-assessments="{{ domain.assessments | tojson | e }}"
|
||||
role="progressbar"
|
||||
aria-valuenow="{{ domain.percentage }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Progression du domaine {{ domain.name }}: {{ domain.percentage }}%"
|
||||
tabindex="0">
|
||||
<div class="segmented-progress-bar" data-expanded="true">
|
||||
<!-- Segments pour chaque évaluation -->
|
||||
{% for assessment in domain.assessments %}
|
||||
<div class="progress-segment"
|
||||
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
|
||||
data-assessment-id="{{ assessment.id }}"
|
||||
data-assessment-title="{{ assessment.title }}"
|
||||
data-assessment-performance="{{ assessment.performance }}"
|
||||
data-earned-this="{{ assessment.earned_this }}"
|
||||
data-max-this="{{ assessment.max_this }}"
|
||||
data-earned-cumulative="{{ assessment.earned_cumulative }}"
|
||||
data-max-cumulative="{{ assessment.max_cumulative }}"
|
||||
data-contribution-percentage="{{ assessment.percentage_contribution }}"
|
||||
title="{{ assessment.title }}: {{ assessment.earned_this }} pts ({{ assessment.performance }}%)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
|
||||
<span class="segment-label">{{ assessment.title }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Colonne droite: Barres de progression #}
|
||||
<div class="progress-bars">
|
||||
{# Compétences #}
|
||||
{% if summary.competence_domain_breakdown.competences %}
|
||||
<div class="mb-4">
|
||||
<h5 class="text-sm font-medium text-purple-700 mb-3 flex items-center">
|
||||
<svg class="w-3 h-3 text-purple-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
||||
</svg>
|
||||
Compétences
|
||||
</h5>
|
||||
<div class="space-y-3">
|
||||
{% for competence in summary.competence_domain_breakdown.competences %}
|
||||
<div class="competence-progress-horizontal">
|
||||
{# Nom et score à côté de la barre #}
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-xs font-medium text-gray-700 truncate flex-1 mr-2">{{ competence.name }}</span>
|
||||
<span class="text-xs font-bold whitespace-nowrap" style="color: {{ competence.color }}">
|
||||
{{ competence.percentage }}%
|
||||
</span>
|
||||
</div>
|
||||
{# Barre de progression compacte #}
|
||||
<div class="progress-bar-container segmented-progress compact"
|
||||
data-competence-name="{{ competence.name }}"
|
||||
data-assessments="{{ competence.assessments | tojson | e }}"
|
||||
role="progressbar"
|
||||
aria-valuenow="{{ competence.percentage }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Progression de la compétence {{ competence.name }}: {{ competence.percentage }}%"
|
||||
tabindex="0">
|
||||
<div class="segmented-progress-bar compact" data-expanded="true">
|
||||
{% for assessment in competence.assessments %}
|
||||
<div class="progress-segment"
|
||||
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
|
||||
data-assessment-id="{{ assessment.id }}"
|
||||
data-assessment-title="{{ assessment.title }}"
|
||||
data-assessment-performance="{{ assessment.performance }}"
|
||||
data-earned-this="{{ assessment.earned_this }}"
|
||||
data-max-this="{{ assessment.max_this }}"
|
||||
data-earned-cumulative="{{ assessment.earned_cumulative }}"
|
||||
data-max-cumulative="{{ assessment.max_cumulative }}"
|
||||
data-contribution-percentage="{{ assessment.percentage_contribution }}"
|
||||
title="{{ assessment.title }}: {{ assessment.earned_this }} pts ({{ assessment.performance }}%)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
|
||||
<span class="segment-label">{{ assessment.title|truncate(6, true) }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Domaines #}
|
||||
{% if summary.competence_domain_breakdown.domains %}
|
||||
<div>
|
||||
<h5 class="text-sm font-medium text-orange-700 mb-3 flex items-center">
|
||||
<svg class="w-3 h-3 text-orange-500 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 01-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Domaines
|
||||
</h5>
|
||||
<div class="space-y-3">
|
||||
{% for domain in summary.competence_domain_breakdown.domains %}
|
||||
<div class="domain-progress-horizontal">
|
||||
{# Nom et score à côté de la barre #}
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-xs font-medium text-gray-700 truncate flex-1 mr-2">{{ domain.name }}</span>
|
||||
<span class="text-xs font-bold whitespace-nowrap" style="color: {{ domain.color }}">
|
||||
{{ domain.percentage }}%
|
||||
</span>
|
||||
</div>
|
||||
{# Barre de progression compacte #}
|
||||
<div class="progress-bar-container segmented-progress compact"
|
||||
data-domain-name="{{ domain.name }}"
|
||||
data-assessments="{{ domain.assessments | tojson | e }}"
|
||||
role="progressbar"
|
||||
aria-valuenow="{{ domain.percentage }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Progression du domaine {{ domain.name }}: {{ domain.percentage }}%"
|
||||
tabindex="0">
|
||||
<div class="segmented-progress-bar compact" data-expanded="true">
|
||||
{% for assessment in domain.assessments %}
|
||||
<div class="progress-segment"
|
||||
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
|
||||
data-assessment-id="{{ assessment.id }}"
|
||||
data-assessment-title="{{ assessment.title }}"
|
||||
data-assessment-performance="{{ assessment.performance }}"
|
||||
data-earned-this="{{ assessment.earned_this }}"
|
||||
data-max-this="{{ assessment.max_this }}"
|
||||
data-earned-cumulative="{{ assessment.earned_cumulative }}"
|
||||
data-max-cumulative="{{ assessment.max_cumulative }}"
|
||||
data-contribution-percentage="{{ assessment.percentage_contribution }}"
|
||||
title="{{ assessment.title }}: {{ assessment.earned_this }} pts ({{ assessment.performance }}%)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
|
||||
<span class="segment-label">{{ assessment.title|truncate(6, true) }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Zone d'appréciation #}
|
||||
<div>
|
||||
<label class="block font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 text-purple-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Appréciation du conseil de classe
|
||||
</label>
|
||||
<textarea
|
||||
data-appreciation-textarea
|
||||
data-student-id="{{ summary.student.id }}"
|
||||
class="w-full border border-gray-300 rounded-lg px-4 py-3 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none transition-colors"
|
||||
rows="4"
|
||||
placeholder="Saisir l'appréciation générale pour le bulletin...">{% if summary.appreciation and summary.appreciation.general_appreciation %}{{ summary.appreciation.general_appreciation }}{% endif %}</textarea>
|
||||
<div class="mt-2 flex justify-between items-center text-xs text-gray-500">
|
||||
<span>L'appréciation est sauvegardée automatiquement</span>
|
||||
<span data-char-counter>0 caractères</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Actions #}
|
||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button data-save-manual="{{ summary.student.id }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 text-sm font-medium transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Sauvegarder
|
||||
</button>
|
||||
|
||||
<button data-finalize="{{ summary.student.id }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-lg hover:from-purple-600 hover:to-purple-700 transition-all duration-300 text-sm font-medium transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Finaliser
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
{% if summary.appreciation %}
|
||||
Dernière modification : <span data-last-modified="{{ summary.student.id }}">{{ summary.appreciation.last_modified.strftime('%d/%m à %H:%M') }}</span>
|
||||
{% else %}
|
||||
Pas encore d'appréciation
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -726,38 +674,112 @@ body.focus-mode {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
background: #f9fafb;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Mode focus - Optimisation pour éviter le scroll */
|
||||
/* Mode focus - Utilisation complète de la hauteur */
|
||||
.focus-mode-student {
|
||||
max-height: calc(100vh - 80px); /* Header compact + padding */
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
max-height: none;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Mode focus - Améliorer la lisibilité */
|
||||
.focus-mode-student textarea {
|
||||
min-height: 100px;
|
||||
max-height: 200px;
|
||||
min-height: 150px;
|
||||
max-height: 50vh;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Mode focus - Compactage de la carte */
|
||||
/* Mode focus - Conteneur de la carte élève s'étend sur toute la hauteur */
|
||||
.focus-mode-student .px-6 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
/* Retirer flex: 1 car ce n'est que le header */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.focus-mode-student .py-4 {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
/* Mode focus - Section détails s'étend pour utiliser l'espace disponible */
|
||||
.focus-mode-student [data-student-details] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Mode focus - Zone d'appréciation garde sa structure */
|
||||
.focus-mode-student .flex-1.min-w-0 {
|
||||
/* Garder la structure originale */
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Mode focus - Garder le layout horizontal du header */
|
||||
.focus-mode-student .flex.items-start.space-x-6 {
|
||||
/* Conserver le layout horizontal original */
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Mode focus - Appréciation garde sa taille originale */
|
||||
.focus-mode-student .flex-1.min-w-0 textarea {
|
||||
/* Garder les propriétés originales, juste ajuster légèrement */
|
||||
min-height: 80px;
|
||||
max-height: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Mode focus - Assurer que les sections s'étendent correctement */
|
||||
.focus-mode-student .space-y-6 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Mode focus - Section détails visible et prend tout l'espace restant */
|
||||
.focus-mode-student [data-student-details] {
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
flex: 1 1 0;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
height: 0; /* Force flexbox à utiliser flex: 1 */
|
||||
}
|
||||
|
||||
/* Mode focus - Organisation du contenu des détails */
|
||||
.focus-mode-student [data-student-details] > .px-6.py-6.space-y-6 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Mode focus - Section compétences/domaines compacte mais visible */
|
||||
.focus-mode-student .competence-domain-section {
|
||||
flex-shrink: 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
|
||||
/* Mode focus - Header garde son padding normal */
|
||||
.focus-mode-student .px-6.py-4 {
|
||||
padding: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Mode focus - Réduction des espacements */
|
||||
@@ -1301,5 +1323,227 @@ button:disabled {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== STYLES POUR LE NOUVEAU LAYOUT HORIZONTAL ========== */
|
||||
|
||||
/* Header à trois colonnes */
|
||||
[data-student-card] .flex.items-start.space-x-6 {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Responsive du header : passage en vertical sur tablette */
|
||||
@media (max-width: 1024px) {
|
||||
[data-student-card] .flex.items-start.space-x-6 {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Appréciation en pleine largeur sur tablette */
|
||||
[data-student-card] .flex-1.min-w-0 textarea {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* Moyenne et statuts alignés à droite même en vertical */
|
||||
[data-student-card] .flex.flex-col.items-end.space-y-2.text-right {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile : layout complètement vertical */
|
||||
@media (max-width: 640px) {
|
||||
[data-student-card] .flex.items-start.space-x-6 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Info élève en ligne sur mobile */
|
||||
[data-student-card] .flex.items-center.space-x-4[data-toggle-student] {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Appréciation compacte sur mobile */
|
||||
[data-student-card] .flex-1.min-w-0 textarea {
|
||||
rows: 2;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Statuts empilés sur mobile */
|
||||
[data-student-card] .flex.flex-col.items-end.space-y-2.text-right {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== STYLES POUR LES BARRES DE PROGRESSION COMPACTES ========== */
|
||||
|
||||
/* Version compacte des barres de progression */
|
||||
.segmented-progress.compact {
|
||||
padding: 3px;
|
||||
min-height: 24px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.segmented-progress-bar.compact {
|
||||
height: 18px;
|
||||
gap: 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.segmented-progress.compact .progress-segment {
|
||||
height: 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
min-width: 4px;
|
||||
}
|
||||
|
||||
/* Labels plus petits en mode compact */
|
||||
.segmented-progress.compact .segment-label {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Hover effects compacts */
|
||||
.segmented-progress.compact .progress-segment:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* ========== SECTION RÉSULTATS PAR ÉVALUATION ========== */
|
||||
|
||||
.evaluation-results {
|
||||
background: rgba(239, 246, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border: 1px solid rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.evaluation-results .space-y-1\.5 > * + * {
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
/* Cards d'évaluation avec couleurs des évaluations */
|
||||
.evaluation-results .flex.items-center.justify-between {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.evaluation-results .flex.items-center.justify-between:hover {
|
||||
transform: translateX(2px);
|
||||
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* ========== BARRES DE PROGRESSION HORIZONTALES ========== */
|
||||
|
||||
.competence-progress-horizontal,
|
||||
.domain-progress-horizontal {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.competence-progress-horizontal:hover,
|
||||
.domain-progress-horizontal:hover {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ========== RESPONSIVE POUR LE NOUVEAU LAYOUT ========== */
|
||||
|
||||
/* Sur grands écrans : layout côte à côte */
|
||||
@media (min-width: 1024px) {
|
||||
.grid.grid-cols-1.lg\\:grid-cols-2.gap-6 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sur moyens écrans : layout adaptatif */
|
||||
@media (max-width: 1023px) {
|
||||
.grid.grid-cols-1.lg\\:grid-cols-2.gap-6 {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Barres plus visibles sur tablette */
|
||||
.segmented-progress.compact {
|
||||
min-height: 28px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.segmented-progress-bar.compact {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.segmented-progress.compact .progress-segment {
|
||||
height: 18px;
|
||||
min-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sur petits écrans : layout ultra-compact */
|
||||
@media (max-width: 640px) {
|
||||
.competence-domain-section .grid.grid-cols-1.lg\\:grid-cols-2.gap-6 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Textes plus petits */
|
||||
.evaluation-results h5,
|
||||
.progress-bars h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Espacement réduit */
|
||||
.competence-progress-horizontal,
|
||||
.domain-progress-horizontal {
|
||||
padding: 0.375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Noms tronqués plus agressivement */
|
||||
.competence-progress-horizontal .text-xs.font-medium.text-gray-700,
|
||||
.domain-progress-horizontal .text-xs.font-medium.text-gray-700 {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== MODE FOCUS ADAPTATIONS ========== */
|
||||
|
||||
/* Mode focus : maintenir le layout 2 colonnes */
|
||||
.focus-mode-student .competence-domain-section .grid.grid-cols-1.lg\\:grid-cols-2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Mode focus sur tablette : revenir à une colonne */
|
||||
@media (max-width: 1023px) {
|
||||
.focus-mode-student .competence-domain-section .grid.grid-cols-1.lg\\:grid-cols-2 {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.focus-mode-student .evaluation-results,
|
||||
.focus-mode-student .progress-bars {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.focus-mode-student .segmented-progress.compact {
|
||||
min-height: 20px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.focus-mode-student .segmented-progress-bar.compact {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.focus-mode-student .segmented-progress.compact .progress-segment {
|
||||
height: 14px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user