620 lines
38 KiB
HTML
620 lines
38 KiB
HTML
{% extends "base.html" %}
|
|
{% from 'components/common/macros.html' import hero_section, action_card, progress_indicator, stat_card %}
|
|
|
|
{% block title %}{{ class_group.name }} - Dashboard{% 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="class-dashboard" data-class-dashboard data-class-id="{{ class_group.id }}">
|
|
<!-- Live region pour les annonces d'accessibilité -->
|
|
<div aria-live="polite" aria-atomic="true" class="live-region" data-live-region></div>
|
|
|
|
<!-- Loading overlay -->
|
|
<div class="loading-overlay hidden" data-loading-overlay>
|
|
<div class="loading-spinner"></div>
|
|
</div>
|
|
|
|
<!-- Error container -->
|
|
<div class="hidden" data-error-container></div>
|
|
|
|
<div class="max-w-7xl mx-auto">
|
|
<div class="space-y-8" style="overflow: visible; padding: 8px; margin: -8px;">
|
|
{# 1. Hero Section avec Contexte de Classe #}
|
|
{% 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': class_group.students|length ~ ' é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 ' ~ (selected_trimester or 'Global')
|
|
},
|
|
{
|
|
'icon': '<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>',
|
|
'text': (class_group.assessments|selectattr('grading_progress.status', 'ne', 'completed')|list|length) ~ ' évaluations en attente'
|
|
}
|
|
] %}
|
|
|
|
{{ hero_section(
|
|
title=class_group.name ~ " 🏫",
|
|
subtitle="Dashboard de gestion de classe",
|
|
meta_info=meta_info,
|
|
gradient_class="from-indigo-600 to-purple-600"
|
|
) }}
|
|
|
|
{# Breadcrumb de retour #}
|
|
<div class="flex items-center text-sm text-gray-600">
|
|
<a href="{{ url_for('classes') }}" 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>
|
|
Toutes les classes
|
|
</a>
|
|
</div>
|
|
|
|
{# 2. Actions Principales avec Code Couleur Prioritaire #}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{# Action ROUGE - Corrections en attente (priorité max) #}
|
|
{% set pending_assessments = class_group.assessments|selectattr('grading_progress.status', 'ne', 'completed')|list %}
|
|
{% if pending_assessments %}
|
|
<a href="{{ url_for('grading.assessment_grading', assessment_id=pending_assessments[0].id) }}"
|
|
class="group bg-gradient-to-r from-red-500 to-red-600 text-white rounded-xl p-6 hover:from-red-600 hover:to-red-700 transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
|
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold mb-1">Terminer corrections</h3>
|
|
<p class="text-sm opacity-90">{{ pending_assessments|length }} évaluation(s) en attente</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
{% endif %}
|
|
|
|
{# Action VERTE - Nouvelle évaluation #}
|
|
<a href="{{ url_for('assessments.new') }}?class_id={{ class_group.id }}"
|
|
class="group bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl p-6 hover:from-green-600 hover:to-green-700 transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
|
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold mb-1">Nouvelle évaluation</h3>
|
|
<p class="text-sm opacity-90">Créer pour {{ class_group.name }}</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
{# Action BLEUE - Gérer les élèves #}
|
|
<a href="{{ url_for('classes.students', id=class_group.id) }}"
|
|
class="group bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl p-6 hover:from-blue-600 hover:to-blue-700 transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
|
|
<svg class="w-6 h-6" 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>
|
|
<div>
|
|
<h3 class="text-lg font-bold mb-1">Gérer élèves</h3>
|
|
<p class="text-sm opacity-90">{% if class_group._current_students %}{{ class_group._current_students|length }}{% else %}{{ class_group.students|length }}{% endif %} élèves inscrits</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
{# Action ORANGE - Préparation conseil de classe #}
|
|
<a href="{{ url_for('classes.council_preparation', id=class_group.id, trimestre=selected_trimester or 2) }}"
|
|
class="group bg-gradient-to-r from-orange-500 to-orange-600 text-white rounded-xl p-6 hover:from-orange-600 hover:to-orange-700 transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
|
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M17 20a1 1 0 01-1-1v-1H4v1a1 1 0 11-2 0v-1a2 2 0 01-2-2V6a2 2 0 012-2h16a2 2 0 012 2v10a2 2 0 01-2 2v1a1 1 0 01-1 1zM2 6v10h16V6H2zm5 2a1 1 0 011 1v4a1 1 0 11-2 0V9a1 1 0 011-1zm4 0a1 1 0 011 1v4a1 1 0 11-2 0V9a1 1 0 011-1zm4 0a1 1 0 011 1v4a1 1 0 11-2 0V9a1 1 0 011-1z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold mb-1">Préparer conseil</h3>
|
|
<p class="text-sm opacity-90">Appréciations élèves</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
{# Action VIOLETTE - Import CSV d'élèves #}
|
|
<a href="javascript:openCSVImportModal()"
|
|
class="group bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl p-6 hover:from-purple-600 hover:to-purple-700 transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center mr-4 group-hover:bg-white/30 transition-colors">
|
|
<svg class="w-6 h-6" 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-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L7.707 6.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold mb-1">Import CSV</h3>
|
|
<p class="text-sm opacity-90">Ajouter plusieurs élèves</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
{# 3. Dashboard Statistiques par Trimestre #}
|
|
<div class="bg-white shadow rounded-xl">
|
|
{# Header de la section #}
|
|
<div class="px-6 py-5 border-b border-gray-200 flex justify-between items-center">
|
|
<div class="flex items-center">
|
|
<h2 class="text-xl font-bold text-gray-900">Statistiques par trimestre</h2>
|
|
<span class="ml-3 bg-blue-100 text-blue-800 text-sm px-3 py-1 rounded-full font-medium">
|
|
Données temps réel
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{# Navigation tabs des trimestres #}
|
|
<div class="px-6 pt-6">
|
|
<nav class="trimester-tabs" role="tablist" aria-label="Navigation par trimestre">
|
|
<button data-trimester-tab="1"
|
|
class="trimester-tab {% if selected_trimester == 1 %}active{% endif %}"
|
|
role="tab"
|
|
aria-selected="{% if selected_trimester == 1 %}true{% else %}false{% endif %}">
|
|
<div class="flex items-center">
|
|
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-blue-600 mr-2"></div>
|
|
Trimestre 1
|
|
</div>
|
|
</button>
|
|
<button data-trimester-tab="2"
|
|
class="trimester-tab {% if selected_trimester == 2 %}active{% endif %}"
|
|
role="tab"
|
|
aria-selected="{% if selected_trimester == 2 %}true{% else %}false{% endif %}">
|
|
<div class="flex items-center">
|
|
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-green-500 to-green-600 mr-2"></div>
|
|
Trimestre 2
|
|
</div>
|
|
</button>
|
|
<button data-trimester-tab="3"
|
|
class="trimester-tab {% if selected_trimester == 3 %}active{% endif %}"
|
|
role="tab"
|
|
aria-selected="{% if selected_trimester == 3 %}true{% else %}false{% endif %}">
|
|
<div class="flex items-center">
|
|
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-orange-500 to-orange-600 mr-2"></div>
|
|
Trimestre 3
|
|
</div>
|
|
</button>
|
|
<button data-trimester-tab="global"
|
|
class="trimester-tab {% if not selected_trimester %}active{% endif %}"
|
|
role="tab"
|
|
aria-selected="{% if not selected_trimester %}true{% else %}false{% endif %}">
|
|
<div class="flex items-center">
|
|
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-purple-500 to-purple-600 mr-2"></div>
|
|
Global
|
|
</div>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
{# 4 Cards de statistiques en grid responsive #}
|
|
<div class="px-6 pb-6" data-stats-content>
|
|
<div class="stats-grid" data-stats-cards>
|
|
{# Card 1 - Domaines #}
|
|
<div class="bg-white rounded-xl shadow-lg p-6" data-stats-card="domains">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-green-900 flex items-center">
|
|
<svg class="w-5 h-5 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V8zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2z" clip-rule="evenodd"/>
|
|
</svg>
|
|
Domaines
|
|
</h3>
|
|
<div class="w-10 h-10 bg-green-500 rounded-xl flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V8zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-green-900" data-domains-count>0</div>
|
|
<div class="text-sm text-green-700">domaines évalués</div>
|
|
</div>
|
|
<div class="space-y-2" data-domains-list>
|
|
<!-- Dynamic content populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Card 2 - Compétences #}
|
|
<div class="bg-white rounded-xl shadow-lg p-6" data-stats-card="competences">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-purple-900 flex items-center">
|
|
<svg class="w-5 h-5 text-purple-500 mr-2" 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
|
|
</h3>
|
|
<div class="w-10 h-10 bg-purple-500 rounded-xl flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-white" 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>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-purple-900" data-competences-count>0</div>
|
|
<div class="text-sm text-purple-700">compétences évaluées</div>
|
|
</div>
|
|
<div class="space-y-2" data-competences-list>
|
|
<!-- Dynamic content populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Card 3 - Résultats #}
|
|
<div class="bg-white rounded-xl shadow-lg p-6" data-stats-card="results">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-orange-900 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>
|
|
Résultats
|
|
</h3>
|
|
<div class="w-10 h-10 bg-orange-500 rounded-xl flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-white" 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>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-orange-900" data-result="mean">0.0</div>
|
|
<div class="text-sm text-orange-700">moyenne générale</div>
|
|
<div class="text-xs text-orange-600 mt-1" data-result="assessments_count">0 évaluation(s)</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3 text-sm mb-4">
|
|
<div class="bg-orange-50 rounded-lg p-3 border border-orange-100">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-orange-800 font-medium">Min:</span>
|
|
<span class="text-orange-900 font-bold" data-result="min">0.0</span>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-3 border border-orange-100">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-orange-800 font-medium">Max:</span>
|
|
<span class="text-orange-900 font-bold" data-result="max">0.0</span>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-3 border border-orange-100">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-orange-800 font-medium">Médiane:</span>
|
|
<span class="text-orange-900 font-bold" data-result="median">0.0</span>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-3 border border-orange-100">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-orange-800 font-medium">Écart-type:</span>
|
|
<span class="text-orange-900 font-bold" data-result="std_dev">0.0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Histogramme des moyennes des élèves #}
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100">
|
|
<h4 class="text-sm font-semibold text-orange-900 mb-3 flex items-center">
|
|
<svg class="w-4 h-4 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>
|
|
Distribution des moyennes
|
|
</h4>
|
|
<div class="relative h-32">
|
|
<canvas id="studentAveragesChart" class="w-full h-full"></canvas>
|
|
<div class="absolute inset-0 flex items-center justify-center text-orange-600 text-sm" data-chart-no-data style="display: none;">
|
|
Aucune donnée disponible
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 4. Tableau de Bord Évaluations #}
|
|
<div class="bg-white shadow rounded-xl">
|
|
<div class="px-6 py-5 border-b border-gray-200 flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<h2 class="text-xl font-bold text-gray-900">
|
|
Toutes les Évaluations
|
|
</h2>
|
|
<span class="ml-3 bg-blue-100 text-blue-800 text-sm px-3 py-1 rounded-full font-medium">
|
|
{{ class_group.assessments|length }} évaluations
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="px-6 py-6">
|
|
{% if class_group.assessments %}
|
|
<div class="space-y-4">
|
|
{% for assessment in class_group.assessments|sort(attribute='date', reverse=True) %}
|
|
<div class="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md transition-shadow duration-300 group">
|
|
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="flex items-center space-x-4 flex-1 min-w-0">
|
|
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-bold text-sm">
|
|
{{ assessment.title[0].upper() }}
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-blue-700 transition-colors truncate">{{ assessment.title }}</h3>
|
|
<div class="flex items-center text-xs text-gray-500 space-x-2">
|
|
<span class="flex items-center">
|
|
<svg class="w-3 h-3 mr-1" 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>
|
|
{{ assessment.date.strftime('%d/%m/%Y') }}
|
|
</span>
|
|
<span>•</span>
|
|
<span>Coeff. {{ assessment.coefficient }}</span>
|
|
<span>•</span>
|
|
<span>T{{ assessment.trimester }}</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
<div class="flex items-center space-x-3 flex-shrink-0">
|
|
{# Indicateur de progression #}
|
|
{{ progress_indicator(assessment.grading_progress, clickable=True, assessment_id=assessment.id, compact=True) }}
|
|
|
|
{# Action directe selon statut #}
|
|
{% if assessment.grading_progress.status == 'completed' %}
|
|
<a href="{{ url_for('assessments.results', id=assessment.id) }}"
|
|
class="text-xs bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white px-3 py-1.5 rounded-lg transition-all duration-300 font-medium transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
Voir résultats
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
|
|
class="text-xs bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-3 py-1.5 rounded-lg transition-all duration-300 font-medium transform hover:scale-[1.02] shadow-lg hover:shadow-xl">
|
|
Noter
|
|
</a>
|
|
{% endif %}
|
|
|
|
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-blue-600 group-hover:text-blue-800 opacity-70 group-hover:opacity-100 transition-all">
|
|
<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>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-12">
|
|
<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="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/>
|
|
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2v1a1 1 0 102 0V3a2 2 0 012 2v6a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 2a1 1 0 000 2h2a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-sm font-medium text-gray-900 mb-1">
|
|
Aucune évaluation pour cette classe
|
|
</h3>
|
|
<p class="text-sm text-gray-500 mb-4">Créez votre première évaluation pour cette classe</p>
|
|
<a href="{{ url_for('assessments.new') }}?class_id={{ class_group.id }}"
|
|
class="inline-flex items-center text-sm bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white px-4 py-2 rounded-lg transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-[1.02]">
|
|
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/>
|
|
</svg>
|
|
Créer une évaluation
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{# 5. Aperçu Élèves (simplifié) #}
|
|
<div class="bg-white shadow rounded-xl">
|
|
<div class="px-6 py-5 border-b border-gray-200 flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<h2 class="text-xl font-bold text-gray-900">Élèves de la classe</h2>
|
|
<span class="ml-3 bg-green-100 text-green-800 text-sm px-3 py-1 rounded-full font-medium">
|
|
{{ class_group.students|length }} élèves
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="px-6 py-6">
|
|
{% if class_group.students %}
|
|
<div class="text-center">
|
|
<div class="flex items-center justify-center space-x-4 text-sm text-gray-600 mb-4">
|
|
<span class="flex items-center">
|
|
<svg class="w-4 h-4 mr-1" 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>
|
|
Effectif complet : {{ class_group.students|length }} élèves
|
|
</span>
|
|
</div>
|
|
<a href="{{ url_for('classes.students', id=class_group.id) }}"
|
|
class="inline-flex items-center text-sm bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white px-4 py-2 rounded-lg transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-[1.02]">
|
|
<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>
|
|
Voir tous les élèves
|
|
</a>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<div class="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg class="w-6 h-6 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-sm font-medium text-gray-900 mb-2">Aucun élève inscrit</h3>
|
|
<p class="text-sm text-gray-500 mb-4">Ajoutez des élèves à cette classe pour commencer les évaluations</p>
|
|
<a href="{{ url_for('classes.students', id=class_group.id) }}"
|
|
class="inline-flex items-center text-sm bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white px-4 py-2 rounded-lg transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-[1.02]">
|
|
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/>
|
|
</svg>
|
|
Ajouter des élèves
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> <!-- Fermeture max-w-7xl -->
|
|
|
|
{# Modal d'import CSV #}
|
|
<div id="csvImportModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden items-center justify-center z-50">
|
|
<div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Import CSV - {{ class_group.name }}</h3>
|
|
<button type="button" onclick="closeCSVImportModal()"
|
|
class="text-gray-400 hover:text-gray-600 transition-colors">
|
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-blue-400" 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>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="text-sm font-medium text-blue-800">Format attendu</h4>
|
|
<p class="text-sm text-blue-700 mt-1">
|
|
Fichier CSV avec séparateur <strong>;</strong><br>
|
|
Première colonne : <strong>"NOM Prénoms"</strong> (ex: "DUPONT Marie Claire")<br>
|
|
Les élèves déjà existants seront ignorés par défaut.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="csvImportForm" method="post" action="{{ url_for('classes.import_students_csv', id=class_group.id) }}" enctype="multipart/form-data">
|
|
<div class="mb-4">
|
|
<label for="csv_file_dashboard" class="block text-sm font-medium text-gray-700 mb-2">Fichier CSV</label>
|
|
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md hover:border-gray-400 transition-colors">
|
|
<div class="space-y-1 text-center">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
|
|
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
</svg>
|
|
<div class="flex text-sm text-gray-600">
|
|
<label for="csv_file_dashboard" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-600 hover:text-blue-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500">
|
|
<span>Sélectionner un fichier</span>
|
|
<input id="csv_file_dashboard" name="csv_file" type="file" accept=".csv" required class="sr-only">
|
|
</label>
|
|
<p class="pl-1">ou glisser-déposer</p>
|
|
</div>
|
|
<p class="text-xs text-gray-500">CSV jusqu'à 10MB</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="enrollment_date_csv_dashboard" class="block text-sm font-medium text-gray-700 mb-2">Date d'inscription</label>
|
|
<input type="date" name="enrollment_date" id="enrollment_date_csv_dashboard" required
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" name="skip_duplicates" value="true" checked
|
|
class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50">
|
|
<span class="ml-2 text-sm text-gray-700">Ignorer les élèves déjà existants</span>
|
|
</label>
|
|
<p class="text-xs text-gray-500 mt-1">Si décoché, l'import échouera en cas de doublon</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3">
|
|
<button type="button" onclick="closeCSVImportModal()"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors">
|
|
Annuler
|
|
</button>
|
|
<button type="submit"
|
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md transition-colors flex items-center">
|
|
<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-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L7.707 6.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
</svg>
|
|
Importer les élèves
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
</div> <!-- Fermeture class-dashboard -->
|
|
|
|
{% endblock %}
|
|
|
|
{% block head %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<!-- Injection des données statistiques dans le JavaScript -->
|
|
<script>
|
|
window.initialStatsData = {{ stats_data|tojson|safe }};
|
|
</script>
|
|
<script src="{{ url_for('static', filename='js/ClassDashboard.js') }}"></script>
|
|
<script>
|
|
// Fonctions pour le modal d'import CSV
|
|
function openCSVImportModal() {
|
|
document.getElementById('csvImportModal').classList.remove('hidden');
|
|
document.getElementById('csvImportModal').classList.add('flex');
|
|
// Initialiser la date d'aujourd'hui
|
|
const today = new Date().toISOString().split('T')[0];
|
|
document.getElementById('enrollment_date_csv_dashboard').value = today;
|
|
}
|
|
|
|
function closeCSVImportModal() {
|
|
document.getElementById('csvImportModal').classList.add('hidden');
|
|
document.getElementById('csvImportModal').classList.remove('flex');
|
|
// Réinitialiser le formulaire
|
|
document.getElementById('csvImportForm').reset();
|
|
}
|
|
|
|
// Fermer le modal avec Escape ou clic à l'extérieur
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
const modal = document.getElementById('csvImportModal');
|
|
if (modal && !modal.classList.contains('hidden')) {
|
|
closeCSVImportModal();
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener('click', function(event) {
|
|
const modal = document.getElementById('csvImportModal');
|
|
if (event.target === modal) {
|
|
closeCSVImportModal();
|
|
}
|
|
});
|
|
</script>
|
|
<style>
|
|
/* Fix pour éviter le clipping des hover effects sur cette page */
|
|
.class-dashboard {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
.class-dashboard .grid {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
.class-dashboard [class*="transform"][class*="hover:scale"] {
|
|
transform-origin: center;
|
|
}
|
|
|
|
/* Assurer que les conteneurs parents permettent l'overflow */
|
|
main {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Ajout d'un peu d'espace pour les animations */
|
|
.class-dashboard .grid > * {
|
|
margin: 4px;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.class-dashboard .grid > *:hover {
|
|
z-index: 10;
|
|
}
|
|
</style>
|
|
{% endblock %} |