feat: improve classes page

This commit is contained in:
2025-08-07 19:22:06 +02:00
parent e08466fab3
commit 74088235fa
4 changed files with 915 additions and 49 deletions

View File

@@ -1,62 +1,68 @@
{% extends "base.html" %}
{% from 'components/common/macros.html' import hero_section %}
{% from 'components/class/class_card.html' import class_card %}
{% block title %}Classes - Gestion Scolaire{% endblock %}
{% block content %}
<div class="space-y-6">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold text-gray-900">Gestion des classes</h1>
<button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
Nouvelle classe
</button>
</div>
<div class="space-y-8">
{# Hero Section avec composant réutilisable #}
{% set meta_info = [
{
'icon': '<svg class="w-4 h-4 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>',
'text': classes|length ~ ' classes actives'
},
{
'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': 'Année scolaire 2024-2025'
}
] %}
{% set primary_action = {
'url': '#',
'text': 'Nouvelle classe',
'icon': '<svg class="w-5 h-5 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>'
} %}
{{ hero_section(
title="Mes Classes 🏫",
subtitle="Gérez et organisez toutes vos classes",
meta_info=meta_info,
primary_action=primary_action,
gradient_class="from-blue-600 to-green-600"
) }}
{% if classes %}
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<ul class="divide-y divide-gray-200">
{% for class in classes %}
<li>
<div class="px-4 py-4 flex items-center justify-between">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<span class="text-sm font-medium text-blue-600">{{ class.name[:2] }}</span>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">{{ class.name }}</div>
<div class="text-sm text-gray-500">
Année {{ class.year }} - {{ class.students|length }} élève(s)
</div>
{% if class.description %}
<div class="text-sm text-gray-500 mt-1">{{ class.description }}</div>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<button class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">
Voir élèves
</button>
<button class="text-gray-600 hover:text-gray-900 text-sm font-medium">
Modifier
</button>
</div>
</div>
</li>
{% endfor %}
</ul>
<!-- Grille de classes avec composants -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for class in classes %}
{{ class_card(class) }}
{% endfor %}
</div>
{% else %}
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path d="M34 40h10v-4a6 6 0 00-10.712-3.714M34 40H14m20 0v-4a9.971 9.971 0 00-.712-3.714M14 40H4v-4a6 6 0 0110.713-3.714M14 40v-4c0-1.313.253-2.566.713-3.714m0 0A10.003 10.003 0 0124 26c4.21 0 7.813 2.602 9.288 6.286" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">Aucune classe</h3>
<p class="mt-1 text-sm text-gray-500">Commencez par créer votre première classe.</p>
<div class="mt-6">
<button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
Nouvelle classe
<!-- État vide moderne -->
<div class="bg-white rounded-xl shadow-lg p-12 text-center">
<div class="w-24 h-24 bg-gradient-to-br from-blue-100 to-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg class="w-12 h-12 text-blue-600" 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>
</div>
<h3 class="text-xl font-bold text-gray-900 mb-2">Aucune classe créée</h3>
<p class="text-gray-600 mb-6 max-w-md mx-auto">
Commencez votre gestion scolaire en créant votre première classe.
Vous pourrez ensuite y ajouter des élèves et créer des évaluations.
</p>
<div class="space-y-4">
<button class="inline-flex items-center bg-gradient-to-r from-blue-500 to-green-500 hover:from-blue-600 hover:to-green-600 text-white px-6 py-3 rounded-xl transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-105">
<svg class="w-5 h-5 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 ma première classe
</button>
<div class="text-sm text-gray-500">
<p>💡 <strong>Astuce :</strong> Une classe peut contenir plusieurs élèves et être utilisée pour de nombreuses évaluations</p>
</div>
</div>
</div>
{% endif %}

View File

