1042 lines
59 KiB
HTML
1042 lines
59 KiB
HTML
{% extends "base.html" %}
|
|
{% from 'components/common/macros.html' import hero_section %}
|
|
|
|
{% block title %}Élèves de {{ class_group.name }} - Gestion Scolaire{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-6">
|
|
{# Hero Section avec Statistiques des Élèves #}
|
|
{% 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': stats.total_current ~ ' élèves actuels'
|
|
},
|
|
{
|
|
'icon': '<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>',
|
|
'text': stats.recent_arrivals ~ ' arrivées (30j)'
|
|
},
|
|
{
|
|
'icon': '<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>',
|
|
'text': stats.recent_departures ~ ' départs (30j)'
|
|
}
|
|
] %}
|
|
|
|
{% set primary_action = {
|
|
'url': 'javascript:openEnrollModal()',
|
|
'text': 'Inscrire un élève',
|
|
'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>'
|
|
} %}
|
|
|
|
{{ hero_section(
|
|
title=class_group.name ~ " 👥",
|
|
subtitle="Gestion des élèves",
|
|
meta_info=meta_info,
|
|
primary_action=primary_action,
|
|
gradient_class="from-blue-500 to-blue-600"
|
|
) }}
|
|
|
|
{# Breadcrumb Navigation #}
|
|
<div class="flex items-center justify-between">
|
|
<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
|
|
</a>
|
|
</div>
|
|
|
|
{# Actions secondaires #}
|
|
<div class="flex space-x-3">
|
|
<button onclick="openCSVImportModal()"
|
|
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
|
|
<svg class="w-4 h-4 mr-2 text-green-600" 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>
|
|
Import CSV
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{# Tableau des élèves actuels #}
|
|
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-xl font-semibold text-gray-900 flex items-center">
|
|
<svg class="w-6 h-6 mr-2 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>
|
|
Élèves actuellement inscrits ({{ current_students|length }})
|
|
</h2>
|
|
</div>
|
|
|
|
{% if current_students %}
|
|
{# Version desktop/tablette : tableau complet #}
|
|
<div class="hidden md:block">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Élève
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Email
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Inscription
|
|
</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
{% for student in current_students %}
|
|
{% set enrollment = student.get_current_enrollment() %}
|
|
<tr class="hover:bg-gray-50 transition-colors">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-4">
|
|
<span class="text-sm font-medium text-blue-600">{{ student.first_name[0] }}{{ student.last_name[0] }}</span>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900">{{ student.first_name }} {{ student.last_name }}</div>
|
|
<div class="text-sm text-gray-500">ID: {{ student.id }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center group">
|
|
<!-- Vue normale de l'email -->
|
|
<div id="email-display-{{ student.id }}" class="flex items-center">
|
|
{% if student.email %}
|
|
<div class="text-sm text-gray-900">{{ student.email }}</div>
|
|
{% else %}
|
|
<div class="flex items-center">
|
|
<span class="text-sm text-gray-400 italic">Cliquez pour ajouter</span>
|
|
<svg class="w-4 h-4 ml-2 text-orange-400" 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>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Formulaire d'édition inline (caché par défaut) -->
|
|
<div id="email-edit-{{ student.id }}" class="hidden">
|
|
<div class="flex items-center space-x-2">
|
|
<input type="email"
|
|
id="email-input-{{ student.id }}"
|
|
value="{{ student.email or '' }}"
|
|
placeholder="exemple@etablissement.fr"
|
|
class="text-sm border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-48">
|
|
<button onclick="saveEmail({{ student.id }})"
|
|
class="text-green-600 hover:text-green-700 p-1"
|
|
title="Enregistrer">
|
|
<svg class="w-4 h-4" 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>
|
|
</button>
|
|
<button onclick="cancelEmailEdit({{ student.id }})"
|
|
class="text-gray-400 hover:text-gray-600 p-1"
|
|
title="Annuler">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bouton d'édition (stylo) - visible au survol -->
|
|
<button id="email-edit-btn-{{ student.id }}"
|
|
onclick="startEmailEdit({{ student.id }})"
|
|
class="ml-2 text-gray-400 hover:text-blue-600 p-1 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
title="Modifier l'email">
|
|
<svg class="w-4 h-4" 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>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
{% if enrollment %}
|
|
<div class="text-sm text-gray-900">{{ enrollment.enrollment_date.strftime('%d/%m/%Y') }}</div>
|
|
{% if enrollment.enrollment_reason %}
|
|
<div class="text-xs text-gray-500">{{ enrollment.enrollment_reason }}</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<div class="flex justify-end space-x-2">
|
|
<button onclick="transferStudent({{ student.id }}, '{{ student.first_name }} {{ student.last_name }}')"
|
|
class="bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-1 rounded text-xs transition-colors">
|
|
Transférer
|
|
</button>
|
|
<button onclick="departureStudent({{ student.id }}, '{{ student.first_name }} {{ student.last_name }}')"
|
|
class="bg-orange-100 hover:bg-orange-200 text-orange-700 px-3 py-1 rounded text-xs transition-colors">
|
|
Départ
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{# Version mobile : cards compactes #}
|
|
<div class="md:hidden divide-y divide-gray-200">
|
|
{% for student in current_students %}
|
|
{% set enrollment = student.get_current_enrollment() %}
|
|
<div class="px-4 py-4">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex items-start">
|
|
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
|
<span class="text-xs font-medium text-blue-600">{{ student.first_name[0] }}{{ student.last_name[0] }}</span>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="text-sm font-medium text-gray-900 truncate">{{ student.first_name }} {{ student.last_name }}</div>
|
|
<div class="mt-1 group">
|
|
<!-- Vue normale de l'email mobile -->
|
|
<div id="email-display-mobile-{{ student.id }}" class="flex items-center">
|
|
{% if student.email %}
|
|
<div class="text-xs text-gray-600">{{ student.email }}</div>
|
|
{% else %}
|
|
<div class="text-xs text-gray-400 italic flex items-center">
|
|
<svg class="w-3 h-3 mr-1 text-orange-400" 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>
|
|
Cliquez pour ajouter
|
|
</div>
|
|
{% endif %}
|
|
<button onclick="startEmailEdit({{ student.id }})"
|
|
class="ml-2 text-gray-400 hover:text-blue-600 p-1"
|
|
title="Modifier l'email">
|
|
<svg class="w-3 h-3" 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>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Formulaire d'édition inline mobile (caché par défaut) -->
|
|
<div id="email-edit-mobile-{{ student.id }}" class="hidden mt-1">
|
|
<div class="flex items-center space-x-2">
|
|
<input type="email"
|
|
id="email-input-mobile-{{ student.id }}"
|
|
value="{{ student.email or '' }}"
|
|
placeholder="exemple@etablissement.fr"
|
|
class="text-xs border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent flex-1">
|
|
<button onclick="saveEmail({{ student.id }})"
|
|
class="text-green-600 hover:text-green-700 p-1"
|
|
title="Enregistrer">
|
|
<svg class="w-3 h-3" 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>
|
|
</button>
|
|
<button onclick="cancelEmailEdit({{ student.id }})"
|
|
class="text-gray-400 hover:text-gray-600 p-1"
|
|
title="Annuler">
|
|
<svg class="w-3 h-3" 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>
|
|
</div>
|
|
{% if enrollment %}
|
|
<div class="text-xs text-gray-500 mt-1">
|
|
Inscrit le {{ enrollment.enrollment_date.strftime('%d/%m/%Y') }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 flex flex-wrap gap-2">
|
|
<button onclick="transferStudent({{ student.id }}, '{{ student.first_name }} {{ student.last_name }}')"
|
|
class="bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded text-xs transition-colors">
|
|
Transférer
|
|
</button>
|
|
<button onclick="departureStudent({{ student.id }}, '{{ student.first_name }} {{ student.last_name }}')"
|
|
class="bg-orange-100 hover:bg-orange-200 text-orange-700 px-2 py-1 rounded text-xs transition-colors">
|
|
Départ
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="px-6 py-8 text-center text-gray-500">
|
|
<svg class="w-12 h-12 mx-auto mb-4 text-gray-300" 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>
|
|
<p>Aucun élève inscrit dans cette classe</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Historique des mouvements #}
|
|
{% if class_movements %}
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-xl font-semibold text-gray-900 flex items-center">
|
|
<svg class="w-6 h-6 mr-2 text-gray-600" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
|
|
</svg>
|
|
Historique des mouvements (6 derniers mois)
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="divide-y divide-gray-200">
|
|
{% for student, enrollments in class_movements %}
|
|
<div class="px-6 py-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="font-medium text-gray-900">{{ student.first_name }} {{ student.last_name }}</span>
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
{% for enrollment in enrollments %}
|
|
<div class="flex items-center justify-between text-sm">
|
|
<div class="flex items-center">
|
|
{% if enrollment.departure_date %}
|
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800 mr-2">
|
|
Départ
|
|
</span>
|
|
<span class="text-gray-600">
|
|
Du {{ enrollment.enrollment_date.strftime('%d/%m/%Y') }} au {{ enrollment.departure_date.strftime('%d/%m/%Y') }}
|
|
{% if enrollment.departure_reason %}
|
|
- {{ enrollment.departure_reason }}
|
|
{% endif %}
|
|
</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 mr-2">
|
|
Arrivée
|
|
</span>
|
|
<span class="text-gray-600">
|
|
Depuis le {{ enrollment.enrollment_date.strftime('%d/%m/%Y') }}
|
|
{% if enrollment.enrollment_reason %}
|
|
- {{ enrollment.enrollment_reason }}
|
|
{% endif %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if enrollment.departure_date %}
|
|
<button onclick="cancelDeparture({{ enrollment.id }}, '{{ student.first_name }} {{ student.last_name }}')"
|
|
class="bg-green-100 hover:bg-green-200 text-green-700 px-2 py-1 rounded text-xs transition-colors">
|
|
Annuler le départ
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Modal d'inscription d'élève #}
|
|
<div id="enrollModal" 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-md w-full mx-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Inscrire un élève dans {{ class_group.name }}</h3>
|
|
<button type="button" onclick="closeEnrollModal()"
|
|
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>
|
|
|
|
{# Tabs pour choisir le mode #}
|
|
<div class="flex mb-4 border-b border-gray-200">
|
|
<button type="button" id="newStudentTab"
|
|
class="px-4 py-2 text-sm font-medium text-blue-600 border-b-2 border-blue-600"
|
|
onclick="switchEnrollMode('new')">
|
|
Nouvel élève
|
|
</button>
|
|
<button type="button" id="existingStudentTab"
|
|
class="px-4 py-2 text-sm font-medium text-gray-500 border-b-2 border-transparent"
|
|
onclick="switchEnrollMode('existing')">
|
|
Élève existant
|
|
</button>
|
|
</div>
|
|
|
|
<form id="enrollForm" method="post" action="{{ url_for('classes.enroll_student') }}">
|
|
<input type="hidden" name="class_id" value="{{ class_group.id }}">
|
|
<input type="hidden" name="mode" id="enrollMode" value="new">
|
|
|
|
{# Mode élève existant #}
|
|
<div id="existingStudentFields" class="hidden">
|
|
<div class="mb-4">
|
|
<label for="student_id" class="block text-sm font-medium text-gray-700 mb-2">Élève</label>
|
|
<select name="student_id" id="student_id"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="">Sélectionner un élève</option>
|
|
{% for student in available_students %}
|
|
<option value="{{ student.id }}">
|
|
{{ student.first_name }} {{ student.last_name }}
|
|
{% set current_class = student.get_current_class() %}
|
|
{% if current_class %}
|
|
(actuellement en {{ current_class.name }})
|
|
{% else %}
|
|
(non inscrit)
|
|
{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{# Mode nouvel élève #}
|
|
<div id="newStudentFields">
|
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label for="new_first_name" class="block text-sm font-medium text-gray-700 mb-2">Prénom</label>
|
|
<input type="text" name="new_first_name" id="new_first_name"
|
|
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>
|
|
<label for="new_last_name" class="block text-sm font-medium text-gray-700 mb-2">Nom</label>
|
|
<input type="text" name="new_last_name" id="new_last_name"
|
|
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>
|
|
<div class="mb-4">
|
|
<label for="new_email" class="block text-sm font-medium text-gray-700 mb-2">Email (optionnel)</label>
|
|
<input type="email" name="new_email" id="new_email"
|
|
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>
|
|
|
|
<div class="mb-4">
|
|
<label for="enrollment_date" class="block text-sm font-medium text-gray-700 mb-2">Date d'inscription</label>
|
|
<input type="date" name="enrollment_date" id="enrollment_date" 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 for="enrollment_reason" class="block text-sm font-medium text-gray-700 mb-2">Motif (optionnel)</label>
|
|
<input type="text" name="enrollment_reason" id="enrollment_reason"
|
|
placeholder="Ex: Nouvelle inscription, Transfert d'établissement..."
|
|
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="flex justify-end space-x-3">
|
|
<button type="button" onclick="closeEnrollModal()"
|
|
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">
|
|
Inscrire
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{# Modal de transfert #}
|
|
<div id="transferModal" 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-md w-full mx-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Transférer <span id="transferStudentName"></span></h3>
|
|
<button type="button" onclick="closeTransferModal()"
|
|
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>
|
|
|
|
<form id="transferForm" method="post" action="{{ url_for('classes.transfer_student') }}">
|
|
<input type="hidden" name="student_id" id="transferStudentId">
|
|
|
|
<div class="mb-4">
|
|
<label for="new_class_id" class="block text-sm font-medium text-gray-700 mb-2">Nouvelle classe</label>
|
|
<select name="new_class_id" id="new_class_id" required
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="">Sélectionner une classe</option>
|
|
{% for class in other_classes %}
|
|
<option value="{{ class.id }}">{{ class.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="transfer_date" class="block text-sm font-medium text-gray-700 mb-2">Date de transfert</label>
|
|
<input type="date" name="transfer_date" id="transfer_date" 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 for="transfer_reason" class="block text-sm font-medium text-gray-700 mb-2">Motif (optionnel)</label>
|
|
<input type="text" name="transfer_reason" id="transfer_reason"
|
|
placeholder="Ex: Changement de niveau, Réorientation..."
|
|
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="flex justify-end space-x-3">
|
|
<button type="button" onclick="closeTransferModal()"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors">
|
|
Annuler
|
|
</button>
|
|
<button type="submit"
|
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
|
|
Transférer
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{# Modal de départ #}
|
|
<div id="departureModal" 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-md w-full mx-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Marquer le départ de <span id="departureStudentName"></span></h3>
|
|
<button type="button" onclick="closeDepartureModal()"
|
|
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>
|
|
|
|
<form id="departureForm" method="post" action="{{ url_for('classes.student_departure') }}">
|
|
<input type="hidden" name="student_id" id="departureStudentId">
|
|
|
|
<div class="mb-4">
|
|
<label for="departure_date" class="block text-sm font-medium text-gray-700 mb-2">Date de départ</label>
|
|
<input type="date" name="departure_date" id="departure_date" 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 for="departure_reason" class="block text-sm font-medium text-gray-700 mb-2">Motif (optionnel)</label>
|
|
<input type="text" name="departure_reason" id="departure_reason"
|
|
placeholder="Ex: Fin de scolarité, Déménagement, Changement d'établissement..."
|
|
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="flex justify-end space-x-3">
|
|
<button type="button" onclick="closeDepartureModal()"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors">
|
|
Annuler
|
|
</button>
|
|
<button type="submit"
|
|
class="bg-orange-600 hover:bg-orange-700 text-white px-4 py-2 rounded-md transition-colors">
|
|
Confirmer le départ
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{# Modal de confirmation d'annulation de départ #}
|
|
<div id="cancelDepartureModal" 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-md w-full mx-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Annuler le départ de <span id="cancelDepartureStudentName"></span></h3>
|
|
<button type="button" onclick="closeCancelDepartureModal()"
|
|
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-6">
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-yellow-400" 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-3">
|
|
<h4 class="text-sm font-medium text-yellow-800">Attention</h4>
|
|
<p class="text-sm text-yellow-700 mt-1">
|
|
Cette action va réintégrer l'élève dans la classe et supprimer la date de départ.
|
|
L'élève redeviendra éligible pour les futures évaluations de cette classe.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="cancelDepartureForm" method="post" action="{{ url_for('classes.cancel_departure') }}">
|
|
<input type="hidden" name="enrollment_id" id="cancelDepartureEnrollmentId">
|
|
|
|
<div class="flex justify-end space-x-3">
|
|
<button type="button" onclick="closeCancelDepartureModal()"
|
|
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">
|
|
Confirmer l'annulation
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{# 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" 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" 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" 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" 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" 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>
|
|
|
|
<script>
|
|
// Initialiser la date d'aujourd'hui dans les champs de date
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
document.getElementById('enrollment_date').value = today;
|
|
document.getElementById('transfer_date').value = today;
|
|
document.getElementById('departure_date').value = today;
|
|
document.getElementById('enrollment_date_csv').value = today;
|
|
|
|
// Vérifier s'il y a eu un rechargement après inscription
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.has('reload')) {
|
|
// Supprimer le paramètre de l'URL pour éviter le rechargement en boucle
|
|
const newUrl = window.location.pathname;
|
|
window.history.replaceState({}, document.title, newUrl);
|
|
|
|
// Faire défiler vers le haut pour voir le message de succès
|
|
window.scrollTo(0, 0);
|
|
}
|
|
});
|
|
|
|
// Gestion des modals d'inscription
|
|
function openEnrollModal() {
|
|
document.getElementById('enrollModal').classList.remove('hidden');
|
|
document.getElementById('enrollModal').classList.add('flex');
|
|
// Réinitialiser au mode "nouvel élève" par défaut
|
|
switchEnrollMode('new');
|
|
}
|
|
|
|
function closeEnrollModal() {
|
|
document.getElementById('enrollModal').classList.add('hidden');
|
|
document.getElementById('enrollModal').classList.remove('flex');
|
|
// Réinitialiser le formulaire
|
|
document.getElementById('enrollForm').reset();
|
|
const today = new Date().toISOString().split('T')[0];
|
|
document.getElementById('enrollment_date').value = today;
|
|
}
|
|
|
|
// Gestion du modal d'import CSV
|
|
function openCSVImportModal() {
|
|
document.getElementById('csvImportModal').classList.remove('hidden');
|
|
document.getElementById('csvImportModal').classList.add('flex');
|
|
// Réinitialiser le formulaire
|
|
document.getElementById('csvImportForm').reset();
|
|
const today = new Date().toISOString().split('T')[0];
|
|
document.getElementById('enrollment_date_csv').value = today;
|
|
}
|
|
|
|
function closeCSVImportModal() {
|
|
document.getElementById('csvImportModal').classList.add('hidden');
|
|
document.getElementById('csvImportModal').classList.remove('flex');
|
|
// Réinitialiser le formulaire
|
|
document.getElementById('csvImportForm').reset();
|
|
}
|
|
|
|
// Gestion du changement de mode d'inscription
|
|
function switchEnrollMode(mode) {
|
|
const existingTab = document.getElementById('existingStudentTab');
|
|
const newTab = document.getElementById('newStudentTab');
|
|
const existingFields = document.getElementById('existingStudentFields');
|
|
const newFields = document.getElementById('newStudentFields');
|
|
const modeInput = document.getElementById('enrollMode');
|
|
|
|
if (mode === 'existing') {
|
|
// Activer l'onglet "Élève existant"
|
|
existingTab.classList.add('text-blue-600', 'border-blue-600');
|
|
existingTab.classList.remove('text-gray-500', 'border-transparent');
|
|
newTab.classList.add('text-gray-500', 'border-transparent');
|
|
newTab.classList.remove('text-blue-600', 'border-blue-600');
|
|
|
|
// Afficher les champs correspondants
|
|
existingFields.classList.remove('hidden');
|
|
newFields.classList.add('hidden');
|
|
|
|
// Mettre à jour le mode
|
|
modeInput.value = 'existing';
|
|
|
|
// Rendre student_id requis
|
|
document.getElementById('student_id').required = true;
|
|
document.getElementById('new_first_name').required = false;
|
|
document.getElementById('new_last_name').required = false;
|
|
} else {
|
|
// Activer l'onglet "Nouvel élève"
|
|
newTab.classList.add('text-blue-600', 'border-blue-600');
|
|
newTab.classList.remove('text-gray-500', 'border-transparent');
|
|
existingTab.classList.add('text-gray-500', 'border-transparent');
|
|
existingTab.classList.remove('text-blue-600', 'border-blue-600');
|
|
|
|
// Afficher les champs correspondants
|
|
newFields.classList.remove('hidden');
|
|
existingFields.classList.add('hidden');
|
|
|
|
// Mettre à jour le mode
|
|
modeInput.value = 'new';
|
|
|
|
// Rendre les champs du nouvel élève requis
|
|
document.getElementById('student_id').required = false;
|
|
document.getElementById('new_first_name').required = true;
|
|
document.getElementById('new_last_name').required = true;
|
|
}
|
|
}
|
|
|
|
// Gestion des modals de transfert
|
|
function transferStudent(studentId, studentName) {
|
|
document.getElementById('transferStudentId').value = studentId;
|
|
document.getElementById('transferStudentName').textContent = studentName;
|
|
document.getElementById('transferModal').classList.remove('hidden');
|
|
document.getElementById('transferModal').classList.add('flex');
|
|
}
|
|
|
|
function closeTransferModal() {
|
|
document.getElementById('transferModal').classList.add('hidden');
|
|
document.getElementById('transferModal').classList.remove('flex');
|
|
}
|
|
|
|
// Gestion des modals de départ
|
|
function departureStudent(studentId, studentName) {
|
|
document.getElementById('departureStudentId').value = studentId;
|
|
document.getElementById('departureStudentName').textContent = studentName;
|
|
document.getElementById('departureModal').classList.remove('hidden');
|
|
document.getElementById('departureModal').classList.add('flex');
|
|
}
|
|
|
|
function closeDepartureModal() {
|
|
document.getElementById('departureModal').classList.add('hidden');
|
|
document.getElementById('departureModal').classList.remove('flex');
|
|
}
|
|
|
|
// Gestion des modals d'annulation de départ
|
|
function cancelDeparture(enrollmentId, studentName) {
|
|
document.getElementById('cancelDepartureEnrollmentId').value = enrollmentId;
|
|
document.getElementById('cancelDepartureStudentName').textContent = studentName;
|
|
document.getElementById('cancelDepartureModal').classList.remove('hidden');
|
|
document.getElementById('cancelDepartureModal').classList.add('flex');
|
|
}
|
|
|
|
function closeCancelDepartureModal() {
|
|
document.getElementById('cancelDepartureModal').classList.add('hidden');
|
|
document.getElementById('cancelDepartureModal').classList.remove('flex');
|
|
}
|
|
|
|
// Fermer les modals en cliquant à l'extérieur
|
|
document.addEventListener('click', function(event) {
|
|
const modals = ['enrollModal', 'transferModal', 'departureModal', 'cancelDepartureModal', 'csvImportModal'];
|
|
modals.forEach(modalId => {
|
|
const modal = document.getElementById(modalId);
|
|
if (event.target === modal) {
|
|
modal.classList.add('hidden');
|
|
modal.classList.remove('flex');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Gestion de l'édition inline des emails
|
|
function startEmailEdit(studentId) {
|
|
// Masquer l'affichage normal
|
|
const displayDesktop = document.getElementById(`email-display-${studentId}`);
|
|
const displayMobile = document.getElementById(`email-display-mobile-${studentId}`);
|
|
const editDesktop = document.getElementById(`email-edit-${studentId}`);
|
|
const editMobile = document.getElementById(`email-edit-mobile-${studentId}`);
|
|
const editBtn = document.getElementById(`email-edit-btn-${studentId}`);
|
|
|
|
if (displayDesktop) {
|
|
displayDesktop.classList.add('hidden');
|
|
editDesktop.classList.remove('hidden');
|
|
editBtn.classList.add('hidden');
|
|
|
|
// Focus sur l'input
|
|
const input = document.getElementById(`email-input-${studentId}`);
|
|
if (input) {
|
|
input.focus();
|
|
input.select();
|
|
}
|
|
}
|
|
|
|
if (displayMobile) {
|
|
displayMobile.classList.add('hidden');
|
|
editMobile.classList.remove('hidden');
|
|
|
|
// Focus sur l'input mobile
|
|
const inputMobile = document.getElementById(`email-input-mobile-${studentId}`);
|
|
if (inputMobile) {
|
|
inputMobile.focus();
|
|
inputMobile.select();
|
|
}
|
|
}
|
|
}
|
|
|
|
function cancelEmailEdit(studentId) {
|
|
// Restaurer l'affichage normal
|
|
const displayDesktop = document.getElementById(`email-display-${studentId}`);
|
|
const displayMobile = document.getElementById(`email-display-mobile-${studentId}`);
|
|
const editDesktop = document.getElementById(`email-edit-${studentId}`);
|
|
const editMobile = document.getElementById(`email-edit-mobile-${studentId}`);
|
|
const editBtn = document.getElementById(`email-edit-btn-${studentId}`);
|
|
|
|
if (displayDesktop) {
|
|
displayDesktop.classList.remove('hidden');
|
|
editDesktop.classList.add('hidden');
|
|
editBtn.classList.remove('hidden');
|
|
}
|
|
|
|
if (displayMobile) {
|
|
displayMobile.classList.remove('hidden');
|
|
editMobile.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
function saveEmail(studentId) {
|
|
// Récupérer la nouvelle valeur
|
|
let newEmail = '';
|
|
const inputDesktop = document.getElementById(`email-input-${studentId}`);
|
|
const inputMobile = document.getElementById(`email-input-mobile-${studentId}`);
|
|
|
|
// Vérifier quel input est actuellement visible/actif
|
|
const editDesktop = document.getElementById(`email-edit-${studentId}`);
|
|
const editMobile = document.getElementById(`email-edit-mobile-${studentId}`);
|
|
|
|
if (editDesktop && !editDesktop.classList.contains('hidden') && inputDesktop) {
|
|
newEmail = inputDesktop.value.trim();
|
|
console.log('Desktop email value:', newEmail);
|
|
} else if (editMobile && !editMobile.classList.contains('hidden') && inputMobile) {
|
|
newEmail = inputMobile.value.trim();
|
|
console.log('Mobile email value:', newEmail);
|
|
}
|
|
|
|
console.log('Final email to save:', newEmail);
|
|
|
|
// Validation basique
|
|
if (newEmail && !isValidEmail(newEmail)) {
|
|
alert('Veuillez saisir un email valide');
|
|
return;
|
|
}
|
|
|
|
// Envoyer la requête AJAX
|
|
const formData = new FormData();
|
|
formData.append('email', newEmail);
|
|
|
|
fetch(`/classes/{{ class_group.id }}/students/${studentId}/update-email`, {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Mettre à jour l'affichage
|
|
updateEmailDisplay(studentId, newEmail);
|
|
cancelEmailEdit(studentId);
|
|
|
|
// Afficher un message de succès discret
|
|
showToast('Email mis à jour avec succès', 'success');
|
|
} else {
|
|
alert('Erreur: ' + (data.error || 'Erreur lors de la mise à jour'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Erreur:', error);
|
|
alert('Erreur lors de la mise à jour de l\'email');
|
|
});
|
|
}
|
|
|
|
function updateEmailDisplay(studentId, newEmail) {
|
|
// Mettre à jour l'affichage desktop
|
|
const displayDesktop = document.getElementById(`email-display-${studentId}`);
|
|
if (displayDesktop) {
|
|
if (newEmail) {
|
|
displayDesktop.innerHTML = `<div class="text-sm text-gray-900">${newEmail}</div>`;
|
|
} else {
|
|
displayDesktop.innerHTML = `
|
|
<div class="flex items-center">
|
|
<span class="text-sm text-gray-400 italic">Cliquez pour ajouter</span>
|
|
<svg class="w-4 h-4 ml-2 text-orange-400" 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>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Mettre à jour l'affichage mobile
|
|
const displayMobile = document.getElementById(`email-display-mobile-${studentId}`);
|
|
if (displayMobile) {
|
|
if (newEmail) {
|
|
displayMobile.innerHTML = `
|
|
<div class="text-xs text-gray-600">${newEmail}</div>
|
|
<button onclick="startEmailEdit(${studentId})"
|
|
class="ml-2 text-gray-400 hover:text-blue-600 p-1"
|
|
title="Modifier l'email">
|
|
<svg class="w-3 h-3" 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>
|
|
</button>
|
|
`;
|
|
} else {
|
|
displayMobile.innerHTML = `
|
|
<div class="text-xs text-gray-400 italic flex items-center">
|
|
<svg class="w-3 h-3 mr-1 text-orange-400" 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>
|
|
Cliquez pour ajouter
|
|
</div>
|
|
<button onclick="startEmailEdit(${studentId})"
|
|
class="ml-2 text-gray-400 hover:text-blue-600 p-1"
|
|
title="Modifier l'email">
|
|
<svg class="w-3 h-3" 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>
|
|
</button>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Mettre à jour la valeur dans les inputs pour la prochaine édition
|
|
const inputDesktop = document.getElementById(`email-input-${studentId}`);
|
|
const inputMobile = document.getElementById(`email-input-mobile-${studentId}`);
|
|
if (inputDesktop) inputDesktop.value = newEmail;
|
|
if (inputMobile) inputMobile.value = newEmail;
|
|
}
|
|
|
|
function isValidEmail(email) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
}
|
|
|
|
function showToast(message, type = 'success') {
|
|
const toast = document.createElement('div');
|
|
const bgColor = type === 'success' ? 'bg-green-500' : 'bg-red-500';
|
|
toast.className = `fixed top-4 right-4 ${bgColor} text-white px-4 py-2 rounded-lg shadow-lg z-50 transition-opacity`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// Fade out après 3 secondes
|
|
setTimeout(() => {
|
|
toast.style.opacity = '0';
|
|
setTimeout(() => {
|
|
document.body.removeChild(toast);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Gestion des touches Entrée et Échap dans les inputs d'email
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.target.type === 'email' && event.target.id.startsWith('email-input')) {
|
|
const studentId = event.target.id.match(/\d+/)[0];
|
|
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
saveEmail(studentId);
|
|
} else if (event.key === 'Escape') {
|
|
event.preventDefault();
|
|
cancelEmailEdit(studentId);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fermer les modals avec la touche Échap
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
const modals = ['enrollModal', 'transferModal', 'departureModal', 'cancelDepartureModal', 'csvImportModal'];
|
|
modals.forEach(modalId => {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal && !modal.classList.contains('hidden')) {
|
|
modal.classList.add('hidden');
|
|
modal.classList.remove('flex');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |