Files
notytex/templates/class_council_preparation.html
2025-08-12 06:41:39 +02:00

1305 lines
61 KiB
HTML

{% extends "base.html" %}
{% from 'components/common/macros.html' import hero_section %}
{% block title %}Préparation Conseil de Classe - {{ class_group.name }} - T{{ trimester }}{% endblock %}
{# Override le style du main container pour éviter le clipping des hover effects #}
{% block main_class %}w-full px-8 py-8 bg-gray-100{% endblock %}
{% block content %}
<div class="council-preparation" data-council-preparation data-class-id="{{ class_group.id }}" data-trimester="{{ trimester }}">
<!-- Loading overlay -->
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50" data-loading-overlay>
<div class="bg-white rounded-xl p-6 flex items-center space-x-3">
<svg class="animate-spin w-6 h-6 text-purple-600" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-gray-700 font-medium">Chargement...</span>
</div>
</div>
<div class="max-w-7xl mx-auto">
<div class="space-y-8" style="overflow: visible; padding: 8px; margin: -8px;">
{# 1. Hero Section #}
{% set meta_info = [
{
'icon': '<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20"><path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/></svg>',
'text': council_data.total_students ~ ' élèves'
},
{
'icon': '<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>',
'text': 'Trimestre ' ~ trimester
},
{
'icon': '<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1117.07 2.93 10 10 0 012.93 17.07zM11.4 10.6L9 8.2V4.5a.5.5 0 00-1 0v4a.5.5 0 00.15.35l2.7 2.7a.5.5 0 00.7-.7z"/></svg>',
'text': council_data.completed_appreciations ~ '/' ~ council_data.total_students ~ ' appréciations rédigées'
}
] %}
<div class="list-mode-hero">
{{ hero_section(
title="📊 Préparation Conseil de Classe",
subtitle="Rédaction des appréciations • " + class_group.name,
meta_info=meta_info,
gradient_class="from-purple-600 to-orange-500"
) }}
</div>
{# Breadcrumb de retour et sélecteur de trimestre #}
<div class="list-mode-breadcrumb flex flex-col sm:flex-row sm:items-center justify-between space-y-3 sm:space-y-0">
<div class="flex items-center text-sm text-gray-600">
<a href="{{ url_for('classes.dashboard', id=class_group.id) }}" class="hover:text-blue-600 transition-colors flex items-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
Retour au dashboard de classe
</a>
</div>
{# Sélecteur de trimestre #}
<div class="flex items-center space-x-3">
<label class="text-sm font-medium text-gray-700">Trimestre :</label>
<select id="trimester-selector"
data-trimester-selector
class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500 bg-white shadow-sm">
<option value="1" {% if trimester == 1 %}selected{% endif %}>Trimestre 1</option>
<option value="2" {% if trimester == 2 %}selected{% endif %}>Trimestre 2</option>
<option value="3" {% if trimester == 3 %}selected{% endif %}>Trimestre 3</option>
</select>
</div>
</div>
{# 2. Filtres et Actions Principales #}
<div class="list-mode-filters-section bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div class="flex flex-col lg:flex-row lg:items-center justify-between space-y-4 lg:space-y-0">
{# Recherche et filtres - masqués en mode focus #}
<div class="list-mode-filters flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-4">
<div class="relative">
<input type="text"
data-search-students
placeholder="Rechercher un élève..."
class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm w-64">
<svg class="w-4 h-4 text-gray-400 absolute left-3 top-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/>
</svg>
</div>
<select data-sort-students class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
<option value="alphabetical">Trier par nom</option>
<option value="average">Trier par moyenne</option>
<option value="status">Trier par statut</option>
</select>
<select data-filter-status class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
<option value="all">Tous les élèves</option>
<option value="completed">Appréciations rédigées</option>
<option value="pending">À rédiger</option>
<option value="struggling">Élèves en difficulté</option>
</select>
</div>
{# Actions globales #}
<div class="flex space-x-3">
{# Toujours visible : bouton mode focus #}
<button data-toggle-focus-mode
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="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>
<span data-focus-mode-text>Mode Focus</span>
</button>
{# Actions masquées en mode focus #}
<div class="list-mode-actions space-x-3 flex">
<button data-export-pdf
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="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
Exporter PDF
</button>
<button data-class-synthesis
class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg hover:from-blue-600 hover:to-blue-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 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>
Synthèse classe
</button>
</div>
</div>
</div>
{# Compteur de résultats et contrôles mode focus #}
<div class="mt-4 pt-4 border-t border-gray-200">
{# Mode liste - compteur normal #}
<div class="list-mode-controls">
<p class="text-sm text-gray-600" data-results-counter>
{{ student_summaries|length }} élèves affichés
</p>
</div>
{# Mode focus - contrôles de navigation #}
<div class="focus-mode-controls hidden">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<button data-focus-prev
class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
Précédent
</button>
<div class="text-sm text-gray-600 bg-gray-50 px-3 py-2 rounded-lg">
Élève <span data-focus-current>1</span> sur <span data-focus-total>{{ student_summaries|length }}</span>
</div>
<button data-focus-next
class="inline-flex items-center px-3 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed">
Suivant
<svg class="w-4 h-4 ml-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
</svg>
</button>
</div>
<div class="flex items-center space-x-2 text-xs text-gray-500">
<span>Navigation: ← → | Échap pour quitter</span>
</div>
</div>
</div>
</div>
</div>
{# Header compact mode focus - visible uniquement en mode focus #}
<div class="focus-mode-header hidden bg-white rounded-lg shadow-sm border border-gray-200 p-3 mb-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<button data-toggle-focus-mode
class="inline-flex items-center px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-md transition-colors text-sm font-medium">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd"/>
</svg>
Mode Liste
</button>
<div class="text-sm text-gray-600">
<strong>{{ class_group.name }}</strong> • Trimestre {{ trimester }}
</div>
</div>
<div class="flex items-center space-x-3">
<button data-focus-prev
class="inline-flex items-center px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
</button>
<div class="text-sm text-gray-600 bg-gray-50 px-2 py-1 rounded">
<span data-focus-current>1</span>/<span data-focus-total>{{ student_summaries|length }}</span>
</div>
<button data-focus-next
class="inline-flex items-center px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
</svg>
</button>
<div class="text-xs text-gray-500">← → Échap</div>
</div>
</div>
</div>
{# 3. Liste des Élèves (Cards Expandables) #}
<div class="students-display">
{# Mode liste - tous les élèves visibles #}
<div class="list-mode-display space-y-4" data-students-container>
{% for summary in student_summaries %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-all duration-300
{% if summary.performance_status == 'struggling' %}border-l-4 border-l-red-400{% endif %}
{% if summary.performance_status == 'excellent' %}border-l-4 border-l-green-400{% endif %}
{% if summary.performance_status == 'good' %}border-l-4 border-l-blue-400{% endif %}
{% if summary.performance_status == 'no_data' %}border-l-4 border-l-gray-400{% endif %}"
data-student-card="{{ summary.student.id }}"
data-student-name="{{ summary.student.last_name }} {{ summary.student.first_name }}"
data-student-average="{{ summary.overall_average or 0 }}"
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 %}
</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>
{# 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) }}
</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 #}
<div class="flex items-center">
{% 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>
Rédigée
</span>
{% else %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-orange-100 text-orange-800">
<span class="w-2 h-2 bg-orange-400 rounded-full mr-1"></span>
À rédiger
</span>
{% endif %}
</div>
{# Indicateur de sauvegarde #}
<div class="hidden" data-save-indicator="{{ summary.student.id }}"></div>
</div>
</div>
</div>
{# Contenu expandable #}
<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 #}
{% 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 %}
</div>
</div>
</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>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</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>
{% endfor %}
</div>
{# Mode focus - un seul élève visible #}
<div class="focus-mode-display hidden" data-focus-container>
{# Le contenu sera géré dynamiquement par JavaScript #}
{# L'élève affiché sera cloné depuis la liste et affiché ici #}
</div>
</div>
{# Message si aucun élève #}
{% if not student_summaries %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg class="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/>
</svg>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucun élève trouvé</h3>
<p class="text-gray-500">Aucune donnée disponible pour ce trimestre ou cette classe.</p>
</div>
{% endif %}
{# 4. Statistiques de classe (sidebar ou bottom) - masqué en mode focus #}
{% if class_statistics and class_statistics.mean %}
<div class="list-mode-stats bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<svg class="w-5 h-5 text-orange-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>
Statistiques de la classe
</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-center">
<div class="text-2xl font-bold text-orange-900">{{ "%.1f"|format(class_statistics.mean) }}</div>
<div class="text-sm text-orange-700">Moyenne générale</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-gray-700">{{ "%.1f"|format(class_statistics.min) }}</div>
<div class="text-xs text-gray-500">Minimum</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-gray-700">{{ "%.1f"|format(class_statistics.max) }}</div>
<div class="text-xs text-gray-500">Maximum</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-gray-700">{{ "%.1f"|format(class_statistics.median) }}</div>
<div class="text-xs text-gray-500">Médiane</div>
</div>
</div>
{# Distribution des performances #}
<div class="mt-6">
<h4 class="text-sm font-medium text-gray-700 mb-3">Répartition des performances</h4>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<div class="bg-green-50 p-2 rounded text-center">
<div class="text-lg font-bold text-green-800">{{ class_statistics.performance_distribution.excellent }}</div>
<div class="text-xs text-green-600">Excellent (≥16)</div>
</div>
<div class="bg-blue-50 p-2 rounded text-center">
<div class="text-lg font-bold text-blue-800">{{ class_statistics.performance_distribution.good }}</div>
<div class="text-xs text-blue-600">Bien (14-16)</div>
</div>
<div class="bg-yellow-50 p-2 rounded text-center">
<div class="text-lg font-bold text-yellow-800">{{ class_statistics.performance_distribution.average }}</div>
<div class="text-xs text-yellow-600">Moyen (10-14)</div>
</div>
<div class="bg-red-50 p-2 rounded text-center">
<div class="text-lg font-bold text-red-800">{{ class_statistics.performance_distribution.struggling }}</div>
<div class="text-xs text-red-600">Difficulté (<10)</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div> <!-- Fermeture max-w-7xl -->
</div> <!-- Fermeture council-preparation -->
{% endblock %}
{% block head %}
<script src="{{ url_for('static', filename='js/CouncilPreparation.js') }}"></script>
<style>
/* Styles spécifiques pour la page conseil */
.council-preparation {
overflow: visible !important;
}
.council-preparation .grid {
overflow: visible !important;
}
.council-preparation [class*="transform"][class*="hover:scale"] {
transform-origin: center;
}
/* Assurer que les conteneurs parents permettent l'overflow */
main {
overflow: visible !important;
}
/* Styles pour le mode focus */
.focus-mode-student {
animation: focusFadeIn 0.3s ease-out;
transform: scale(1);
}
@keyframes focusFadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Mode focus - Carte élève élargie */
.focus-mode-display .focus-mode-student {
max-width: none !important;
width: 100%;
margin: 0 auto;
}
/* Mode focus - Section appréciation toujours visible */
.focus-mode-student [data-student-details] {
display: block !important;
height: auto !important;
opacity: 1 !important;
}
/* Mode focus - Interface ultra-compacte */
body.focus-mode {
overflow: hidden; /* Empêcher le scroll global */
}
.focus-mode-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
background: white;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.focus-mode-display {
position: fixed;
top: 60px; /* Hauteur du header compact */
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
background: #f9fafb;
padding: 1rem;
}
/* Mode focus - Optimisation pour éviter le scroll */
.focus-mode-student {
max-height: calc(100vh - 80px); /* Header compact + padding */
overflow-y: auto;
background: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* Mode focus - Améliorer la lisibilité */
.focus-mode-student textarea {
min-height: 100px;
max-height: 200px;
font-size: 14px;
line-height: 1.5;
resize: vertical;
}
/* Mode focus - Compactage de la carte */
.focus-mode-student .px-6 {
padding-left: 1rem;
padding-right: 1rem;
}
.focus-mode-student .py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
/* Mode focus - Réduction des espacements */
.focus-mode-student .space-y-4 > * + * {
margin-top: 0.75rem;
}
.focus-mode-student .mb-2 {
margin-bottom: 0.5rem;
}
/* ========== BARRES DE PROGRESSION COMPÉTENCES/DOMAINES ========== */
/* ========== BARRES DE PROGRESSION SEGMENTÉES ========== */
/* Conteneur principal des barres de progression */
.competence-progress-bar,
.domain-progress-bar {
margin-bottom: 0.75rem;
}
.progress-bar-container {
position: relative;
cursor: pointer;
transition: all 0.3s ease;
}
/* Conteneur segmenté avec apparence moderne et claire */
.segmented-progress {
border-radius: 10px;
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.08);
padding: 6px;
min-height: 44px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.segmented-progress:hover {
border-color: rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 0.95);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.1);
}
.segmented-progress:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Barre segmentée - état expandé par défaut */
.segmented-progress-bar {
display: flex;
height: 36px;
gap: 3px;
padding: 3px;
border-radius: 6px;
overflow: visible;
position: relative;
background: transparent;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* État contracté (optionnel, si besoin futur) */
.segmented-progress-bar[data-expanded="false"] {
height: 16px;
gap: 0;
padding: 0;
background: #e5e7eb;
overflow: hidden;
}
/* Segments individuels - état expandé par défaut */
.progress-segment {
height: 30px;
border-radius: 6px;
position: relative;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
min-width: 8px; /* Minimum pour les petites contributions */
border: 2px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
animation: segmentAppear 0.6s ease-out forwards;
overflow: hidden;
}
/* État contracté des segments (optionnel) */
.segmented-progress-bar[data-expanded="false"] .progress-segment {
height: 100%;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: none;
}
/* Séparateurs visuels entre segments (état contracté seulement) */
.segmented-progress-bar:not([data-expanded="true"]) .progress-segment:not(:last-child) {
border-right: 2px solid rgba(255, 255, 255, 0.8);
}
/* Hover effects pour les segments */
.progress-segment:hover {
filter: brightness(1.15);
transform: scaleY(1.05);
z-index: 10;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}
.segmented-progress-bar[data-expanded="true"] .progress-segment:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
/* Focus pour accessibilité clavier */
.progress-segment:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
z-index: 15;
}
/* Labels des segments - visibles par défaut */
.segment-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.7rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
opacity: 1;
transition: opacity 0.3s ease;
pointer-events: none;
white-space: nowrap;
max-width: 90%;
overflow: hidden;
text-overflow: ellipsis;
}
/* État contracté des labels (optionnel) */
.segmented-progress-bar[data-expanded="false"] .segment-label {
opacity: 0;
font-size: 0.65rem;
}
/* Tooltips améliorés avec fond clair */
.progress-segment:hover::after {
content: attr(data-assessment-title) ': ' attr(data-earned-this) ' pts (' attr(data-assessment-performance) '%)';
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.98);
color: #374151;
padding: 8px 12px;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
z-index: 1001;
pointer-events: none;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.08);
animation: tooltipFadeIn 0.2s ease-out;
}
.progress-segment:hover::before {
content: '';
position: absolute;
bottom: calc(100% + 2px);
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: rgba(255, 255, 255, 0.98);
z-index: 1001;
pointer-events: none;
}
/* Animation d'apparition des segments */
@keyframes segmentAppear {
0% {
opacity: 0;
transform: scaleX(0);
}
60% {
opacity: 0.9;
transform: scaleX(1.02);
}
100% {
opacity: 1;
transform: scaleX(1);
}
}
@keyframes tooltipFadeIn {
0% {
opacity: 0;
transform: translateX(-50%) translateY(4px);
}
100% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* Animation séquentielle des segments */
.progress-segment:nth-child(1) { animation-delay: 0.1s; }
.progress-segment:nth-child(2) { animation-delay: 0.2s; }
.progress-segment:nth-child(3) { animation-delay: 0.3s; }
.progress-segment:nth-child(4) { animation-delay: 0.4s; }
.progress-segment:nth-child(5) { animation-delay: 0.5s; }
.progress-segment:nth-child(6) { animation-delay: 0.6s; }
/* ========== RESPONSIVE ET ACCESSIBILITÉ ========== */
/* Responsive pour mobile */
@media (max-width: 640px) {
.segmented-progress {
padding: 3px;
min-height: 24px;
}
.segmented-progress-bar {
height: 20px;
}
.segmented-progress-bar[data-expanded="true"] {
height: 44px; /* Touch target minimum 44px */
gap: 2px;
padding: 2px;
}
.segmented-progress-bar[data-expanded="true"] .progress-segment {
height: 40px;
min-width: 44px; /* Touch target minimum */
}
.segment-label {
font-size: 0.6rem;
}
.segmented-progress-bar[data-expanded="true"] .segment-label {
font-size: 0.65rem;
}
.progress-segment:hover::after {
font-size: 0.7rem;
padding: 6px 10px;
bottom: calc(100% + 12px);
}
}
/* Responsive pour très petits écrans */
@media (max-width: 480px) {
.competence-progress-bar .flex.items-center.justify-between,
.domain-progress-bar .flex.items-center.justify-between {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.progress-segment:hover::after {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
bottom: auto;
max-width: 280px;
text-align: center;
font-size: 0.75rem;
padding: 8px 12px;
}
}
/* Mode sombre - Réduction de mouvement pour accessibilité */
@media (prefers-reduced-motion: reduce) {
.progress-segment,
.segmented-progress,
.segmented-progress-bar,
.segment-label,
.progress-summary {
animation: none !important;
transition: none !important;
}
.progress-segment:hover {
transform: none;
}
}
/* Contraste élevé pour accessibilité */
@media (prefers-contrast: high) {
.segmented-progress {
border: 3px solid #000000;
}
.progress-segment {
border: 2px solid #ffffff;
filter: contrast(1.2);
}
.segment-label {
text-shadow: 1px 1px 0 #000000;
font-weight: 700;
}
}
/* Mode focus amélioré pour les barres segmentées */
.focus-mode-student .segmented-progress {
padding: 2px;
min-height: 16px;
}
.focus-mode-student .segmented-progress-bar {
height: 12px;
}
.focus-mode-student .segmented-progress-bar[data-expanded="true"] {
height: 28px;
}
.focus-mode-student .segmented-progress-bar[data-expanded="true"] .progress-segment {
height: 24px;
}
/* ========== CLASSES UTILITAIRES D'ACCESSIBILITÉ ========== */
/* Screen reader only content */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Skip link pour navigation au clavier */
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
border-radius: 4px;
z-index: 9999;
transition: top 0.3s;
}
.skip-link:focus {
top: 6px;
}
/* Focus visible amélioré pour tous les éléments interactifs */
.progress-segment:focus-visible,
.segmented-progress:focus-visible {
outline: 3px solid #4f46e5;
outline-offset: 2px;
border-radius: 4px;
}
/* Indicateur de charge pour les animations */
.progress-segment[aria-busy="true"]::after {
content: '';
position: absolute;
top: 2px;
right: 2px;
width: 8px;
height: 8px;
border: 1px solid rgba(255,255,255,0.8);
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Mode sombre - support système */
@media (prefers-color-scheme: dark) {
.segmented-progress {
background: rgba(31, 41, 55, 0.8);
border-color: rgba(75, 85, 99, 0.4);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.segmented-progress:hover {
background: rgba(31, 41, 55, 0.95);
border-color: rgba(75, 85, 99, 0.6);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
}
.progress-segment {
border-color: rgba(255, 255, 255, 0.2);
}
.segment-label {
color: rgba(255, 255, 255, 0.9);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.progress-segment:hover::after {
background: rgba(17, 24, 39, 0.95);
color: #f9fafb;
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.progress-segment:hover::before {
border-top-color: rgba(17, 24, 39, 0.95);
}
}
/* ========== CLASSES DE PERFORMANCE POUR LES NOUVEAUX SEGMENTS ========== */
/* Indicateurs visuels de performance pour les nouveaux segments */
.progress-segment.segment-excellent {
box-shadow: inset 0 0 0 2px rgba(34, 197, 94, 0.4);
}
.progress-segment.segment-good {
box-shadow: inset 0 0 0 2px rgba(59, 130, 246, 0.4);
}
.progress-segment.segment-average {
box-shadow: inset 0 0 0 2px rgba(245, 158, 11, 0.4);
}
.progress-segment.segment-struggling {
box-shadow: inset 0 0 0 2px rgba(239, 68, 68, 0.4);
}
/* Effet de pulsation pour les segments excellents */
.progress-segment.segment-excellent:hover {
animation: excellentPulse 0.6s ease-in-out;
}
@keyframes excellentPulse {
0%, 100% { filter: brightness(1.2); }
50% { filter: brightness(1.4); }
}
/* ========== ANIMATION D'APPARITION DE LA SECTION ========== */
/* Animation d'apparition pour toute la section */
.competence-domain-section {
animation: slideInUp 0.5s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Boutons de navigation désactivés */
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Transitions fluides pour les changements de mode */
.list-mode-display,
.focus-mode-display {
transition: all 0.3s ease-out;
}
.hidden {
display: none !important;
}
/* Animations fluides pour l'expansion des cards */
[data-student-details] {
overflow: hidden;
transition: all 0.3s ease-out;
}
/* États des textareas */
[data-appreciation-textarea]:focus {
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
}
/* Indicateurs de sauvegarde */
[data-save-indicator] {
transition: all 0.2s ease-in-out;
}
/* Hover effects pour les cards */
[data-student-card]:hover {
transform: translateY(-1px);
}
/* Styles pour l'aperçu des évaluations */
.assessment-preview-pills {
transition: all 0.2s ease-in-out;
}
.assessment-preview-pills:hover {
transform: scale(1.05);
}
/* Responsive: masquer l'aperçu sur très petits écrans */
@media (max-width: 640px) {
.assessment-preview-mobile-hide {
display: none !important;
}
}
/* Animation pour les indicateurs de tendance */
[title*="Tendance"] svg {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Amélioration de la lisibilité sur mobile */
@media (max-width: 768px) {
[data-student-card] .flex.items-center.justify-between {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
}
[data-student-card] .flex.flex-col.items-end {
align-items: stretch;
text-align: center;
}
}
</style>
{% endblock %}