feat: improve focus mode

This commit is contained in:
2025-08-12 07:25:26 +02:00
parent 11bfc5c5cb
commit c3ef5287b3
2 changed files with 627 additions and 347 deletions

View File

@@ -538,25 +538,8 @@ class AutoSaveManager {
} }
bindManualSaveButtons() { bindManualSaveButtons() {
const saveButtons = document.querySelectorAll('[data-save-manual]'); // Boutons supprimés de l'interface - auto-sauvegarde uniquement
const finalizeButtons = document.querySelectorAll('[data-finalize]'); console.log('📝 Auto-sauvegarde activée - Pas de boutons manuels');
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);
});
});
} }
setupCharacterCounter(textarea, studentId) { setupCharacterCounter(textarea, studentId) {
@@ -1077,6 +1060,18 @@ class FocusManager {
detailsSection.classList.remove('hidden'); detailsSection.classList.remove('hidden');
detailsSection.style.height = 'auto'; detailsSection.style.height = 'auto';
detailsSection.style.opacity = '1'; 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 // 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 // Réattacher TOUS les événements pour le nouvel élément focus
this.bindFocusStudentEvents(clonedStudent, studentId); this.bindFocusStudentEvents(clonedStudent, studentId);
// Assurer que toutes les sections sont visibles
this.ensureAllSectionsVisible(clonedStudent);
// Focus automatique sur le textarea de l'appréciation // Focus automatique sur le textarea de l'appréciation
this.focusAppreciationTextarea(clonedStudent); this.focusAppreciationTextarea(clonedStudent);
@@ -1133,36 +1131,49 @@ class FocusManager {
this.parent.autoSaveManager.setupCharacterCounter(textarea, studentId); this.parent.autoSaveManager.setupCharacterCounter(textarea, studentId);
} }
// 2. Boutons de sauvegarde manuelle // 2. Boutons supprimés - auto-sauvegarde uniquement
const saveButton = clonedStudent.querySelector(`[data-save-manual="${studentId}"]`); console.log(`🔧 Mode focus: Auto-sauvegarde configurée pour élève ${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);
}
});
}
// 4. Gestion des barres de progression // 4. Gestion des barres de progression
this.setupProgressBars(clonedStudent); 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) { setupProgressBars(clonedStudent) {
// Configure les interactions avec les barres de progression des compétences et domaines // 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'); const student = focusContainer.querySelector('.focus-mode-student');
if (!student) return; if (!student) return;
// Calculer la hauteur disponible // Forcer explicitement la hauteur complète
const windowHeight = window.innerHeight; student.style.height = '100%';
const headerHeight = 200; // Approximation header + navigation + contrôles student.style.minHeight = '100%';
const maxHeight = windowHeight - headerHeight; student.style.maxHeight = 'none';
student.style.display = 'flex';
student.style.flexDirection = 'column';
student.style.overflow = 'hidden';
// Ajuster la hauteur de la carte // S'assurer que le header garde sa taille et ne grandit pas
student.style.maxHeight = `${maxHeight}px`; 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 // Scroll vers le haut si nécessaire
window.scrollTo(0, 0); 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) { preserveJsonDataBeforeCloning(originalStudent) {

View File

@@ -233,10 +233,12 @@
data-performance-status="{{ summary.performance_status }}" data-performance-status="{{ summary.performance_status }}"
data-has-appreciation="{{ 'true' if summary.has_appreciation else 'false' }}"> data-has-appreciation="{{ 'true' if summary.has_appreciation else 'false' }}">
{# Header cliquable #} {# Header avec layout en deux colonnes principales #}
<div class="px-6 py-4 cursor-pointer flex items-center justify-between" <div class="px-6 py-4">
data-toggle-student="{{ summary.student.id }}"> {# Layout principal : Informations élève + Appréciation + Moyenne #}
<div class="flex items-center space-x-4"> <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 #} {# Avatar avec initiales #}
<div class="w-12 h-12 rounded-full flex items-center justify-center text-white font-bold text-sm <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 == 'excellent' %}bg-gradient-to-r from-green-500 to-green-600{% endif %}
@@ -247,12 +249,12 @@
{{ summary.student.first_name[0] }}{{ summary.student.last_name[0] }} {{ summary.student.first_name[0] }}{{ summary.student.last_name[0] }}
</div> </div>
{# Informations élève #} {# Informations de base élève #}
<div class="flex-1 min-w-0"> <div class="min-w-0">
<h3 class="font-semibold text-gray-900 text-lg">{{ summary.student.last_name }}, {{ summary.student.first_name }}</h3> <h3 class="font-semibold text-gray-900 text-lg">{{ summary.student.last_name }}, {{ summary.student.first_name }}</h3>
{# Ligne 1: Info de base #} {# Info de base avec chevron #}
<div class="flex items-center space-x-4 text-sm text-gray-600 mb-2"> <div class="flex items-center space-x-4 text-sm text-gray-600">
<span class="flex items-center"> <span class="flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> <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"/> <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"/>
@@ -264,37 +266,46 @@
⚠️ Attention requise ⚠️ Attention requise
</span> </span>
{% endif %} {% 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>
{# 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>
{% endif %}
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
{# Moyenne et statut - REORGANISÉ pour plus de clarté #} {# Colonne 2: Zone d'appréciation intégrée #}
<div class="flex flex-col items-end space-y-2 text-right"> <div class="flex-1 min-w-0">
{# Ligne 1: Moyenne principale #} <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"> <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 %} {% if summary.overall_average %}
<span class="text-xl font-bold px-4 py-2 rounded-lg <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 == 'excellent' %}bg-green-100 text-green-800{% endif %}
@@ -308,47 +319,11 @@
Pas de données Pas de données
</span> </span>
{% endif %} {% 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> </div>
{# Ligne 2: Indicateurs de statut #} {# Indicateurs de statut compacts #}
<div class="flex items-center space-x-2"> <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) }}
</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>
{% endif %}
{% endif %}
{# Indicateur d'appréciation #} {# Indicateur d'appréciation #}
<div class="flex items-center">
{% if summary.has_appreciation %} {% 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="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> <span class="w-2 h-2 bg-green-400 rounded-full mr-1"></span>
@@ -360,10 +335,27 @@
À rédiger À rédiger
</span> </span>
{% endif %} {% endif %}
</div>
{# Indicateur de sauvegarde #} {# Indicateur de tendance condensé #}
<div class="hidden" data-save-indicator="{{ summary.student.id }}"></div> {% 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>
</div> </div>
</div> </div>
</div> </div>
@@ -372,22 +364,30 @@
<div class="hidden border-t border-gray-200" data-student-details="{{ summary.student.id }}"> <div class="hidden border-t border-gray-200" data-student-details="{{ summary.student.id }}">
<div class="px-6 py-6 space-y-6"> <div class="px-6 py-6 space-y-6">
{# Détail des évaluations #} {# 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">
{# 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 %} {% if summary.grades_by_assessment %}
<div> <div class="evaluation-results">
<h4 class="font-medium text-gray-700 mb-3 flex items-center"> <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"> <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"/> <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> </svg>
Résultats par évaluation Résultats par évaluation
</h4> </h5>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3"> <div class="space-y-1.5">
{% for assessment_id, assessment_data in summary.grades_by_assessment.items() %} {% 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="flex items-center justify-between p-2 bg-blue-50 rounded-lg border border-blue-100">
<div class="font-medium text-blue-900 text-sm mb-1">{{ assessment_data.title }}</div> <span class="text-sm font-medium text-blue-900 truncate">{{ assessment_data.title }}</span>
<div class="flex items-center justify-between"> <div class="flex items-center space-x-2">
<span class="text-blue-700 font-bold">{{ "%.1f"|format(assessment_data.score) }}/{{ assessment_data.max }}</span> <span class="text-sm font-bold text-blue-700">{{ "%.1f"|format(assessment_data.score) }}/{{ assessment_data.max }}</span>
<span class="text-xs text-blue-600">Coeff. {{ assessment_data.coefficient }}</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>
</div> </div>
{% endfor %} {% endfor %}
@@ -395,17 +395,9 @@
</div> </div>
{% endif %} {% endif %}
{# Section Compétences et Domaines #} {# Colonne droite: Barres de progression #}
{% if summary.competence_domain_breakdown and (summary.competence_domain_breakdown.competences or summary.competence_domain_breakdown.domains) %} <div class="progress-bars">
<div class="competence-domain-section"> {# Compétences #}
<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 %} {% if summary.competence_domain_breakdown.competences %}
<div class="mb-4"> <div class="mb-4">
<h5 class="text-sm font-medium text-purple-700 mb-3 flex items-center"> <h5 class="text-sm font-medium text-purple-700 mb-3 flex items-center">
@@ -414,16 +406,18 @@
</svg> </svg>
Compétences Compétences
</h5> </h5>
<div class="space-y-2"> <div class="space-y-3">
{% for competence in summary.competence_domain_breakdown.competences %} {% for competence in summary.competence_domain_breakdown.competences %}
<div class="competence-progress-bar"> <div class="competence-progress-horizontal">
{# Nom et score à côté de la barre #}
<div class="flex items-center justify-between mb-1"> <div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-700">{{ competence.name }}</span> <span class="text-xs font-medium text-gray-700 truncate flex-1 mr-2">{{ competence.name }}</span>
<span class="text-sm font-bold" style="color: {{ competence.color }}"> <span class="text-xs font-bold whitespace-nowrap" style="color: {{ competence.color }}">
{{ competence.percentage }}% ({{ competence.earned_points }}/{{ competence.total_points }}) {{ competence.percentage }}%
</span> </span>
</div> </div>
<div class="progress-bar-container segmented-progress" {# Barre de progression compacte #}
<div class="progress-bar-container segmented-progress compact"
data-competence-name="{{ competence.name }}" data-competence-name="{{ competence.name }}"
data-assessments="{{ competence.assessments | tojson | e }}" data-assessments="{{ competence.assessments | tojson | e }}"
role="progressbar" role="progressbar"
@@ -432,8 +426,7 @@
aria-valuemax="100" aria-valuemax="100"
aria-label="Progression de la compétence {{ competence.name }}: {{ competence.percentage }}%" aria-label="Progression de la compétence {{ competence.name }}: {{ competence.percentage }}%"
tabindex="0"> tabindex="0">
<div class="segmented-progress-bar" data-expanded="true"> <div class="segmented-progress-bar compact" data-expanded="true">
<!-- Segments pour chaque évaluation -->
{% for assessment in competence.assessments %} {% for assessment in competence.assessments %}
<div class="progress-segment" <div class="progress-segment"
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};" style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
@@ -449,7 +442,7 @@
role="button" role="button"
tabindex="0" tabindex="0"
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}"> aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
<span class="segment-label">{{ assessment.title }}</span> <span class="segment-label">{{ assessment.title|truncate(6, true) }}</span>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -460,7 +453,7 @@
</div> </div>
{% endif %} {% endif %}
{# Barres de domaines #} {# Domaines #}
{% if summary.competence_domain_breakdown.domains %} {% if summary.competence_domain_breakdown.domains %}
<div> <div>
<h5 class="text-sm font-medium text-orange-700 mb-3 flex items-center"> <h5 class="text-sm font-medium text-orange-700 mb-3 flex items-center">
@@ -469,16 +462,18 @@
</svg> </svg>
Domaines Domaines
</h5> </h5>
<div class="space-y-2"> <div class="space-y-3">
{% for domain in summary.competence_domain_breakdown.domains %} {% for domain in summary.competence_domain_breakdown.domains %}
<div class="domain-progress-bar"> <div class="domain-progress-horizontal">
{# Nom et score à côté de la barre #}
<div class="flex items-center justify-between mb-1"> <div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-700">{{ domain.name }}</span> <span class="text-xs font-medium text-gray-700 truncate flex-1 mr-2">{{ domain.name }}</span>
<span class="text-sm font-bold" style="color: {{ domain.color }}"> <span class="text-xs font-bold whitespace-nowrap" style="color: {{ domain.color }}">
{{ domain.percentage }}% ({{ domain.earned_points }}/{{ domain.total_points }}) {{ domain.percentage }}%
</span> </span>
</div> </div>
<div class="progress-bar-container segmented-progress" {# Barre de progression compacte #}
<div class="progress-bar-container segmented-progress compact"
data-domain-name="{{ domain.name }}" data-domain-name="{{ domain.name }}"
data-assessments="{{ domain.assessments | tojson | e }}" data-assessments="{{ domain.assessments | tojson | e }}"
role="progressbar" role="progressbar"
@@ -487,8 +482,7 @@
aria-valuemax="100" aria-valuemax="100"
aria-label="Progression du domaine {{ domain.name }}: {{ domain.percentage }}%" aria-label="Progression du domaine {{ domain.name }}: {{ domain.percentage }}%"
tabindex="0"> tabindex="0">
<div class="segmented-progress-bar" data-expanded="true"> <div class="segmented-progress-bar compact" data-expanded="true">
<!-- Segments pour chaque évaluation -->
{% for assessment in domain.assessments %} {% for assessment in domain.assessments %}
<div class="progress-segment" <div class="progress-segment"
style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};" style="width: {{ assessment.percentage_contribution }}%; background-color: {{ assessment.color }};"
@@ -504,7 +498,7 @@
role="button" role="button"
tabindex="0" tabindex="0"
aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}"> aria-label="Évaluation {{ assessment.title }}: {{ assessment.earned_this }} points sur {{ assessment.max_this }}">
<span class="segment-label">{{ assessment.title }}</span> <span class="segment-label">{{ assessment.title|truncate(6, true) }}</span>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -515,56 +509,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
{% endif %} {% 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> </div>
</div> </div>
@@ -726,38 +674,112 @@ body.focus-mode {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
overflow-y: auto; overflow: hidden;
background: #f9fafb; background: #f9fafb;
padding: 1rem; 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 { .focus-mode-student {
max-height: calc(100vh - 80px); /* Header compact + padding */ height: 100%;
overflow-y: auto; min-height: 100%;
max-height: none;
overflow: hidden;
background: white; background: white;
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
} }
/* Mode focus - Améliorer la lisibilité */ /* Mode focus - Améliorer la lisibilité */
.focus-mode-student textarea { .focus-mode-student textarea {
min-height: 100px; min-height: 150px;
max-height: 200px; max-height: 50vh;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
resize: vertical; 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 { .focus-mode-student .px-6 {
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
/* Retirer flex: 1 car ce n'est que le header */
flex-shrink: 0;
} }
.focus-mode-student .py-4 { /* Mode focus - Section détails s'étend pour utiliser l'espace disponible */
padding-top: 1rem; .focus-mode-student [data-student-details] {
padding-bottom: 1rem; 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 */ /* Mode focus - Réduction des espacements */
@@ -1301,5 +1323,227 @@ button:disabled {
text-align: center; 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> </style>
{% endblock %} {% endblock %}