feat: compact concil page

This commit is contained in:
2025-08-18 07:51:13 +02:00
parent 3fd49d1351
commit 87ff0d22c8
2 changed files with 305 additions and 108 deletions

View File

@@ -360,12 +360,39 @@ class FilterManager {
const trimesterSelector = this.parent.elements.trimesterSelector;
if (!trimesterSelector) return;
trimesterSelector.addEventListener('change', (e) => {
const newTrimester = parseInt(e.target.value);
if (newTrimester !== this.parent.state.currentTrimester) {
this.changeTrimester(newTrimester);
}
});
// Support pour les nouveaux boutons tabs
if (trimesterSelector.hasAttribute('data-trimester-selector')) {
// Navigation tabs - écouter les clics sur les boutons
const trimesterTabs = trimesterSelector.querySelectorAll('[data-trimester-tab]');
trimesterTabs.forEach(tab => {
tab.addEventListener('click', (e) => {
e.preventDefault();
const newTrimester = parseInt(tab.dataset.trimesterTab);
if (newTrimester !== this.parent.state.currentTrimester) {
this.changeTrimester(newTrimester);
}
});
// Support clavier pour accessibilité
tab.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const newTrimester = parseInt(tab.dataset.trimesterTab);
if (newTrimester !== this.parent.state.currentTrimester) {
this.changeTrimester(newTrimester);
}
}
});
});
} else {
// Ancien sélecteur dropdown - pour compatibilité
trimesterSelector.addEventListener('change', (e) => {
const newTrimester = parseInt(e.target.value);
if (newTrimester !== this.parent.state.currentTrimester) {
this.changeTrimester(newTrimester);
}
});
}
}
async changeTrimester(newTrimester) {
@@ -382,7 +409,24 @@ class FilterManager {
this.parent.showToast('Erreur lors du changement de trimestre', 'error');
// Revert selector to previous value
this.parent.elements.trimesterSelector.value = this.parent.state.currentTrimester;
const trimesterSelector = this.parent.elements.trimesterSelector;
if (trimesterSelector.hasAttribute('data-trimester-selector')) {
// Pour les tabs - remettre les bonnes classes active
const tabs = trimesterSelector.querySelectorAll('[data-trimester-tab]');
tabs.forEach(tab => {
const tabTrimester = parseInt(tab.dataset.trimesterTab);
if (tabTrimester === this.parent.state.currentTrimester) {
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
} else {
tab.classList.remove('active');
tab.setAttribute('aria-selected', 'false');
}
});
} else {
// Pour l'ancien dropdown
trimesterSelector.value = this.parent.state.currentTrimester;
}
} finally {
// Hide loading overlay
this.parent.elements.loadingOverlay?.classList.add('hidden');

View File

@@ -48,8 +48,8 @@
) }}
</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">
{# Breadcrumb de retour #}
<div class="list-mode-breadcrumb">
<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">
@@ -58,24 +58,111 @@
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 #}
{# Filtres, Actions et Sélection Trimestre fusionnés #}
<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">
{# Première ligne : Sélection trimestre et synthèse compacte #}
<div class="flex flex-col lg:flex-row lg:items-center justify-between space-y-4 lg:space-y-0 mb-6">
<div class="flex items-center space-x-4">
{# Sélection du trimestre #}
<div class="flex items-center space-x-3">
<h2 class="text-base font-medium text-gray-900">Trimestre</h2>
<nav class="trimester-tabs compact" role="tablist" aria-label="Navigation par trimestre" data-trimester-selector>
<button data-trimester-tab="1"
class="trimester-tab {% if trimester == 1 %}active{% endif %}"
role="tab"
aria-selected="{% if trimester == 1 %}true{% else %}false{% endif %}">
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-gradient-to-r from-blue-500 to-blue-600 mr-1.5"></div>
T1
</div>
</button>
<button data-trimester-tab="2"
class="trimester-tab {% if trimester == 2 %}active{% endif %}"
role="tab"
aria-selected="{% if trimester == 2 %}true{% else %}false{% endif %}">
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-gradient-to-r from-purple-500 to-purple-600 mr-1.5"></div>
T2
</div>
</button>
<button data-trimester-tab="3"
class="trimester-tab {% if trimester == 3 %}active{% endif %}"
role="tab"
aria-selected="{% if trimester == 3 %}true{% else %}false{% endif %}">
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-gradient-to-r from-orange-500 to-orange-600 mr-1.5"></div>
T3
</div>
</button>
</nav>
</div>
{# Synthèse classe compacte - toujours visible #}
<div class="hidden lg:flex items-center space-x-4 bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg px-4 py-2 border border-blue-100">
<div class="flex items-center space-x-2">
<svg class="w-4 h-4 text-blue-600" 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>
<span class="text-sm font-medium text-blue-900">Synthèse</span>
</div>
<div class="flex items-center space-x-3 text-xs">
{% if class_statistics and class_statistics.mean %}
<div class="text-center">
<div class="font-bold text-blue-900">{{ "%.1f"|format(class_statistics.mean) }}</div>
<div class="text-blue-600">Moyenne</div>
</div>
<div class="text-center">
<div class="font-semibold text-green-700">{{ class_statistics.performance_distribution.excellent }}</div>
<div class="text-green-600">≥16</div>
</div>
<div class="text-center">
<div class="font-semibold text-red-700">{{ class_statistics.performance_distribution.struggling }}</div>
<div class="text-red-600">&lt;10</div>
</div>
{% else %}
<div class="text-center">
<div class="font-bold text-blue-900">{{ council_data.total_students }}</div>
<div class="text-blue-600">Élèves</div>
</div>
<div class="text-center">
<div class="font-semibold text-purple-700">{{ council_data.completed_appreciations }}</div>
<div class="text-purple-600">Appréciations</div>
</div>
<div class="text-center">
<div class="font-semibold text-orange-700">{{ council_data.total_students - council_data.completed_appreciations }}</div>
<div class="text-orange-600">À rédiger</div>
</div>
{% endif %}
</div>
</div>
</div>
{# Actions principales #}
<div class="flex space-x-3">
<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>
<div class="list-mode-actions">
<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 détaillée
</button>
</div>
</div>
</div>
{# Deuxième ligne : Recherche et filtres #}
<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">
@@ -101,38 +188,6 @@
<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">
@@ -644,59 +699,6 @@
</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 -->
@@ -708,6 +710,157 @@
{% block head %}
<script src="{{ url_for('static', filename='js/CouncilPreparation.js') }}"></script>
<style>
/* ========== STYLES POUR LES TABS TRIMESTRE ========== */
.trimester-tabs {
display: flex;
gap: 0.5rem;
padding: 1rem;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.trimester-tabs::-webkit-scrollbar {
display: none;
}
.trimester-tab {
flex-shrink: 0;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
font-weight: 600;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
cursor: pointer;
white-space: nowrap;
}
.trimester-tab::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
transition: left 0.6s ease;
}
.trimester-tab:hover::before {
left: 100%;
}
.trimester-tab.active {
background: linear-gradient(135deg, #8b5cf6, #a855f7);
color: white;
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.3);
transform: scale(1.02);
}
.trimester-tab:not(.active) {
background: white;
color: #374151;
border: 2px solid #e5e7eb;
}
.trimester-tab:not(.active):hover {
background: #f9fafb;
border-color: #d1d5db;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* Version compacte des tabs */
.trimester-tabs.compact {
display: flex;
gap: 0.25rem;
padding: 0;
overflow: visible;
}
.trimester-tabs.compact .trimester-tab {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
border-radius: 0.5rem;
}
.trimester-tabs.compact .trimester-tab.active {
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.25);
transform: scale(1);
}
.trimester-tabs.compact .trimester-tab:not(.active):hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* Responsive pour les tabs */
@media (max-width: 768px) {
.trimester-tabs {
padding: 1rem 0.5rem;
gap: 0.25rem;
}
.trimester-tabs.compact {
padding: 0;
gap: 0.125rem;
}
.trimester-tab {
padding: 0.5rem 1rem;
font-size: 0.875rem;
min-width: fit-content;
}
.trimester-tabs.compact .trimester-tab {
padding: 0.375rem 0.5rem;
font-size: 0.7rem;
}
}
/* Touch feedback pour mobile */
@media (hover: none) and (pointer: coarse) {
.trimester-tab:active {
transform: scale(0.98);
transition: transform 0.1s ease;
}
}
/* Focus pour accessibilité */
.trimester-tab:focus-visible {
outline: 2px solid #8b5cf6;
outline-offset: 2px;
}
/* High contrast mode */
@media (prefers-contrast: high) {
.trimester-tab.active {
background: #1f2937;
border-color: #1f2937;
}
.trimester-tab:not(.active) {
background: white;
border: 2px solid #6b7280;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.trimester-tab,
.trimester-tab::before {
animation: none !important;
transition: none !important;
}
.trimester-tab:hover {
transform: none;
}
}
/* Styles spécifiques pour la page conseil */
.council-preparation {
overflow: visible !important;