@@ -0,0 +1,116 @@
{# Composant pour carte de classe dans la liste #}
{% macro class_card(class) %}
{# Extraire le niveau de classe du nom (ex: "6ème A" -> 6, "Terminale S" -> "T") #}
{% set class_level = class.name[0] | int if class.name[0].isdigit() else ('T' if class.name.startswith('T') or class.name.startswith('t') else 'unknown') %}
{% set year_colors = {
6: {'bg': 'from-blue-500 to-blue-600', 'accent': 'blue', 'icon_bg': 'bg-blue-100', 'icon_text': 'text-blue-600'},
5: {'bg': 'from-green-500 to-green-600', 'accent': 'green', 'icon_bg': 'bg-green-100', 'icon_text': 'text-green-600'},
4: {'bg': 'from-purple-500 to-purple-600', 'accent': 'purple', 'icon_bg': 'bg-purple-100', 'icon_text': 'text-purple-600'},
3: {'bg': 'from-orange-500 to-orange-600', 'accent': 'orange', 'icon_bg': 'bg-orange-100', 'icon_text': 'text-orange-600'},
2: {'bg': 'from-red-500 to-red-600', 'accent': 'red', 'icon_bg': 'bg-red-100', 'icon_text': 'text-red-600'},
1: {'bg': 'from-pink-500 to-pink-600', 'accent': 'pink', 'icon_bg': 'bg-pink-100', 'icon_text': 'text-pink-600'},
'T': {'bg': 'from-indigo-500 to-indigo-600', 'accent': 'indigo', 'icon_bg': 'bg-indigo-100', 'icon_text': 'text-indigo-600'},
'unknown': {'bg': 'from-gray-500 to-gray-600', 'accent': 'gray', 'icon_bg': 'bg-gray-100', 'icon_text': 'text-gray-600'}
} %}
{% set year_config = year_colors.get(class_level, year_colors['unknown']) %}
<div class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105 overflow-hidden flex flex-col">
<!-- Header avec nom de classe et année (couleur selon niveau) -->
<div class="bg-gradient-to-r {{ year_config.bg }} p-4 text-white">
<div class="flex items-center justify-between">
<!-- Nom de classe (élément principal) -->
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
<span class="text-lg font-black">{{ class.name[:2] }}</span>
</div>
<div>
<div class="text-xl md:text-2xl font-black">{{ class.name }}</div>
<div class="text-sm opacity-90">{{ class.year }}</div>
</div>
</div>
<!-- Indicateur nombre d'élèves -->
<div class="bg-white/20 px-3 py-2 rounded-full">
<div class="flex items-center space-x-2 text-sm font-medium">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
</svg>
<span>{{ class.students|length }}</span>
</div>
</div>
</div>
</div>
<!-- Contenu principal -->
<div class="p-4 flex flex-col justify-between flex-1">
<!-- Description (si présente) -->
{% if class.description %}
<p class="text-sm text-gray-600 mb-4 line-clamp-2 italic">{{ class.description }}</p>
{% else %}
<p class="text-sm text-gray-400 mb-4 italic">Aucune description</p>
{% endif %}
<div class="flex flex-col">
<!-- Meta informations -->
<div class="flex flex-wrap items-center gap-3 text-xs font-medium text-gray-600 mb-4">
<div class="flex items-center space-x-1">
<span class="w-2 h-2 bg-{{ year_config.accent }}-400 rounded-full"></span>
{% if class_level == 'T' %}
<span>Terminale</span>
{% elif class_level == 'unknown' %}
<span>Non reconnu</span>
{% else %}
<span>Niveau {{ class_level }}ème</span>
{% endif %}
</div>
<div class="flex items-center space-x-1">
<svg class="w-3 h-3" 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>
<span>{{ class.students|length }} élève{{ 's' if class.students|length != 1 else '' }}</span>
</div>
{% if class.students|length > 0 %}
<div class="bg-{{ year_config.accent }}-100 text-{{ year_config.accent }}-800 px-2 py-1 rounded-full">
<span>Active</span>
</div>
{% else %}
<div class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full">
<span>Vide</span>
</div>
{% endif %}
</div>
<!-- Actions principales -->
<div class="grid grid-cols-2 gap-2 mb-3">
<a href="{{ url_for('students') }}?class_id={{ class.id }}"
class="bg-{{ year_config.accent }}-50 hover:bg-{{ year_config.accent }}-100 text-{{ year_config.accent }}-700 hover:text-{{ year_config.accent }}-900 px-3 py-2 rounded-lg text-xs font-medium transition-colors flex items-center justify-center">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
</svg>
{{ class.students|length }} Élèves
</a>
<a href="{{ url_for('assessments.list') }}?class={{ class.id }}"
class="bg-gray-50 hover:bg-gray-100 text-gray-700 hover:text-gray-900 px-3 py-2 rounded-lg text-xs font-medium transition-colors flex items-center justify-center">
<svg class="w-4 h-4 mr-1" 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-2V5zm2.5 2.5a.5.5 0 000 1h3a.5.5 0 000-1h-3z" clip-rule="evenodd"/>
</svg>
Évaluations
</a>
</div>
<!-- Action secondaire -->
<div class="pt-2 border-t border-gray-100">
<button class="w-full text-gray-600 hover:text-gray-900 text-xs font-medium transition-colors flex items-center justify-center py-2">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/>
</svg>
Modifier la classe
</button>
</div>
</div>
</div>
</div>
{% endmacro %}