refact: unify js and css
This commit is contained in:
@@ -119,8 +119,15 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript Core -->
|
||||
<script src="{{ url_for('static', filename='js/notytex.js') }}"></script>
|
||||
<!-- JavaScript Unifié Moderne -->
|
||||
<script type="module" src="{{ url_for('static', filename='js/notytex-unified.js') }}"></script>
|
||||
|
||||
<!-- Fallback pour anciens navigateurs -->
|
||||
<script nomodule>
|
||||
console.warn('Navigateur non supporté - Veuillez utiliser un navigateur moderne');
|
||||
// Fallback vers l'ancien système si nécessaire
|
||||
document.head.insertAdjacentHTML('beforeend', '<script src="{{ url_for("static", filename="js/notytex.js") }}"><\/script>');
|
||||
</script>
|
||||
|
||||
<!-- JavaScript spécifique aux pages -->
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
@@ -447,6 +447,10 @@
|
||||
|
||||
{% 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>
|
||||
<style>
|
||||
/* Fix pour éviter le clipping des hover effects sur cette page */
|
||||
|
||||
@@ -1,156 +1,134 @@
|
||||
{% extends "base.html" %}
|
||||
{% from 'components/common/macros.html' import hero_section %}
|
||||
{% from 'components/common/macros.html' import page_layout, content_section, empty_state %}
|
||||
{% from 'components/class/class_card.html' import class_card %}
|
||||
|
||||
{% block title %}Classes - Gestion Scolaire{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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': url_for('classes.new'),
|
||||
{% call page_layout(
|
||||
"Mes Classes 🏫",
|
||||
"Gérez et organisez toutes vos classes",
|
||||
[],
|
||||
[{
|
||||
'url': url_for('classes.new') if url_for else '#',
|
||||
'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"
|
||||
) }}
|
||||
'icon': '<svg class="w-5 h-5" 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>',
|
||||
'variant': 'primary'
|
||||
}]
|
||||
) %}
|
||||
|
||||
{% if classes %}
|
||||
<!-- 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 %}
|
||||
<!-- É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>
|
||||
{% call content_section("Classes disponibles", classes|length ~ " classes actives") %}
|
||||
{% if classes %}
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<a href="{{ url_for('classes.new') }}" 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
|
||||
</a>
|
||||
|
||||
<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 %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ empty_state(
|
||||
"Aucune classe créée",
|
||||
"Commencez votre gestion scolaire en créant votre première classe. Vous pourrez ensuite y ajouter des élèves et créer des évaluations.",
|
||||
'<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>',
|
||||
{
|
||||
'url': url_for('classes.new') if url_for else '#',
|
||||
'text': 'Créer ma première classe',
|
||||
'icon': '<svg class="w-5 h-5" 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>',
|
||||
'variant': 'primary'
|
||||
},
|
||||
{
|
||||
'text': 'Une classe peut contenir plusieurs élèves et être utilisée pour de nombreuses évaluations'
|
||||
}
|
||||
) }}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Modal de confirmation de suppression -->
|
||||
<div id="deleteModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-12 h-12 text-red-600" 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>
|
||||
<!-- Modal de confirmation de suppression -->
|
||||
<div id="deleteModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-12 h-12 text-red-600" 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 class="ml-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Supprimer la classe</h3>
|
||||
<p class="mt-1 text-sm text-gray-600" id="deleteMessage">
|
||||
Êtes-vous sûr de vouloir supprimer cette classe ?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Supprimer la classe</h3>
|
||||
<p class="mt-1 text-sm text-gray-600" id="deleteMessage">
|
||||
Êtes-vous sûr de vouloir supprimer cette classe ?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end space-x-3">
|
||||
<button onclick="closeDeleteModal()" class="bg-white hover:bg-gray-50 border border-gray-300 text-gray-700 px-4 py-2 rounded-lg font-medium transition-colors">
|
||||
Annuler
|
||||
</button>
|
||||
<form id="deleteForm" method="POST" class="inline">
|
||||
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
|
||||
Supprimer définitivement
|
||||
|
||||
<div class="mt-6 flex justify-end space-x-3">
|
||||
<button onclick="closeDeleteModal()" class="bg-white hover:bg-gray-50 border border-gray-300 text-gray-700 px-4 py-2 rounded-lg font-medium transition-colors">
|
||||
Annuler
|
||||
</button>
|
||||
</form>
|
||||
<form id="deleteForm" method="POST" class="inline">
|
||||
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
|
||||
Supprimer définitivement
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmDeleteClass(classId, className, studentsCount, assessmentsCount) {
|
||||
const modal = document.getElementById('deleteModal');
|
||||
const message = document.getElementById('deleteMessage');
|
||||
const form = document.getElementById('deleteForm');
|
||||
|
||||
// Configuration du message selon le contenu de la classe
|
||||
if (studentsCount > 0 || assessmentsCount > 0) {
|
||||
message.innerHTML = `
|
||||
<strong>Impossible de supprimer la classe "${className}".</strong><br>
|
||||
Elle contient <strong>${studentsCount} élève(s)</strong> et <strong>${assessmentsCount} évaluation(s)</strong>.<br>
|
||||
Supprimez d'abord ces éléments pour pouvoir supprimer la classe.
|
||||
`;
|
||||
// Masquer le bouton de suppression et changer le titre
|
||||
document.querySelector('#deleteForm button').style.display = 'none';
|
||||
document.querySelector('#deleteModal h3').textContent = 'Suppression impossible';
|
||||
} else {
|
||||
message.innerHTML = `
|
||||
Êtes-vous sûr de vouloir supprimer la classe <strong>"${className}"</strong> ?<br>
|
||||
<span class="text-red-600">Cette action est irréversible.</span>
|
||||
`;
|
||||
// Réafficher le bouton de suppression et restaurer le titre
|
||||
document.querySelector('#deleteForm button').style.display = 'inline';
|
||||
document.querySelector('#deleteModal h3').textContent = 'Supprimer la classe';
|
||||
}
|
||||
|
||||
// Configuration de l'action du formulaire
|
||||
form.action = `/classes/${classId}/delete`;
|
||||
|
||||
// Afficher le modal
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeDeleteModal() {
|
||||
document.getElementById('deleteModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Fermeture au clic sur le fond
|
||||
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeDeleteModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Fermeture avec la touche Échap
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeDeleteModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmDeleteClass(classId, className, studentsCount, assessmentsCount) {
|
||||
const modal = document.getElementById('deleteModal');
|
||||
const message = document.getElementById('deleteMessage');
|
||||
const form = document.getElementById('deleteForm');
|
||||
|
||||
// Configuration du message selon le contenu de la classe
|
||||
if (studentsCount > 0 || assessmentsCount > 0) {
|
||||
message.innerHTML = `
|
||||
<strong>Impossible de supprimer la classe "${className}".</strong><br>
|
||||
Elle contient <strong>${studentsCount} élève(s)</strong> et <strong>${assessmentsCount} évaluation(s)</strong>.<br>
|
||||
Supprimez d'abord ces éléments pour pouvoir supprimer la classe.
|
||||
`;
|
||||
// Masquer le bouton de suppression et changer le titre
|
||||
document.querySelector('#deleteForm button').style.display = 'none';
|
||||
document.querySelector('#deleteModal h3').textContent = 'Suppression impossible';
|
||||
} else {
|
||||
message.innerHTML = `
|
||||
Êtes-vous sûr de vouloir supprimer la classe <strong>"${className}"</strong> ?<br>
|
||||
<span class="text-red-600">Cette action est irréversible.</span>
|
||||
`;
|
||||
// Réafficher le bouton de suppression et restaurer le titre
|
||||
document.querySelector('#deleteForm button').style.display = 'inline';
|
||||
document.querySelector('#deleteModal h3').textContent = 'Supprimer la classe';
|
||||
}
|
||||
|
||||
// Configuration de l'action du formulaire
|
||||
form.action = `/classes/${classId}/delete`;
|
||||
|
||||
// Afficher le modal
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeDeleteModal() {
|
||||
document.getElementById('deleteModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Fermeture au clic sur le fond
|
||||
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeDeleteModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Fermeture avec la touche Échap
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeDeleteModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -212,6 +212,125 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour page layout standardisé avec hero section #}
|
||||
{% macro page_layout(title, subtitle=None, breadcrumbs=[], actions=[]) %}
|
||||
{% if breadcrumbs %}
|
||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
||||
<ol class="flex items-center space-x-2 text-sm">
|
||||
{% for crumb in breadcrumbs %}
|
||||
<li class="flex items-center">
|
||||
{% if not loop.last %}
|
||||
<a href="{{ crumb.url }}" class="text-gray-500 hover:text-gray-700 transition-colors">
|
||||
{{ crumb.text }}
|
||||
</a>
|
||||
<svg class="w-4 h-4 mx-2 text-gray-400" 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>
|
||||
{% else %}
|
||||
<span class="text-gray-900 font-medium">{{ crumb.text }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<!-- Hero Section avec gradient -->
|
||||
<div class="bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl p-8 shadow-lg mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-4xl font-bold mb-2">{{ title }}</h1>
|
||||
{% if subtitle %}
|
||||
<p class="text-xl opacity-90 mb-1">{{ subtitle }}</p>
|
||||
{% endif %}
|
||||
<p class="text-sm opacity-75">{{ moment().format('dddd D MMMM YYYY') if moment else 'Lundi 16 août 2025' }}</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
{% if actions %}
|
||||
<div class="flex items-center space-x-3">
|
||||
{% for action in actions %}
|
||||
<a href="{{ action.url }}"
|
||||
class="bg-white/20 hover:bg-white/30 text-white px-6 py-3 rounded-xl transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-105 flex items-center">
|
||||
{% if action.icon %}{{ action.icon|safe }}{% endif %}
|
||||
{{ action.text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-24 h-24 bg-white/20 rounded-full flex items-center justify-center">
|
||||
<svg class="w-12 h-12" 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ caller() }}
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour section de contenu standardisée #}
|
||||
{% macro content_section(title=None, description=None, actions=[], classes="") %}
|
||||
<div class="bg-white rounded-xl shadow-lg overflow-hidden {{ classes }}">
|
||||
{% if title %}
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900">{{ title }}</h2>
|
||||
{% if description %}
|
||||
<p class="text-sm text-gray-600 mt-1">{{ description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if actions %}
|
||||
<div class="flex items-center space-x-2">
|
||||
{% for action in actions %}
|
||||
<a href="{{ action.url }}"
|
||||
class="text-blue-600 hover:text-blue-800 text-sm font-medium">
|
||||
{% if action.icon %}{{ action.icon|safe }}{% endif %}
|
||||
{{ action.text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="p-6">
|
||||
{{ caller() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour état vide unifié #}
|
||||
{% macro empty_state(title, description, icon, action=None, secondary_action=None) %}
|
||||
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
<div class="p-6 text-center py-12">
|
||||
<div class="w-24 h-24 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
{{ icon|safe }}
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">{{ title }}</h3>
|
||||
<p class="text-base text-gray-600 mb-6 max-w-md mx-auto">{{ description }}</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
{% if action %}
|
||||
<a href="{{ action.url }}"
|
||||
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold">
|
||||
{% if action.icon %}{{ action.icon|safe }}{% endif %}
|
||||
{{ action.text }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if secondary_action %}
|
||||
<div class="text-sm text-gray-600">
|
||||
<p><strong>💡 Astuce :</strong> {{ secondary_action.text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour filtres simplifiés et discrets #}
|
||||
{% macro simple_filter_section(filters_config, current_values={}, total_items=0, filtered_items=0) %}
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-6 border border-gray-200">
|
||||
@@ -292,4 +411,130 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour tableau standardisé #}
|
||||
{% macro data_table(headers, rows, actions_column=False, zebra=True) %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
{{ header }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% if actions_column %}
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for row in rows %}
|
||||
<tr class="{% if zebra and loop.index % 2 == 0 %}bg-gray-50{% endif %} hover:bg-blue-50 transition-colors">
|
||||
{% for cell in row.cells %}
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ cell|safe }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
{% if actions_column and row.actions %}
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div class="flex justify-end space-x-2">
|
||||
{% for action in row.actions %}
|
||||
<a href="{{ action.url }}"
|
||||
class="text-{{ action.color|default('blue') }}-600 hover:text-{{ action.color|default('blue') }}-900 transition-colors">
|
||||
{{ action.text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour liste d'éléments uniformisée #}
|
||||
{% macro item_list(items, show_meta=True, show_actions=True) %}
|
||||
<div class="space-tight">
|
||||
{% for item in items %}
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-blue-50 rounded-lg transition-colors group">
|
||||
<a href="{{ item.url }}" class="flex items-center space-x-4 flex-1 min-w-0">
|
||||
{% if item.avatar %}
|
||||
<div class="w-10 h-10 {{ item.avatar.classes|default('bg-gradient-to-br from-blue-500 to-purple-600') }} rounded-full flex items-center justify-center text-white font-bold text-sm">
|
||||
{{ item.avatar.content|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-blue-700 transition-colors truncate">
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
{% if show_meta and item.meta %}
|
||||
<div class="flex items-center text-xs text-gray-500 space-x-2 mt-1">
|
||||
{% for meta in item.meta %}
|
||||
<span class="flex items-center">
|
||||
{% if meta.icon %}{{ meta.icon|safe }}{% endif %}
|
||||
{{ meta.text }}
|
||||
</span>
|
||||
{% if not loop.last %}<span>•</span>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{% if show_actions and item.indicators %}
|
||||
<div class="flex items-center space-x-3 flex-shrink-0">
|
||||
{% for indicator in item.indicators %}
|
||||
{{ indicator|safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro pour notifications/alertes uniformisées #}
|
||||
{% macro alert(type, title, message, dismissible=True, icon=True) %}
|
||||
{% set alert_classes = {
|
||||
'info': 'bg-blue-50 border-blue-200 text-blue-800',
|
||||
'success': 'bg-green-50 border-green-200 text-green-800',
|
||||
'warning': 'bg-orange-50 border-orange-200 text-orange-800',
|
||||
'error': 'bg-red-50 border-red-200 text-red-800'
|
||||
} %}
|
||||
|
||||
{% set icons = {
|
||||
'info': '<svg class="w-5 h-5" 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>',
|
||||
'success': '<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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>',
|
||||
'warning': '<svg class="w-5 h-5" 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>',
|
||||
'error': '<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
||||
} %}
|
||||
|
||||
<div class="border rounded-lg p-4 {{ alert_classes[type] }} {% if dismissible %}relative{% endif %}" role="alert">
|
||||
<div class="flex items-start">
|
||||
{% if icon %}
|
||||
<div class="flex-shrink-0 mr-3 mt-0.5">
|
||||
{{ icons[type]|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-medium">{{ title }}</h3>
|
||||
<p class="text-sm mt-1">{{ message }}</p>
|
||||
</div>
|
||||
{% if dismissible %}
|
||||
<button type="button" class="flex-shrink-0 ml-3 hover:opacity-75 transition-opacity"
|
||||
onclick="this.parentElement.remove()">
|
||||
<svg class="w-4 h-4" 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,58 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% from 'components/common/macros.html' import page_layout, content_section, alert, stat_card, action_card %}
|
||||
|
||||
{% block title %}Accueil - Gestion Scolaire{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-8">
|
||||
<!-- Notifications contextuelles -->
|
||||
{% call page_layout("Bonjour ! 👋", "Prêt à gérer vos évaluations aujourd'hui ?") %}
|
||||
<!-- Notifications contextuelles avec composant unifié -->
|
||||
{% if total_assessments == 0 %}
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<svg class="w-5 h-5 text-blue-400 mt-0.5 mr-3" 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>
|
||||
<h3 class="text-sm font-medium text-blue-800">Bienvenue dans Notytex ! 🎉</h3>
|
||||
<p class="text-sm text-blue-700 mt-1">
|
||||
Commencez par créer votre première évaluation.
|
||||
<a href="{{ url_for('assessments.new') }}" class="underline hover:no-underline font-medium">Créer maintenant</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ alert('info', 'Bienvenue dans Notytex ! 🎉', 'Commencez par créer votre première évaluation. <a href="' + url_for('assessments.new') + '" class="underline hover:no-underline font-medium">Créer maintenant</a>', false) }}
|
||||
{% elif recent_assessments and recent_assessments|length >= 3 %}
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<svg class="w-5 h-5 text-green-400 mt-0.5 mr-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-green-800">Système bien utilisé ! 📈</h3>
|
||||
<p class="text-sm text-green-700 mt-1">
|
||||
Vous avez {{ total_assessments }} évaluations créées. Pensez à exporter vos données régulièrement.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ alert('success', 'Système bien utilisé ! 📈', 'Vous avez ' + total_assessments|string + ' évaluations créées. Pensez à exporter vos données régulièrement.', false) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- Hero Section avec accueil personnalisé -->
|
||||
<div class="bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl p-8 shadow-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold mb-2">Bonjour ! 👋</h1>
|
||||
<p class="text-xl opacity-90 mb-1">Prêt à gérer vos évaluations aujourd'hui ?</p>
|
||||
<p class="text-sm opacity-75">{{ moment().format('dddd D MMMM YYYY') if moment else 'Lundi 4 août 2025' }}</p>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="w-24 h-24 bg-white/20 rounded-full flex items-center justify-center">
|
||||
<svg class="w-12 h-12" 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hero Section déjà gérée par page_layout -->
|
||||
|
||||
<!-- Statistiques enrichies et cliquables -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
@@ -341,4 +302,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user