chore: Application du design system

This commit is contained in:
Luc SORIGNET
2026-04-05 12:00:34 +02:00
parent f9c0585b30
commit 2ef71f99c3
124 changed files with 1619 additions and 1508 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import Establishment.models import Establishment.models
import django.contrib.postgres.fields import django.contrib.postgres.fields

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.contrib.postgres.fields import django.contrib.postgres.fields
import django.db.models.deletion import django.db.models.deletion
@ -124,6 +124,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('color_code', models.CharField(default='#FFFFFF', max_length=7)), ('color_code', models.CharField(default='#FFFFFF', max_length=7)),
('school_year', models.CharField(blank=True, max_length=9)),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specialities', to='Establishment.establishment')), ('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specialities', to='Establishment.establishment')),
], ],
), ),
@ -153,6 +154,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_name', models.CharField(max_length=100)), ('last_name', models.CharField(max_length=100)),
('first_name', models.CharField(max_length=100)), ('first_name', models.CharField(max_length=100)),
('school_year', models.CharField(blank=True, max_length=9)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('profile_role', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teacher_profile', to='Auth.profilerole')), ('profile_role', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teacher_profile', to='Auth.profilerole')),
('specialities', models.ManyToManyField(blank=True, to='School.speciality')), ('specialities', models.ManyToManyField(blank=True, to='School.speciality')),

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.3 on 2026-04-04 09:15 # Generated by Django 5.1.3 on 2026-04-05 08:05
import Subscriptions.models import Subscriptions.models
import django.db.models.deletion import django.db.models.deletion
@ -53,7 +53,7 @@ class Migration(migrations.Migration):
('name', models.CharField(default='', max_length=255)), ('name', models.CharField(default='', max_length=255)),
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_school_file_upload_to)), ('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_school_file_upload_to)),
('formTemplateData', models.JSONField(blank=True, default=list, null=True)), ('formTemplateData', models.JSONField(blank=True, default=list, null=True)),
('isValidated', models.BooleanField(default=False)), ('isValidated', models.BooleanField(blank=True, default=None, null=True)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -200,7 +200,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_parent_file_upload_to)), ('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_parent_file_upload_to)),
('isValidated', models.BooleanField(default=False)), ('isValidated', models.BooleanField(blank=True, default=None, null=True)),
('master', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationparentfilemaster')), ('master', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationparentfilemaster')),
('registration_form', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationform')), ('registration_form', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationform')),
], ],

View File

@ -3,16 +3,19 @@ import Logo from '../components/Logo';
export default function Custom500() { export default function Custom500() {
return ( return (
<div className="flex items-center justify-center min-h-screen bg-emerald-500"> <div className="flex items-center justify-center min-h-screen bg-primary">
<div className="text-center p-6 "> <div className="text-center p-6 bg-white rounded-md shadow-sm border border-gray-200">
<Logo className="w-32 h-32 mx-auto mb-4" /> <Logo className="w-32 h-32 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-emerald-900 mb-4"> <h2 className="font-headline text-2xl font-bold text-secondary mb-4">
500 | Erreur interne 500 | Erreur interne
</h2> </h2>
<p className="text-emerald-900 mb-4"> <p className="font-body text-gray-600 mb-4">
Une erreur interne est survenue. Une erreur interne est survenue.
</p> </p>
<Link className="text-gray-900 hover:underline" href="/"> <Link
className="inline-flex items-center justify-center min-h-[44px] px-4 py-2 rounded font-label font-medium bg-primary hover:bg-secondary text-white transition-colors"
href="/"
>
Retour Accueil Retour Accueil
</Link> </Link>
</div> </div>

View File

@ -2,8 +2,17 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
import { PARENT_FILTER, SCHOOL_FILTER } from '@/utils/constants'; import { PARENT_FILTER, SCHOOL_FILTER } from '@/utils/constants';
import { Trash2, ToggleLeft, ToggleRight, Info, XCircle } from 'lucide-react'; import {
Trash2,
ToggleLeft,
ToggleRight,
Info,
XCircle,
Users,
UserPlus,
} from 'lucide-react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import EmptyState from '@/components/EmptyState';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import StatusLabel from '@/components/StatusLabel'; import StatusLabel from '@/components/StatusLabel';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem'; import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
@ -17,7 +26,6 @@ import { dissociateGuardian } from '@/app/actions/subscriptionAction';
import { useCsrfToken } from '@/context/CsrfContext'; import { useCsrfToken } from '@/context/CsrfContext';
import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import AlertMessage from '@/components/AlertMessage';
const roleTypeToLabel = (roleType) => { const roleTypeToLabel = (roleType) => {
switch (roleType) { switch (roleType) {
@ -39,7 +47,7 @@ const roleTypeToBadgeClass = (roleType) => {
case 1: case 1:
return 'bg-red-100 text-red-600'; return 'bg-red-100 text-red-600';
case 2: case 2:
return 'bg-green-100 text-green-600'; return 'bg-tertiary/10 text-tertiary';
default: default:
return 'bg-gray-100 text-gray-600'; return 'bg-gray-100 text-gray-600';
} }
@ -378,7 +386,7 @@ export default function Page() {
type="button" type="button"
className={ className={
row.is_active row.is_active
? 'text-emerald-500 hover:text-emerald-700' ? 'text-primary hover:text-secondary'
: 'text-orange-500 hover:text-orange-700' : 'text-orange-500 hover:text-orange-700'
} }
onClick={() => handleConfirmActivateProfile(row)} onClick={() => handleConfirmActivateProfile(row)}
@ -474,7 +482,7 @@ export default function Page() {
type="button" type="button"
className={ className={
row.is_active row.is_active
? 'text-emerald-500 hover:text-emerald-700' ? 'text-primary hover:text-secondary'
: 'text-orange-500 hover:text-orange-700' : 'text-orange-500 hover:text-orange-700'
} }
onClick={() => handleConfirmActivateProfile(row)} onClick={() => handleConfirmActivateProfile(row)}
@ -516,10 +524,10 @@ export default function Page() {
totalPages={totalProfilesParentPages} totalPages={totalProfilesParentPages}
onPageChange={handlePageChange} onPageChange={handlePageChange}
emptyMessage={ emptyMessage={
<AlertMessage <EmptyState
type="info" icon={Users}
title="Aucun profil PARENT enregistré" title="Aucun profil parent enregistré"
message="Un profil Parent est ajouté lors de la création d'un nouveau dossier d'inscription." description="Les profils parents sont créés automatiquement lors de la création d'un dossier d'inscription."
/> />
} }
/> />
@ -540,10 +548,10 @@ export default function Page() {
totalPages={totalProfilesSchoolPages} totalPages={totalProfilesSchoolPages}
onPageChange={handlePageChange} onPageChange={handlePageChange}
emptyMessage={ emptyMessage={
<AlertMessage <EmptyState
type="info" icon={UserPlus}
title="Aucun profil ECOLE enregistré" title="Aucun profil école enregistré"
message="Un profil ECOLE est ajouté lors de la création d'un nouvel enseignant." description="Les profils école sont créés automatiquement lors de l'ajout d'un enseignant."
/> />
} }
/> />

View File

@ -82,12 +82,12 @@ export default function FeedbackPage() {
return ( return (
<div className="h-full flex flex-col p-4"> <div className="h-full flex flex-col p-4">
<div className="max-w-3xl mx-auto w-full"> <div className="max-w-3xl mx-auto w-full">
<h1 className="text-2xl font-headline font-bold text-gray-800 mb-2"> <h1 className="font-headline text-2xl font-bold text-gray-800 mb-2">
{t('title')} {t('title')}
</h1> </h1>
<p className="text-gray-600 mb-6">{t('description')}</p> <p className="text-gray-600 mb-6">{t('description')}</p>
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-md shadow-sm border border-gray-200 p-6">
{/* Catégorie */} {/* Catégorie */}
<SelectChoice <SelectChoice
name="category" name="category"

View File

@ -251,31 +251,31 @@ export default function StudentGradesPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={() => router.push('/admin/grades')} onClick={() => router.push('/admin/grades')}
className="p-2 rounded-md hover:bg-gray-100 border border-gray-200" className="p-2 rounded hover:bg-gray-100 border border-gray-200 min-h-[44px] min-w-[44px] flex items-center justify-center transition-colors"
aria-label="Retour à la liste" aria-label="Retour à la liste"
> >
<ArrowLeft size={20} /> <ArrowLeft size={20} />
</button> </button>
<h1 className="text-xl font-bold text-gray-800">Suivi pédagogique</h1> <h1 className="font-headline text-xl font-bold text-gray-800">Suivi pédagogique</h1>
</div> </div>
{/* Student profile */} {/* Student profile */}
{student && ( {student && (
<div className="bg-stone-50 rounded-lg shadow-sm border border-gray-100 p-4 md:p-6 flex flex-col sm:flex-row items-center sm:items-start gap-4"> <div className="bg-neutral rounded-md shadow-sm border border-gray-100 p-4 md:p-6 flex flex-col sm:flex-row items-center sm:items-start gap-4">
{student.photo ? ( {student.photo ? (
<img <img
src={getSecureFileUrl(student.photo)} src={getSecureFileUrl(student.photo)}
alt={`${student.first_name} ${student.last_name}`} alt={`${student.first_name} ${student.last_name}`}
className="w-24 h-24 object-cover rounded-full border-4 border-emerald-200 shadow" className="w-24 h-24 object-cover rounded-full border-4 border-primary/20 shadow"
/> />
) : ( ) : (
<div className="w-24 h-24 flex items-center justify-center bg-gray-200 rounded-full text-gray-500 font-bold text-3xl border-4 border-emerald-100"> <div className="w-24 h-24 flex items-center justify-center bg-gray-200 rounded-full text-gray-500 font-bold text-3xl border-4 border-primary/10">
{student.first_name?.[0]} {student.first_name?.[0]}
{student.last_name?.[0]} {student.last_name?.[0]}
</div> </div>
)} )}
<div className="flex-1 text-center sm:text-left"> <div className="flex-1 text-center sm:text-left">
<div className="text-xl font-bold text-emerald-800"> <div className="text-xl font-bold text-secondary">
{student.last_name} {student.first_name} {student.last_name} {student.first_name}
</div> </div>
<div className="text-sm text-gray-600 mt-1"> <div className="text-sm text-gray-600 mt-1">
@ -322,7 +322,7 @@ export default function StudentGradesPage() {
`${FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL}?studentId=${studentId}&period=${periodString}` `${FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL}?studentId=${studentId}&period=${periodString}`
); );
}} }}
className="px-4 py-2 rounded-md shadow bg-emerald-500 text-white hover:bg-emerald-600 w-full sm:w-auto" className="px-4 py-2 rounded shadow bg-primary text-white font-label font-medium hover:bg-secondary w-full sm:w-auto min-h-[44px] transition-colors"
icon={<Award className="w-5 h-5" />} icon={<Award className="w-5 h-5" />}
text="Évaluer" text="Évaluer"
title="Évaluer l'élève" title="Évaluer l'élève"
@ -351,10 +351,10 @@ export default function StudentGradesPage() {
</div> </div>
{/* Évaluations par matière */} {/* Évaluations par matière */}
<div className="bg-stone-50 rounded-lg shadow-sm border border-gray-200 p-4 md:p-6"> <div className="bg-neutral rounded-md shadow-sm border border-gray-200 p-4 md:p-6">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<BookOpen className="w-6 h-6 text-emerald-600" /> <BookOpen className="w-6 h-6 text-primary" />
<h2 className="text-xl font-semibold text-gray-800"> <h2 className="font-headline text-xl font-semibold text-gray-800">
Évaluations par matière Évaluations par matière
</h2> </h2>
</div> </div>

View File

@ -12,13 +12,17 @@ import {
Save, Save,
Download, Download,
FileText, FileText,
UserPlus,
Users,
} from 'lucide-react'; } from 'lucide-react';
import SectionHeader from '@/components/SectionHeader'; import SectionHeader from '@/components/SectionHeader';
import Table from '@/components/Table'; import Table from '@/components/Table';
import EmptyState from '@/components/EmptyState';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { import {
FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL, FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL,
FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL, FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL,
FE_ADMIN_SUBSCRIPTIONS_CREATE_URL,
} from '@/utils/Url'; } from '@/utils/Url';
import { getSecureFileUrl } from '@/utils/fileUrl'; import { getSecureFileUrl } from '@/utils/fileUrl';
import { import {
@ -36,12 +40,22 @@ import { useClasses } from '@/context/ClassesContext';
import { useCsrfToken } from '@/context/CsrfContext'; import { useCsrfToken } from '@/context/CsrfContext';
import { exportToCSV } from '@/utils/exportCSV'; import { exportToCSV } from '@/utils/exportCSV';
import SchoolYearFilter from '@/components/SchoolYearFilter'; import SchoolYearFilter from '@/components/SchoolYearFilter';
import { getCurrentSchoolYear, getNextSchoolYear, getHistoricalYears } from '@/utils/Date'; import {
import { CURRENT_YEAR_FILTER, NEXT_YEAR_FILTER, HISTORICAL_FILTER } from '@/utils/constants'; getCurrentSchoolYear,
getNextSchoolYear,
getHistoricalYears,
} from '@/utils/Date';
import {
CURRENT_YEAR_FILTER,
NEXT_YEAR_FILTER,
HISTORICAL_FILTER,
} from '@/utils/constants';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
function getPeriodString(periodValue, frequency, schoolYear = null) { function getPeriodString(periodValue, frequency, schoolYear = null) {
const year = schoolYear || (() => { const year =
schoolYear ||
(() => {
const y = dayjs().month() >= 8 ? dayjs().year() : dayjs().year() - 1; const y = dayjs().month() >= 8 ? dayjs().year() : dayjs().year() - 1;
return `${y}-${y + 1}`; return `${y}-${y + 1}`;
})(); })();
@ -97,7 +111,7 @@ const COMPETENCY_COLUMNS = [
{ {
key: 'acquired', key: 'acquired',
label: 'Acquises', label: 'Acquises',
color: 'bg-emerald-100 text-emerald-700', color: 'bg-primary/10 text-secondary',
}, },
{ {
key: 'inProgress', key: 'inProgress',
@ -145,7 +159,7 @@ function PercentBadge({ value, loading, color }) {
const badgeColor = const badgeColor =
color || color ||
(value >= 75 (value >= 75
? 'bg-emerald-100 text-emerald-700' ? 'bg-primary/10 text-secondary'
: value >= 50 : value >= 50
? 'bg-yellow-100 text-yellow-700' ? 'bg-yellow-100 text-yellow-700'
: 'bg-red-100 text-red-600'); : 'bg-red-100 text-red-600');
@ -193,9 +207,10 @@ export default function Page() {
return historicalYears[0]; return historicalYears[0];
}, [activeYearFilter, currentSchoolYear, nextSchoolYear, historicalYears]); }, [activeYearFilter, currentSchoolYear, nextSchoolYear, historicalYears]);
const periodColumns = useMemo(() => getPeriodColumns( const periodColumns = useMemo(
selectedEstablishmentEvaluationFrequency () => getPeriodColumns(selectedEstablishmentEvaluationFrequency),
), [selectedEstablishmentEvaluationFrequency]); [selectedEstablishmentEvaluationFrequency]
);
const currentPeriodValue = getCurrentPeriodValue( const currentPeriodValue = getCurrentPeriodValue(
selectedEstablishmentEvaluationFrequency selectedEstablishmentEvaluationFrequency
@ -229,7 +244,11 @@ export default function Page() {
const tasks = students.flatMap((student) => const tasks = students.flatMap((student) =>
periodColumns.map(({ value: periodValue }) => { periodColumns.map(({ value: periodValue }) => {
const periodStr = getPeriodString(periodValue, frequency, selectedSchoolYear); const periodStr = getPeriodString(
periodValue,
frequency,
selectedSchoolYear
);
return fetchStudentCompetencies(student.id, periodStr) return fetchStudentCompetencies(student.id, periodStr)
.then((data) => ({ studentId: student.id, periodValue, data })) .then((data) => ({ studentId: student.id, periodValue, data }))
.catch(() => ({ studentId: student.id, periodValue, data: null })); .catch(() => ({ studentId: student.id, periodValue, data: null }));
@ -281,7 +300,12 @@ export default function Page() {
setStatsMap(map); setStatsMap(map);
setStatsLoading(false); setStatsLoading(false);
}); });
}, [students, selectedEstablishmentEvaluationFrequency, selectedSchoolYear, periodColumns]); }, [
students,
selectedEstablishmentEvaluationFrequency,
selectedSchoolYear,
periodColumns,
]);
const filteredStudents = students.filter( const filteredStudents = students.filter(
(student) => (student) =>
@ -330,12 +354,16 @@ export default function Page() {
{ key: 'last_name', label: 'Nom' }, { key: 'last_name', label: 'Nom' },
{ key: 'first_name', label: 'Prénom' }, { key: 'first_name', label: 'Prénom' },
{ key: 'birth_date', label: 'Date de naissance' }, { key: 'birth_date', label: 'Date de naissance' },
{ key: 'level', label: 'Niveau', transform: (value) => getNiveauLabel(value) }, {
key: 'level',
label: 'Niveau',
transform: (value) => getNiveauLabel(value),
},
{ key: 'associated_class_name', label: 'Classe' }, { key: 'associated_class_name', label: 'Classe' },
{ {
key: 'id', key: 'id',
label: 'Absences', label: 'Absences',
transform: (value) => absencesMap[value] || 0 transform: (value) => absencesMap[value] || 0,
}, },
]; ];
@ -347,7 +375,7 @@ export default function Page() {
transform: (value) => { transform: (value) => {
const stats = statsMap[value]; const stats = statsMap[value];
return stats?.[key] !== undefined ? `${stats[key]}%` : ''; return stats?.[key] !== undefined ? `${stats[key]}%` : '';
} },
}); });
}); });
@ -526,7 +554,7 @@ export default function Page() {
`${FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL}?schoolClassId=${student.associated_class_id}` `${FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL}?schoolClassId=${student.associated_class_id}`
); );
}} }}
className="text-emerald-700 hover:underline font-medium" className="text-secondary hover:underline font-medium"
> >
{student.associated_class_name} {student.associated_class_name}
</button> </button>
@ -581,7 +609,7 @@ export default function Page() {
<button <button
onClick={(e) => handleEvaluer(e, student.id)} onClick={(e) => handleEvaluer(e, student.id)}
disabled={!currentPeriodValue} disabled={!currentPeriodValue}
className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-emerald-100 text-emerald-700 hover:bg-emerald-200 transition whitespace-nowrap disabled:opacity-40 disabled:cursor-not-allowed" className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-primary/10 text-secondary hover:bg-primary/20 transition whitespace-nowrap disabled:opacity-40 disabled:cursor-not-allowed"
title="Évaluer" title="Évaluer"
> >
<Award size={14} /> <Award size={14} />
@ -634,7 +662,7 @@ export default function Page() {
</div> </div>
<button <button
onClick={handleExportCSV} onClick={handleExportCSV}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-emerald-700 bg-emerald-100 rounded-lg hover:bg-emerald-200 transition-colors ml-4" className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-secondary bg-primary/10 rounded hover:bg-primary/20 transition-colors ml-4"
title="Exporter en CSV" title="Exporter en CSV"
> >
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
@ -651,7 +679,22 @@ export default function Page() {
totalPages={totalPages} totalPages={totalPages}
onPageChange={setCurrentPage} onPageChange={setCurrentPage}
emptyMessage={ emptyMessage={
<span className="text-gray-400 text-sm">Aucun élève trouvé</span> students.length === 0 && !searchTerm ? (
<EmptyState
icon={Users}
title="Aucun élève inscrit"
description="Commencez par inscrire des élèves pour suivre leur parcours pédagogique."
actionLabel="Inscrire un élève"
actionIcon={UserPlus}
onAction={() => router.push(FE_ADMIN_SUBSCRIPTIONS_CREATE_URL)}
/>
) : (
<EmptyState
icon={Search}
title="Aucun élève trouvé"
description="Modifiez votre recherche pour trouver un élève."
/>
)
} }
/> />
@ -662,7 +705,7 @@ export default function Page() {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b bg-gray-50"> <div className="flex items-center justify-between px-6 py-4 border-b bg-gray-50">
<div> <div>
<h2 className="text-lg font-semibold text-gray-800"> <h2 className="font-headline text-lg font-semibold text-gray-800">
Notes de {gradesModalStudent.first_name}{' '} Notes de {gradesModalStudent.first_name}{' '}
{gradesModalStudent.last_name} {gradesModalStudent.last_name}
</h2> </h2>
@ -683,7 +726,7 @@ export default function Page() {
<div className="flex-1 overflow-y-auto p-6"> <div className="flex-1 overflow-y-auto p-6">
{gradesLoading ? ( {gradesLoading ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div> </div>
) : Object.keys(groupedBySubject).length === 0 ? ( ) : Object.keys(groupedBySubject).length === 0 ? (
<div className="text-center py-12 text-gray-400"> <div className="text-center py-12 text-gray-400">
@ -720,13 +763,13 @@ export default function Page() {
: null; : null;
return ( return (
<div className="bg-gradient-to-r from-emerald-50 to-blue-50 rounded-lg p-4 border border-emerald-100"> <div className="bg-gradient-to-r from-primary/5 to-blue-50 rounded-lg p-4 border border-primary/10">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-600">
Résumé Résumé
</span> </span>
{overallAvg !== null && ( {overallAvg !== null && (
<span className="text-lg font-bold text-emerald-700"> <span className="text-lg font-bold text-secondary">
Moyenne générale : {overallAvg}/20 Moyenne générale : {overallAvg}/20
</span> </span>
)} )}
@ -878,7 +921,7 @@ export default function Page() {
onClick={() => onClick={() =>
handleSaveEval(evalItem) handleSaveEval(evalItem)
} }
className="p-1 text-emerald-600 hover:bg-emerald-50 rounded" className="p-1 text-primary hover:bg-primary/5 rounded"
title="Enregistrer" title="Enregistrer"
> >
<Save size={14} /> <Save size={14} />

View File

@ -86,12 +86,12 @@ export default function StudentCompetenciesPage() {
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<button <button
onClick={() => router.push('/admin/grades')} onClick={() => router.push('/admin/grades')}
className="p-2 rounded-md hover:bg-gray-100 border border-gray-200" className="p-2 rounded hover:bg-gray-100 border border-gray-200 min-h-[44px] min-w-[44px] flex items-center justify-center transition-colors"
aria-label="Retour à la fiche élève" aria-label="Retour à la fiche élève"
> >
<ArrowLeft size={20} /> <ArrowLeft size={20} />
</button> </button>
<h1 className="text-xl font-bold text-gray-800">Bilan de compétence</h1> <h1 className="font-headline text-xl font-bold text-gray-800">Bilan de compétence</h1>
</div> </div>
<div className="flex-1 min-h-0 flex flex-col"> <div className="flex-1 min-h-0 flex flex-col">
<form <form

View File

@ -158,7 +158,7 @@ export default function Layout({ children }) {
)} )}
{/* Main container */} {/* Main container */}
<div className="absolute overflow-auto bg-gradient-to-br from-emerald-50 via-sky-50 to-emerald-100 top-14 md:top-0 bottom-16 left-0 md:left-64 right-0"> <div className="absolute overflow-auto bg-gradient-to-br from-primary/5 via-sky-50 to-primary/10 top-14 md:top-0 bottom-16 left-0 md:left-64 right-0">
{children} {children}
</div> </div>

View File

@ -174,14 +174,14 @@ export default function DashboardPage() {
<StatCard <StatCard
title={t('pendingRegistrations')} title={t('pendingRegistrations')}
value={pendingRegistrationCount} value={pendingRegistrationCount}
icon={<Clock className="text-green-500" size={24} />} icon={<Clock className="text-tertiary" size={24} />}
color="green" color="tertiary"
/> />
<StatCard <StatCard
title={t('structureCapacity')} title={t('structureCapacity')}
value={selectedEstablishmentTotalCapacity} value={selectedEstablishmentTotalCapacity}
icon={<School className="text-green-500" size={24} />} icon={<School className="text-primary" size={24} />}
color="emerald" color="primary"
/> />
<StatCard <StatCard
title={t('capacityRate')} title={t('capacityRate')}
@ -200,8 +200,8 @@ export default function DashboardPage() {
{/* Colonne de gauche : Graphique des inscriptions + Présence */} {/* Colonne de gauche : Graphique des inscriptions + Présence */}
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* Graphique des inscriptions */} {/* Graphique des inscriptions */}
<div className="bg-stone-50 p-4 md:p-6 rounded-lg shadow-sm border border-gray-100 flex-1"> <div className="bg-neutral p-4 md:p-6 rounded-md shadow-sm border border-gray-100 flex-1">
<h2 className="text-lg font-semibold mb-4 md:mb-6"> <h2 className="font-headline text-lg font-semibold mb-4 md:mb-6">
{t('inscriptionTrends')} {t('inscriptionTrends')}
</h2> </h2>
<div className="flex flex-col sm:flex-row gap-6 mt-4"> <div className="flex flex-col sm:flex-row gap-6 mt-4">
@ -214,14 +214,14 @@ export default function DashboardPage() {
</div> </div>
</div> </div>
{/* Présence et assiduité */} {/* Présence et assiduité */}
<div className="bg-stone-50 p-4 md:p-6 rounded-lg shadow-sm border border-gray-100 flex-1"> <div className="bg-neutral p-4 md:p-6 rounded-md shadow-sm border border-gray-100 flex-1">
<Attendance absences={absencesToday} readOnly={true} /> <Attendance absences={absencesToday} readOnly={true} />
</div> </div>
</div> </div>
{/* Colonne de droite : Événements à venir */} {/* Colonne de droite : Événements à venir */}
<div className="bg-stone-50 p-4 md:p-6 rounded-lg shadow-sm border border-gray-100 flex-1 h-full"> <div className="bg-neutral p-4 md:p-6 rounded-md shadow-sm border border-gray-100 flex-1 h-full">
<h2 className="text-lg font-semibold mb-4">{t('upcomingEvents')}</h2> <h2 className="font-headline text-lg font-semibold mb-4">{t('upcomingEvents')}</h2>
{upcomingEvents.map((event, index) => ( {upcomingEvents.map((event, index) => (
<EventCard key={index} {...event} /> <EventCard key={index} {...event} />
))} ))}

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import Tab from '@/components/Tab';
import TabContent from '@/components/TabContent';
import Button from '@/components/Form/Button'; import Button from '@/components/Form/Button';
import InputText from '@/components/Form/InputText'; import InputText from '@/components/Form/InputText';
import CheckBox from '@/components/Form/CheckBox'; // Import du composant CheckBox import CheckBox from '@/components/Form/CheckBox'; // Import du composant CheckBox
@ -13,13 +11,8 @@ import {
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
import { useCsrfToken } from '@/context/CsrfContext'; // Import du hook pour récupérer le csrfToken import { useCsrfToken } from '@/context/CsrfContext'; // Import du hook pour récupérer le csrfToken
import { useNotification } from '@/context/NotificationContext'; import { useNotification } from '@/context/NotificationContext';
import { useSearchParams } from 'next/navigation'; // Ajoute cet import
export default function SettingsPage() { export default function SettingsPage() {
const [activeTab, setActiveTab] = useState('smtp');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [smtpServer, setSmtpServer] = useState(''); const [smtpServer, setSmtpServer] = useState('');
const [smtpPort, setSmtpPort] = useState(''); const [smtpPort, setSmtpPort] = useState('');
const [smtpUser, setSmtpUser] = useState(''); const [smtpUser, setSmtpUser] = useState('');
@ -29,23 +22,10 @@ export default function SettingsPage() {
const { selectedEstablishmentId } = useEstablishment(); const { selectedEstablishmentId } = useEstablishment();
const csrfToken = useCsrfToken(); // Récupération du csrfToken const csrfToken = useCsrfToken(); // Récupération du csrfToken
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const searchParams = useSearchParams();
const handleTabClick = (tab) => {
setActiveTab(tab);
};
// Ajout : sélection automatique de l'onglet via l'ancre ou le paramètre de recherche
useEffect(() => {
const tabParam = searchParams.get('tab');
if (tabParam === 'smtp') {
setActiveTab('smtp');
}
}, [searchParams]);
// Charger les paramètres SMTP existants // Charger les paramètres SMTP existants
useEffect(() => { useEffect(() => {
if (activeTab === 'smtp') { if (csrfToken && selectedEstablishmentId) {
fetchSmtpSettings(csrfToken, selectedEstablishmentId) // Passer le csrfToken ici fetchSmtpSettings(csrfToken, selectedEstablishmentId) // Passer le csrfToken ici
.then((data) => { .then((data) => {
setSmtpServer(data.smtp_server || ''); setSmtpServer(data.smtp_server || '');
@ -75,7 +55,7 @@ export default function SettingsPage() {
} }
}); });
} }
}, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance }, [csrfToken, selectedEstablishmentId]);
const handleSmtpServerChange = (e) => { const handleSmtpServerChange = (e) => {
setSmtpServer(e.target.value); setSmtpServer(e.target.value);
@ -128,16 +108,14 @@ export default function SettingsPage() {
}; };
return ( return (
<div className="p-8"> <div className="p-6">
<div className="flex space-x-4 mb-4"> <h1 className="font-headline text-2xl font-bold text-gray-900 mb-6">
<Tab Paramètres
text="Paramètres SMTP" </h1>
active={activeTab === 'smtp'} <div className="bg-white rounded-md border border-gray-200 shadow-sm p-6">
onClick={() => handleTabClick('smtp')} <h2 className="font-headline text-lg font-semibold text-gray-800 mb-4">
/> Paramètres SMTP
</div> </h2>
<div className="mt-4">
<TabContent isActive={activeTab === 'smtp'}>
<form onSubmit={handleSmtpSubmit}> <form onSubmit={handleSmtpSubmit}>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<InputText <InputText
@ -187,7 +165,6 @@ export default function SettingsPage() {
className="mt-6" className="mt-6"
></Button> ></Button>
</form> </form>
</TabContent>
</div> </div>
</div> </div>
); );

View File

@ -204,7 +204,7 @@ export default function FormBuilderPage() {
<ArrowLeft size={20} /> <ArrowLeft size={20} />
Retour Retour
</button> </button>
<h1 className="text-lg font-headline font-semibold text-gray-800"> <h1 className="font-headline text-lg font-headline font-semibold text-gray-800">
{isEditing ? 'Modifier le formulaire' : 'Créer un formulaire personnalisé'} {isEditing ? 'Modifier le formulaire' : 'Créer un formulaire personnalisé'}
</h1> </h1>
</div> </div>

View File

@ -1,10 +1,28 @@
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Users, Layers, CheckCircle, Clock, XCircle, ClipboardList, Plus } from 'lucide-react'; import {
Users,
Layers,
CheckCircle,
Clock,
XCircle,
ClipboardList,
Plus,
} from 'lucide-react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import { fetchClasse, fetchSpecialities, fetchEvaluations, createEvaluation, updateEvaluation, deleteEvaluation, fetchStudentEvaluations, saveStudentEvaluations, deleteStudentEvaluation } from '@/app/actions/schoolAction'; import {
fetchClasse,
fetchSpecialities,
fetchEvaluations,
createEvaluation,
updateEvaluation,
deleteEvaluation,
fetchStudentEvaluations,
saveStudentEvaluations,
deleteStudentEvaluation,
} from '@/app/actions/schoolAction';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { useClasses } from '@/context/ClassesContext'; import { useClasses } from '@/context/ClassesContext';
@ -17,7 +35,11 @@ import {
editAbsences, editAbsences,
deleteAbsences, deleteAbsences,
} from '@/app/actions/subscriptionAction'; } from '@/app/actions/subscriptionAction';
import { EvaluationForm, EvaluationList, EvaluationGradeTable } from '@/components/Evaluation'; import {
EvaluationForm,
EvaluationList,
EvaluationGradeTable,
} from '@/components/Evaluation';
import { useCsrfToken } from '@/context/CsrfContext'; import { useCsrfToken } from '@/context/CsrfContext';
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
@ -53,7 +75,8 @@ export default function Page() {
const [editingEvaluation, setEditingEvaluation] = useState(null); const [editingEvaluation, setEditingEvaluation] = useState(null);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
const { selectedEstablishmentId, selectedEstablishmentEvaluationFrequency } = useEstablishment(); const { selectedEstablishmentId, selectedEstablishmentEvaluationFrequency } =
useEstablishment();
// Périodes selon la fréquence d'évaluation // Périodes selon la fréquence d'évaluation
const getPeriods = () => { const getPeriods = () => {
@ -212,16 +235,25 @@ export default function Page() {
const currentSchoolYear = `${year}-${year + 1}`; const currentSchoolYear = `${year}-${year + 1}`;
fetchSpecialities(selectedEstablishmentId, currentSchoolYear) fetchSpecialities(selectedEstablishmentId, currentSchoolYear)
.then((data) => setSpecialities(data)) .then((data) => setSpecialities(data))
.catch((error) => logger.error('Erreur lors du chargement des matières:', error)); .catch((error) =>
logger.error('Erreur lors du chargement des matières:', error)
);
} }
}, [selectedEstablishmentId]); }, [selectedEstablishmentId]);
// Load evaluations when tab is active and period is selected // Load evaluations when tab is active and period is selected
useEffect(() => { useEffect(() => {
if (activeTab === 'evaluations' && selectedEstablishmentId && schoolClassId && selectedPeriod) { if (
activeTab === 'evaluations' &&
selectedEstablishmentId &&
schoolClassId &&
selectedPeriod
) {
fetchEvaluations(selectedEstablishmentId, schoolClassId, selectedPeriod) fetchEvaluations(selectedEstablishmentId, schoolClassId, selectedPeriod)
.then((data) => setEvaluations(data)) .then((data) => setEvaluations(data))
.catch((error) => logger.error('Erreur lors du chargement des évaluations:', error)); .catch((error) =>
logger.error('Erreur lors du chargement des évaluations:', error)
);
} }
}, [activeTab, selectedEstablishmentId, schoolClassId, selectedPeriod]); }, [activeTab, selectedEstablishmentId, schoolClassId, selectedPeriod]);
@ -230,7 +262,9 @@ export default function Page() {
if (selectedEvaluation && schoolClassId) { if (selectedEvaluation && schoolClassId) {
fetchStudentEvaluations(null, selectedEvaluation.id, null, schoolClassId) fetchStudentEvaluations(null, selectedEvaluation.id, null, schoolClassId)
.then((data) => setStudentEvaluations(data)) .then((data) => setStudentEvaluations(data))
.catch((error) => logger.error('Erreur lors du chargement des notes:', error)); .catch((error) =>
logger.error('Erreur lors du chargement des notes:', error)
);
} }
}, [selectedEvaluation, schoolClassId]); }, [selectedEvaluation, schoolClassId]);
@ -241,7 +275,11 @@ export default function Page() {
showNotification('Évaluation créée avec succès', 'success', 'Succès'); showNotification('Évaluation créée avec succès', 'success', 'Succès');
setShowEvaluationForm(false); setShowEvaluationForm(false);
// Reload evaluations // Reload evaluations
const updatedEvaluations = await fetchEvaluations(selectedEstablishmentId, schoolClassId, selectedPeriod); const updatedEvaluations = await fetchEvaluations(
selectedEstablishmentId,
schoolClassId,
selectedPeriod
);
setEvaluations(updatedEvaluations); setEvaluations(updatedEvaluations);
} catch (error) { } catch (error) {
logger.error('Erreur lors de la création:', error); logger.error('Erreur lors de la création:', error);
@ -261,7 +299,11 @@ export default function Page() {
setShowEvaluationForm(false); setShowEvaluationForm(false);
setEditingEvaluation(null); setEditingEvaluation(null);
// Reload evaluations // Reload evaluations
const updatedEvaluations = await fetchEvaluations(selectedEstablishmentId, schoolClassId, selectedPeriod); const updatedEvaluations = await fetchEvaluations(
selectedEstablishmentId,
schoolClassId,
selectedPeriod
);
setEvaluations(updatedEvaluations); setEvaluations(updatedEvaluations);
} catch (error) { } catch (error) {
logger.error('Erreur lors de la modification:', error); logger.error('Erreur lors de la modification:', error);
@ -272,20 +314,31 @@ export default function Page() {
const handleDeleteEvaluation = async (evaluationId) => { const handleDeleteEvaluation = async (evaluationId) => {
await deleteEvaluation(evaluationId, csrfToken); await deleteEvaluation(evaluationId, csrfToken);
// Reload evaluations // Reload evaluations
const updatedEvaluations = await fetchEvaluations(selectedEstablishmentId, schoolClassId, selectedPeriod); const updatedEvaluations = await fetchEvaluations(
selectedEstablishmentId,
schoolClassId,
selectedPeriod
);
setEvaluations(updatedEvaluations); setEvaluations(updatedEvaluations);
}; };
const handleSaveGrades = async (gradesData) => { const handleSaveGrades = async (gradesData) => {
await saveStudentEvaluations(gradesData, csrfToken); await saveStudentEvaluations(gradesData, csrfToken);
// Reload student evaluations // Reload student evaluations
const updatedStudentEvaluations = await fetchStudentEvaluations(null, selectedEvaluation.id, null, schoolClassId); const updatedStudentEvaluations = await fetchStudentEvaluations(
null,
selectedEvaluation.id,
null,
schoolClassId
);
setStudentEvaluations(updatedStudentEvaluations); setStudentEvaluations(updatedStudentEvaluations);
}; };
const handleDeleteGrade = async (studentEvalId) => { const handleDeleteGrade = async (studentEvalId) => {
await deleteStudentEvaluation(studentEvalId, csrfToken); await deleteStudentEvaluation(studentEvalId, csrfToken);
setStudentEvaluations((prev) => prev.filter((se) => se.id !== studentEvalId)); setStudentEvaluations((prev) =>
prev.filter((se) => se.id !== studentEvalId)
);
}; };
const handleLevelClick = (label) => { const handleLevelClick = (label) => {
@ -543,14 +596,16 @@ export default function Page() {
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<h1 className="text-2xl font-bold">{classe?.atmosphere_name}</h1> <h1 className="font-headline text-2xl font-bold">
{classe?.atmosphere_name}
</h1>
{/* Section Niveaux et Enseignants */} {/* Section Niveaux et Enseignants */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Section Niveaux */} {/* Section Niveaux */}
<div className="bg-white p-4 rounded-lg shadow-md"> <div className="bg-white p-4 rounded-md shadow-sm">
<h2 className="text-xl font-semibold mb-4 flex items-center"> <h2 className="font-headline text-xl font-semibold mb-4 flex items-center">
<Layers className="w-6 h-6 mr-2" /> <Layers className="w-6 h-6 mr-2 text-primary" />
Niveaux Niveaux
</h2> </h2>
<p className="text-sm text-gray-500 mb-4"> <p className="text-sm text-gray-500 mb-4">
@ -559,24 +614,24 @@ export default function Page() {
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{classe?.levels?.length > 0 ? ( {classe?.levels?.length > 0 ? (
getNiveauxLabels(classe.levels).map((label, index) => ( getNiveauxLabels(classe.levels).map((label, index) => (
<span <button
key={index} key={index}
onClick={() => handleLevelClick(label)} // Gérer le clic sur un niveau onClick={() => handleLevelClick(label)}
className={`px-4 py-2 rounded-full cursor-pointer border transition-all duration-200 ${ className={`px-4 py-2 rounded font-label font-medium cursor-pointer border transition-colors min-h-[44px] ${
selectedLevels.includes(label) selectedLevels.includes(label)
? 'bg-emerald-200 text-emerald-800 border-emerald-300 shadow-md' ? 'bg-primary/20 text-secondary border-primary/30 shadow-sm'
: 'bg-gray-200 text-gray-800 border-gray-300 hover:bg-gray-300' : 'bg-gray-200 text-gray-800 border-gray-300 hover:bg-gray-300'
}`} }`}
> >
{selectedLevels.includes(label) ? ( {selectedLevels.includes(label) ? (
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-emerald-600" /> <CheckCircle className="w-4 h-4 text-primary" />
{label} {label}
</span> </span>
) : ( ) : (
label label
)} )}
</span> </button>
)) ))
) : ( ) : (
<span className="text-gray-500">Aucun niveau associé</span> <span className="text-gray-500">Aucun niveau associé</span>
@ -585,9 +640,9 @@ export default function Page() {
</div> </div>
{/* Section Enseignants */} {/* Section Enseignants */}
<div className="bg-white p-4 rounded-lg shadow-md"> <div className="bg-white p-4 rounded-md shadow-sm">
<h2 className="text-xl font-semibold mb-4 flex items-center"> <h2 className="font-headline text-xl font-semibold mb-4 flex items-center">
<Users className="w-6 h-6 mr-2" /> <Users className="w-6 h-6 mr-2 text-primary" />
Enseignants Enseignants
</h2> </h2>
<p className="text-sm text-gray-500 mb-4">Liste des enseignants</p> <p className="text-sm text-gray-500 mb-4">Liste des enseignants</p>
@ -595,7 +650,7 @@ export default function Page() {
{classe?.teachers_details?.map((teacher) => ( {classe?.teachers_details?.map((teacher) => (
<span <span
key={teacher.id} key={teacher.id}
className="px-3 py-1 bg-emerald-200 rounded-full text-emerald-800" className="px-3 py-1 bg-primary/20 rounded text-secondary font-label text-sm"
> >
{teacher.last_name} {teacher.first_name} {teacher.last_name} {teacher.first_name}
</span> </span>
@ -605,14 +660,14 @@ export default function Page() {
</div> </div>
{/* Tabs Navigation */} {/* Tabs Navigation */}
<div className="bg-white rounded-lg shadow-md overflow-hidden"> <div className="bg-white rounded-md shadow-sm overflow-hidden">
<div className="flex border-b border-gray-200"> <div className="flex border-b border-gray-200">
<button <button
onClick={() => setActiveTab('attendance')} onClick={() => setActiveTab('attendance')}
className={`flex-1 py-3 px-4 text-center font-medium transition-colors ${ className={`flex-1 py-3 px-4 text-center font-label font-medium transition-colors min-h-[44px] ${
activeTab === 'attendance' activeTab === 'attendance'
? 'text-emerald-600 border-b-2 border-emerald-600 bg-emerald-50' ? 'text-primary border-b-2 border-primary bg-primary/5'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50' : 'text-gray-500 hover:text-secondary hover:bg-gray-50'
}`} }`}
> >
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
@ -622,10 +677,10 @@ export default function Page() {
</button> </button>
<button <button
onClick={() => setActiveTab('evaluations')} onClick={() => setActiveTab('evaluations')}
className={`flex-1 py-3 px-4 text-center font-medium transition-colors ${ className={`flex-1 py-3 px-4 text-center font-label font-medium transition-colors min-h-[44px] ${
activeTab === 'evaluations' activeTab === 'evaluations'
? 'text-emerald-600 border-b-2 border-emerald-600 bg-emerald-50' ? 'text-primary border-b-2 border-primary bg-primary/5'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50' : 'text-gray-500 hover:text-secondary hover:bg-gray-50'
}`} }`}
> >
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
@ -640,14 +695,14 @@ export default function Page() {
{activeTab === 'attendance' && ( {activeTab === 'attendance' && (
<> <>
{/* Affichage de la date du jour */} {/* Affichage de la date du jour */}
<div className="flex justify-between items-center mb-4 bg-white p-4 rounded-lg shadow-md"> <div className="flex justify-between items-center mb-4 bg-white p-4 rounded-md shadow-sm">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="flex items-center justify-center w-10 h-10 bg-emerald-100 text-emerald-600 rounded-full"> <div className="flex items-center justify-center w-10 h-10 bg-primary/10 text-primary rounded">
<Clock className="w-6 h-6" /> <Clock className="w-6 h-6" />
</div> </div>
<h2 className="text-lg font-semibold text-gray-800"> <h2 className="font-headline text-lg font-semibold text-gray-800">
Appel du jour :{' '} Appel du jour :{' '}
<span className="ml-2 text-emerald-600">{today}</span> <span className="ml-2 text-primary">{today}</span>
</h2> </h2>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
@ -656,14 +711,14 @@ export default function Page() {
text="Faire l'appel" text="Faire l'appel"
onClick={handleToggleAttendanceMode} onClick={handleToggleAttendanceMode}
primary primary
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all" className="px-4 py-2 bg-primary text-white font-label font-medium rounded shadow-sm hover:bg-secondary transition-colors min-h-[44px]"
/> />
) : ( ) : (
<Button <Button
text="Valider l'appel" text="Valider l'appel"
onClick={handleValidateAttendance} onClick={handleValidateAttendance}
primary primary
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all" className="px-4 py-2 bg-primary text-white font-label font-medium rounded shadow-sm hover:bg-secondary transition-colors min-h-[44px]"
/> />
)} )}
</div> </div>
@ -702,7 +757,9 @@ export default function Page() {
<CheckBox <CheckBox
item={{ id: row.id }} item={{ id: row.id }}
formData={{ formData={{
attendance: attendance[row.id] ? [row.id] : [], attendance: attendance[row.id]
? [row.id]
: [],
}} }}
handleChange={() => handleChange={() =>
handleAttendanceChange(row.id) handleAttendanceChange(row.id)
@ -733,10 +790,10 @@ export default function Page() {
{/* Détails absence/retard */} {/* Détails absence/retard */}
{!attendance[row.id] && ( {!attendance[row.id] && (
<div className="w-full bg-emerald-50 border border-emerald-100 rounded-lg p-3 mt-2 shadow-sm"> <div className="w-full bg-primary/5 border border-primary/10 rounded-lg p-3 mt-2 shadow-sm">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Clock className="w-4 h-4 text-emerald-500" /> <Clock className="w-4 h-4 text-primary" />
<span className="font-semibold text-emerald-700 text-sm"> <span className="font-semibold text-secondary text-sm">
Motif d&apos;absence Motif d&apos;absence
</span> </span>
</div> </div>
@ -790,7 +847,9 @@ export default function Page() {
type="text" type="text"
className="border rounded px-2 py-1 text-sm w-full" className="border rounded px-2 py-1 text-sm w-full"
placeholder="Commentaire" placeholder="Commentaire"
value={formAbsences[row.id]?.commentaire || ''} value={
formAbsences[row.id]?.commentaire || ''
}
onChange={(e) => onChange={(e) =>
setFormAbsences((prev) => ({ setFormAbsences((prev) => ({
...prev, ...prev,
@ -807,7 +866,8 @@ export default function Page() {
<CheckBox <CheckBox
item={{ id: `justified-${row.id}` }} item={{ id: `justified-${row.id}` }}
formData={{ formData={{
justified: !!formAbsences[row.id]?.justified, justified:
!!formAbsences[row.id]?.justified,
}} }}
handleChange={() => handleChange={() =>
setFormAbsences((prev) => ({ setFormAbsences((prev) => ({
@ -838,7 +898,8 @@ export default function Page() {
formAbsences[row.id] || formAbsences[row.id] ||
Object.values(fetchedAbsences).find( Object.values(fetchedAbsences).find(
(absence) => (absence) =>
absence.student === row.id && absence.day === today absence.student === row.id &&
absence.day === today
); );
if (!absence) { if (!absence) {
@ -900,11 +961,11 @@ export default function Page() {
{activeTab === 'evaluations' && ( {activeTab === 'evaluations' && (
<div className="space-y-4"> <div className="space-y-4">
{/* Header avec sélecteur de période et bouton d'ajout */} {/* Header avec sélecteur de période et bouton d'ajout */}
<div className="bg-white p-4 rounded-lg shadow-md"> <div className="bg-white p-4 rounded-md shadow-sm border border-gray-200">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<ClipboardList className="w-6 h-6 text-emerald-600" /> <ClipboardList className="w-6 h-6 text-primary" />
<h2 className="text-lg font-semibold text-gray-800"> <h2 className="font-headline text-lg font-semibold text-gray-800">
Évaluations de la classe Évaluations de la classe
</h2> </h2>
</div> </div>
@ -936,7 +997,11 @@ export default function Page() {
schoolClassId={parseInt(schoolClassId)} schoolClassId={parseInt(schoolClassId)}
establishmentId={selectedEstablishmentId} establishmentId={selectedEstablishmentId}
initialValues={editingEvaluation} initialValues={editingEvaluation}
onSubmit={editingEvaluation ? handleUpdateEvaluation : handleCreateEvaluation} onSubmit={
editingEvaluation
? handleUpdateEvaluation
: handleCreateEvaluation
}
onCancel={() => { onCancel={() => {
setShowEvaluationForm(false); setShowEvaluationForm(false);
setEditingEvaluation(null); setEditingEvaluation(null);
@ -945,12 +1010,14 @@ export default function Page() {
)} )}
{/* Liste des évaluations */} {/* Liste des évaluations */}
<div className="bg-white p-4 rounded-lg shadow-md"> <div className="bg-white p-4 rounded-md shadow-sm border border-gray-200">
<EvaluationList <EvaluationList
evaluations={evaluations} evaluations={evaluations}
onDelete={handleDeleteEvaluation} onDelete={handleDeleteEvaluation}
onEdit={handleEditEvaluation} onEdit={handleEditEvaluation}
onGradeStudents={(evaluation) => setSelectedEvaluation(evaluation)} onGradeStudents={(evaluation) =>
setSelectedEvaluation(evaluation)
}
/> />
</div> </div>

View File

@ -746,11 +746,11 @@ export default function CreateSubscriptionPage() {
return ( return (
<div className="mx-auto p-12 space-y-12"> <div className="mx-auto p-12 space-y-12">
{registerFormID ? ( {registerFormID ? (
<h1 className="text-2xl font-bold"> <h1 className="font-headline text-2xl font-bold">
Modifier un dossier d&apos;inscription Modifier un dossier d&apos;inscription
</h1> </h1>
) : ( ) : (
<h1 className="text-2xl font-bold"> <h1 className="font-headline text-2xl font-bold">
Créer un dossier d&apos;inscription Créer un dossier d&apos;inscription
</h1> </h1>
)} )}
@ -953,7 +953,7 @@ export default function CreateSubscriptionPage() {
}} }}
rowClassName={(row) => rowClassName={(row) =>
selectedStudent && selectedStudent.id === row.id selectedStudent && selectedStudent.id === row.id
? 'bg-emerald-600 text-white' ? 'bg-primary text-white'
: '' : ''
} }
selectedRows={selectedStudent ? [selectedStudent.id] : []} // Assurez-vous que selectedRows est un tableau selectedRows={selectedStudent ? [selectedStudent.id] : []} // Assurez-vous que selectedRows est un tableau
@ -965,7 +965,7 @@ export default function CreateSubscriptionPage() {
{selectedStudent && ( {selectedStudent && (
<div className="mt-4"> <div className="mt-4">
<h3 className="font-bold"> <h3 className="font-headline font-bold">
Responsables associés à {selectedStudent.last_name}{' '} Responsables associés à {selectedStudent.last_name}{' '}
{selectedStudent.first_name} : {selectedStudent.first_name} :
</h3> </h3>
@ -1026,7 +1026,7 @@ export default function CreateSubscriptionPage() {
</div> </div>
{/* Montant total */} {/* Montant total */}
<div className="flex items-center justify-between bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-300 mt-4"> <div className="flex items-center justify-between bg-gray-50 p-4 rounded-md shadow-sm border border-gray-300 mt-4">
<span className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-600">
Montant total des frais d&apos;inscription : Montant total des frais d&apos;inscription :
</span> </span>
@ -1073,7 +1073,7 @@ export default function CreateSubscriptionPage() {
</div> </div>
{/* Montant total */} {/* Montant total */}
<div className="flex items-center justify-between bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-300 mt-4"> <div className="flex items-center justify-between bg-gray-50 p-4 rounded-md shadow-sm border border-gray-300 mt-4">
<span className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-600">
Montant total des frais de scolarité : Montant total des frais de scolarité :
</span> </span>
@ -1136,7 +1136,7 @@ export default function CreateSubscriptionPage() {
className={`px-6 py-2 rounded-md shadow ${ className={`px-6 py-2 rounded-md shadow ${
isSubmitDisabled() isSubmitDisabled()
? 'bg-gray-300 text-gray-500 cursor-not-allowed' ? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-primary text-white hover:bg-primary'
}`} }`}
primary primary
disabled={isSubmitDisabled()} disabled={isSubmitDisabled()}

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Tab from '@/components/Tab'; import SidebarTabs from '@/components/SidebarTabs';
import Textarea from '@/components/Textarea'; import Textarea from '@/components/Textarea';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import StatusLabel from '@/components/StatusLabel'; import StatusLabel from '@/components/StatusLabel';
@ -55,6 +55,7 @@ import {
HISTORICAL_FILTER, HISTORICAL_FILTER,
} from '@/utils/constants'; } from '@/utils/constants';
import AlertMessage from '@/components/AlertMessage'; import AlertMessage from '@/components/AlertMessage';
import EmptyState from '@/components/EmptyState';
import { useNotification } from '@/context/NotificationContext'; import { useNotification } from '@/context/NotificationContext';
import { exportToCSV } from '@/utils/exportCSV'; import { exportToCSV } from '@/utils/exportCSV';
@ -167,24 +168,65 @@ export default function Page({ params: { locale } }) {
// Export CSV // Export CSV
const handleExportCSV = () => { const handleExportCSV = () => {
const dataToExport = activeTab === CURRENT_YEAR_FILTER const dataToExport =
activeTab === CURRENT_YEAR_FILTER
? registrationFormsDataCurrentYear ? registrationFormsDataCurrentYear
: activeTab === NEXT_YEAR_FILTER : activeTab === NEXT_YEAR_FILTER
? registrationFormsDataNextYear ? registrationFormsDataNextYear
: registrationFormsDataHistorical; : registrationFormsDataHistorical;
const exportColumns = [ const exportColumns = [
{ key: 'student', label: 'Nom', transform: (value) => value?.last_name || '' }, {
{ key: 'student', label: 'Prénom', transform: (value) => value?.first_name || '' }, key: 'student',
{ key: 'student', label: 'Date de naissance', transform: (value) => value?.birth_date || '' }, label: 'Nom',
{ key: 'student', label: 'Email contact', transform: (value) => value?.guardians?.[0]?.associated_profile_email || '' }, transform: (value) => value?.last_name || '',
{ key: 'student', label: 'Téléphone contact', transform: (value) => value?.guardians?.[0]?.phone || '' }, },
{ key: 'student', label: 'Nom responsable 1', transform: (value) => value?.guardians?.[0]?.last_name || '' }, {
{ key: 'student', label: 'Prénom responsable 1', transform: (value) => value?.guardians?.[0]?.first_name || '' }, key: 'student',
{ key: 'student', label: 'Nom responsable 2', transform: (value) => value?.guardians?.[1]?.last_name || '' }, label: 'Prénom',
{ key: 'student', label: 'Prénom responsable 2', transform: (value) => value?.guardians?.[1]?.first_name || '' }, transform: (value) => value?.first_name || '',
},
{
key: 'student',
label: 'Date de naissance',
transform: (value) => value?.birth_date || '',
},
{
key: 'student',
label: 'Email contact',
transform: (value) =>
value?.guardians?.[0]?.associated_profile_email || '',
},
{
key: 'student',
label: 'Téléphone contact',
transform: (value) => value?.guardians?.[0]?.phone || '',
},
{
key: 'student',
label: 'Nom responsable 1',
transform: (value) => value?.guardians?.[0]?.last_name || '',
},
{
key: 'student',
label: 'Prénom responsable 1',
transform: (value) => value?.guardians?.[0]?.first_name || '',
},
{
key: 'student',
label: 'Nom responsable 2',
transform: (value) => value?.guardians?.[1]?.last_name || '',
},
{
key: 'student',
label: 'Prénom responsable 2',
transform: (value) => value?.guardians?.[1]?.first_name || '',
},
{ key: 'school_year', label: 'Année scolaire' }, { key: 'school_year', label: 'Année scolaire' },
{ key: 'status', label: 'Statut', transform: (value) => { {
key: 'status',
label: 'Statut',
transform: (value) => {
const statusMap = { const statusMap = {
0: 'En attente', 0: 'En attente',
1: 'En cours', 1: 'En cours',
@ -195,11 +237,13 @@ export default function Page({ params: { locale } }) {
6: 'Archivé', 6: 'Archivé',
}; };
return statusMap[value] || value; return statusMap[value] || value;
}}, },
},
{ key: 'formatted_last_update', label: 'Dernière mise à jour' }, { key: 'formatted_last_update', label: 'Dernière mise à jour' },
]; ];
const yearLabel = activeTab === CURRENT_YEAR_FILTER const yearLabel =
activeTab === CURRENT_YEAR_FILTER
? currentSchoolYear ? currentSchoolYear
: activeTab === NEXT_YEAR_FILTER : activeTab === NEXT_YEAR_FILTER
? nextSchoolYear ? nextSchoolYear
@ -506,7 +550,7 @@ export default function Page({ params: { locale } }) {
{ {
icon: ( icon: (
<span title="Envoyer le dossier"> <span title="Envoyer le dossier">
<Send className="w-5 h-5 text-green-500 hover:text-green-700" /> <Send className="w-5 h-5 text-primary hover:text-secondary" />
</span> </span>
), ),
onClick: () => onClick: () =>
@ -536,7 +580,7 @@ export default function Page({ params: { locale } }) {
{ {
icon: ( icon: (
<span title="Renvoyer le dossier"> <span title="Renvoyer le dossier">
<Send className="w-5 h-5 text-green-500 hover:text-green-700" /> <Send className="w-5 h-5 text-primary hover:text-secondary" />
</span> </span>
), ),
onClick: () => onClick: () =>
@ -575,7 +619,7 @@ export default function Page({ params: { locale } }) {
{ {
icon: ( icon: (
<span title="Valider le dossier"> <span title="Valider le dossier">
<CheckCircle className="w-5 h-5 text-green-500 hover:text-green-700" /> <CheckCircle className="w-5 h-5 text-primary hover:text-secondary" />
</span> </span>
), ),
onClick: () => { onClick: () => {
@ -690,7 +734,7 @@ export default function Page({ params: { locale } }) {
{ {
icon: ( icon: (
<span title="Uploader un mandat SEPA"> <span title="Uploader un mandat SEPA">
<Upload className="w-5 h-5 text-emerald-500 hover:text-emerald-700" /> <Upload className="w-5 h-5 text-primary hover:text-secondary" />
</span> </span>
), ),
onClick: () => openSepaUploadModal(row), onClick: () => openSepaUploadModal(row),
@ -800,90 +844,51 @@ export default function Page({ params: { locale } }) {
}, },
]; ];
let emptyMessage; const getEmptyMessageForTab = (tabFilter) => {
if (activeTab === CURRENT_YEAR_FILTER && searchTerm === '') { if (searchTerm !== '') {
emptyMessage = (
<AlertMessage
type="warning"
title="Aucun dossier d'inscription pour l'année en cours"
message="Veuillez procéder à la création d'un nouveau dossier d'inscription pour l'année scolaire en cours."
/>
);
} else if (activeTab === NEXT_YEAR_FILTER && searchTerm === '') {
emptyMessage = (
<AlertMessage
type="info"
title="Aucun dossier d'inscription pour l'année prochaine"
message="Aucun dossier n'a encore été créé pour la prochaine année scolaire."
/>
);
} else if (activeTab === HISTORICAL_FILTER && searchTerm === '') {
emptyMessage = (
<AlertMessage
type="info"
title="Aucun dossier d'inscription historique"
message="Aucun dossier archivé n'est disponible pour les années précédentes."
/>
);
}
if (isLoading) {
return <Loader />;
}
return ( return (
<div className="p-8"> <EmptyState
<div className="border-b border-gray-200 mb-6"> icon={Search}
<div className="flex items-center gap-8"> title="Aucun dossier trouvé"
{/* Tab pour l'année scolaire en cours */} description="Modifiez votre recherche pour trouver un dossier d'inscription."
<Tab
text={
<>
{currentSchoolYear}
<span className="ml-2 text-sm text-gray-400">
({totalCurrentYear})
</span>
</>
}
active={activeTab === CURRENT_YEAR_FILTER}
onClick={() => setActiveTab(CURRENT_YEAR_FILTER)}
/> />
);
{/* Tab pour l'année scolaire prochaine */}
<Tab
text={
<>
{nextSchoolYear}
<span className="ml-2 text-sm text-gray-400">
({totalNextYear})
</span>
</>
} }
active={activeTab === NEXT_YEAR_FILTER} if (tabFilter === CURRENT_YEAR_FILTER) {
onClick={() => setActiveTab(NEXT_YEAR_FILTER)} return (
<EmptyState
icon={FileText}
title="Aucun dossier d'inscription pour l'année en cours"
description="Commencez par créer un dossier d'inscription pour l'année scolaire en cours."
actionLabel="Créer un dossier"
actionIcon={Plus}
onAction={() => router.push(FE_ADMIN_SUBSCRIPTIONS_CREATE_URL)}
/> />
);
{/* Tab pour l'historique */}
<Tab
text={
<>
{t('historical')}
<span className="ml-2 text-sm text-gray-400">
({totalHistorical})
</span>
</>
} }
active={activeTab === HISTORICAL_FILTER} if (tabFilter === NEXT_YEAR_FILTER) {
onClick={() => setActiveTab(HISTORICAL_FILTER)} return (
<EmptyState
icon={FileText}
title="Aucun dossier pour l'année prochaine"
description="Aucun dossier n'a encore été créé pour la prochaine année scolaire."
actionLabel="Créer un dossier"
actionIcon={Plus}
onAction={() => router.push(FE_ADMIN_SUBSCRIPTIONS_CREATE_URL)}
/> />
</div> );
</div> }
return (
<EmptyState
icon={Archive}
title="Aucun dossier archivé"
description="Aucun dossier archivé n'est disponible pour les années précédentes."
/>
);
};
<div className="border-b border-gray-200 mb-6 w-full"> const renderTabContent = (data, currentPage, totalPages, tabFilter) => (
{activeTab === CURRENT_YEAR_FILTER || <div className="p-4">
activeTab === NEXT_YEAR_FILTER ||
activeTab === HISTORICAL_FILTER ? (
<React.Fragment>
<div className="flex justify-between items-center mb-4 w-full"> <div className="flex justify-between items-center mb-4 w-full">
<div className="relative flex-grow"> <div className="relative flex-grow">
<Search <Search
@ -901,7 +906,7 @@ export default function Page({ params: { locale } }) {
<div className="flex items-center gap-2 ml-4"> <div className="flex items-center gap-2 ml-4">
<button <button
onClick={handleExportCSV} onClick={handleExportCSV}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-emerald-700 bg-emerald-100 rounded-lg hover:bg-emerald-200 transition-colors" className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-secondary bg-primary/10 rounded hover:bg-primary/20 transition-colors"
title="Exporter en CSV" title="Exporter en CSV"
> >
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
@ -909,52 +914,69 @@ export default function Page({ params: { locale } }) {
</button> </button>
{profileRole !== 0 && ( {profileRole !== 0 && (
<button <button
onClick={() => { onClick={() => router.push(FE_ADMIN_SUBSCRIPTIONS_CREATE_URL)}
const url = `${FE_ADMIN_SUBSCRIPTIONS_CREATE_URL}`; className="flex items-center bg-primary text-white p-2 rounded-full shadow hover:bg-secondary transition duration-200"
router.push(url);
}}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
> >
<Plus className="w-5 h-5" /> <Plus className="w-5 h-5" />
</button> </button>
)} )}
</div> </div>
</div> </div>
<div className="w-full">
<DjangoCSRFToken csrfToken={csrfToken} /> <DjangoCSRFToken csrfToken={csrfToken} />
<Table <Table
key={`${currentSchoolYearPage}-${searchTerm}`} key={`${tabFilter}-${currentPage}-${searchTerm}`}
data={ data={data}
activeTab === CURRENT_YEAR_FILTER
? registrationFormsDataCurrentYear
: activeTab === NEXT_YEAR_FILTER
? registrationFormsDataNextYear
: registrationFormsDataHistorical
}
columns={columns} columns={columns}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
currentPage={ currentPage={currentPage}
activeTab === CURRENT_YEAR_FILTER totalPages={totalPages}
? currentSchoolYearPage
: activeTab === NEXT_YEAR_FILTER
? currentSchoolNextYearPage
: currentSchoolHistoricalYearPage
}
totalPages={
activeTab === CURRENT_YEAR_FILTER
? totalCurrentSchoolYearPages
: activeTab === NEXT_YEAR_FILTER
? totalNextSchoolYearPages
: totalHistoricalPages
}
onPageChange={handlePageChange} onPageChange={handlePageChange}
emptyMessage={emptyMessage} emptyMessage={getEmptyMessageForTab(tabFilter)}
/> />
</div> </div>
</React.Fragment> );
) : null}
</div> if (isLoading) {
return <Loader />;
}
return (
<div className="h-full flex flex-col">
<SidebarTabs
tabs={[
{
id: CURRENT_YEAR_FILTER,
label: `${currentSchoolYear}${totalCurrentYear > 0 ? ` (${totalCurrentYear})` : ''}`,
content: renderTabContent(
registrationFormsDataCurrentYear,
currentSchoolYearPage,
totalCurrentSchoolYearPages,
CURRENT_YEAR_FILTER
),
},
{
id: NEXT_YEAR_FILTER,
label: `${nextSchoolYear}${totalNextYear > 0 ? ` (${totalNextYear})` : ''}`,
content: renderTabContent(
registrationFormsDataNextYear,
currentSchoolNextYearPage,
totalNextSchoolYearPages,
NEXT_YEAR_FILTER
),
},
{
id: HISTORICAL_FILTER,
label: `${t('historical')}${totalHistorical > 0 ? ` (${totalHistorical})` : ''}`,
content: renderTabContent(
registrationFormsDataHistorical,
currentSchoolHistoricalYearPage,
totalHistoricalPages,
HISTORICAL_FILTER
),
},
]}
onTabChange={(newTab) => setActiveTab(newTab)}
/>
<Popup <Popup
isOpen={confirmPopupVisible} isOpen={confirmPopupVisible}
message={confirmPopupMessage} message={confirmPopupMessage}

View File

@ -10,10 +10,10 @@ export default function Home() {
const t = useTranslations('homePage'); const t = useTranslations('homePage');
return ( return (
<div className="flex flex-col items-center justify-center min-h-screen py-2"> <div className="flex flex-col items-center justify-center min-h-screen py-2 bg-neutral">
<Logo className="mb-4" /> {/* Ajout du logo */} <Logo className="mb-4" />
<h1 className="text-4xl font-bold mb-4">{t('welcomeParents')}</h1> <h1 className="font-headline text-4xl font-bold mb-4">{t('welcomeParents')}</h1>
<p className="text-lg mb-8">{t('pleaseLogin')}</p> <p className="font-body text-lg mb-8">{t('pleaseLogin')}</p>
<Button text={t('loginButton')} primary href="/users/login" /> <Button text={t('loginButton')} primary href="/users/login" />
</div> </div>
); );

View File

@ -100,7 +100,7 @@ export default function Layout({ children }) {
{/* Main container */} {/* Main container */}
<div <div
className={`absolute overflow-auto bg-gradient-to-br from-emerald-50 via-sky-50 to-emerald-100 top-14 md:top-0 bottom-16 left-0 md:left-64 right-0 ${!isMessagingPage ? 'p-4 md:p-8' : ''}`} className={`absolute overflow-auto bg-gradient-to-br from-primary/5 via-sky-50 to-primary/10 top-14 md:top-0 bottom-16 left-0 md:left-64 right-0 ${!isMessagingPage ? 'p-4 md:p-8' : ''}`}
> >
{children} {children}
</div> </div>

View File

@ -282,10 +282,10 @@ export default function ParentHomePage() {
<> <>
<div className="p-4 flex items-center border-b"> <div className="p-4 flex items-center border-b">
<button <button
className="text-emerald-600 hover:text-emerald-800 font-semibold flex items-center" className="text-primary hover:text-secondary font-label font-medium min-h-[44px] flex items-center transition-colors"
onClick={() => setShowPlanning(false)} onClick={() => setShowPlanning(false)}
> >
Retour <ArrowLeft className="w-4 h-4 mr-1" /> Retour
</button> </button>
</div> </div>
<div className="flex-1 flex overflow-hidden"> <div className="flex-1 flex overflow-hidden">
@ -309,7 +309,7 @@ export default function ParentHomePage() {
title="Événements à venir" title="Événements à venir"
description="Prochains événements de l'établissement" description="Prochains événements de l'établissement"
/> />
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100"> <div className="bg-neutral p-4 rounded-md shadow-sm border border-gray-100">
{upcomingEvents.slice(0, 3).map((event, index) => ( {upcomingEvents.slice(0, 3).map((event, index) => (
<EventCard key={index} {...event} /> <EventCard key={index} {...event} />
))} ))}
@ -343,7 +343,7 @@ export default function ParentHomePage() {
return ( return (
<div <div
key={student.id} key={student.id}
className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden" className="bg-white rounded-md shadow-sm border border-gray-200 overflow-hidden"
> >
{/* En-tête de la carte (toujours visible) */} {/* En-tête de la carte (toujours visible) */}
<div <div
@ -373,7 +373,7 @@ export default function ParentHomePage() {
{/* Infos principales */} {/* Infos principales */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-gray-800"> <h3 className="font-headline text-lg font-semibold text-gray-800">
{student.last_name} {student.first_name} {student.last_name} {student.first_name}
</h3> </h3>
<div className="mt-1"> <div className="mt-1">
@ -399,7 +399,7 @@ export default function ParentHomePage() {
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
{child.status === 2 && ( {child.status === 2 && (
<button <button
className="p-2 text-blue-500 hover:text-blue-700 hover:bg-blue-50 rounded-full" className="p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-blue-500 hover:text-blue-700 hover:bg-blue-50 rounded-full transition-colors"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleEdit(student.id); handleEdit(student.id);
@ -412,7 +412,7 @@ export default function ParentHomePage() {
{(child.status === 3 || child.status === 8 || child.status === 5 || child.status === 7) && ( {(child.status === 3 || child.status === 8 || child.status === 5 || child.status === 7) && (
<button <button
className="p-2 text-purple-500 hover:text-purple-700 hover:bg-purple-50 rounded-full" className="p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-purple-500 hover:text-purple-700 hover:bg-purple-50 rounded-full transition-colors"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleView(student.id); handleView(student.id);
@ -429,14 +429,14 @@ export default function ParentHomePage() {
href={getSecureFileUrl(child.sepa_file)} href={getSecureFileUrl(child.sepa_file)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="p-2 text-green-500 hover:text-green-700 hover:bg-green-50 rounded-full" className="p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-primary hover:text-secondary hover:bg-primary/5 rounded-full transition-colors"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
title="Télécharger le mandat SEPA" title="Télécharger le mandat SEPA"
> >
<Download className="h-5 w-5" /> <Download className="h-5 w-5" />
</a> </a>
<button <button
className={`p-2 rounded-full ${ className={`p-2 min-h-[44px] min-w-[44px] flex items-center justify-center rounded-full transition-colors ${
uploadingStudentId === student.id && uploadState === 'on' uploadingStudentId === student.id && uploadState === 'on'
? 'bg-blue-100 text-blue-600' ? 'bg-blue-100 text-blue-600'
: 'text-blue-500 hover:text-blue-700 hover:bg-blue-50' : 'text-blue-500 hover:text-blue-700 hover:bg-blue-50'
@ -455,7 +455,7 @@ export default function ParentHomePage() {
{isEnrolled && ( {isEnrolled && (
<> <>
<button <button
className="p-2 text-primary hover:text-secondary hover:bg-tertiary/10 rounded-full" className="p-2 min-h-[44px] min-w-[44px] flex items-center justify-center text-primary hover:text-secondary hover:bg-tertiary/10 rounded-full transition-colors"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
showClassPlanning(student); showClassPlanning(student);
@ -484,7 +484,7 @@ export default function ParentHomePage() {
onFileSelect={handleFileUpload} onFileSelect={handleFileUpload}
/> />
<button <button
className={`mt-4 px-6 py-2 rounded-md ${ className={`mt-4 px-4 py-2 rounded font-label font-medium min-h-[44px] transition-colors ${
uploadedFile uploadedFile
? 'bg-primary text-white hover:bg-secondary' ? 'bg-primary text-white hover:bg-secondary'
: 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-gray-300 text-gray-700 cursor-not-allowed'
@ -499,10 +499,10 @@ export default function ParentHomePage() {
{/* Section détaillée pour les élèves inscrits (expanded) */} {/* Section détaillée pour les élèves inscrits (expanded) */}
{isEnrolled && isExpanded && ( {isEnrolled && isExpanded && (
<div className="border-t bg-stone-50 p-4 space-y-6"> <div className="border-t bg-neutral p-4 space-y-6">
{/* Bloc période : compétences + notes */} {/* Bloc période : compétences + notes */}
<div className="bg-white rounded-lg border border-primary/20 p-4 space-y-4"> <div className="bg-white rounded-md border border-primary/20 p-4 space-y-4">
{/* Sélecteur de période */} {/* Sélecteur de période */}
<div className="flex items-center gap-3 pb-3 border-b border-gray-100"> <div className="flex items-center gap-3 pb-3 border-b border-gray-100">
<div className="w-full sm:w-48"> <div className="w-full sm:w-48">
@ -540,10 +540,10 @@ export default function ParentHomePage() {
const pctNotEvaluated = total ? Math.round((notEvaluated / total) * 100) : 0; const pctNotEvaluated = total ? Math.round((notEvaluated / total) * 100) : 0;
return ( return (
<div className="border border-gray-100 rounded-lg p-4"> <div className="border border-gray-100 rounded-md p-4">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<Award className="w-5 h-5 text-primary" /> <Award className="w-5 h-5 text-primary" />
<h3 className="text-lg font-semibold text-gray-800"> <h3 className="font-headline text-lg font-semibold text-gray-800">
Compétences Compétences
</h3> </h3>
{total > 0 && ( {total > 0 && (
@ -552,19 +552,19 @@ export default function ParentHomePage() {
</div> </div>
{total > 0 ? ( {total > 0 ? (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="flex flex-col items-center p-3 bg-emerald-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-primary/5 rounded-md">
<span className="text-2xl font-bold text-emerald-600">{pctAcquired}%</span> <span className="text-2xl font-bold text-primary">{pctAcquired}%</span>
<span className="text-sm text-emerald-700">Acquises</span> <span className="text-sm text-secondary">Acquises</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-yellow-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-yellow-50 rounded-md">
<span className="text-2xl font-bold text-yellow-600">{pctInProgress}%</span> <span className="text-2xl font-bold text-yellow-600">{pctInProgress}%</span>
<span className="text-sm text-yellow-700">En cours</span> <span className="text-sm text-yellow-700">En cours</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-red-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-red-50 rounded-md">
<span className="text-2xl font-bold text-red-500">{pctNotAcquired}%</span> <span className="text-2xl font-bold text-red-500">{pctNotAcquired}%</span>
<span className="text-sm text-red-600">Non acquises</span> <span className="text-sm text-red-600">Non acquises</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-gray-100 rounded-lg"> <div className="flex flex-col items-center p-3 bg-gray-100 rounded-md">
<span className="text-2xl font-bold text-gray-500">{pctNotEvaluated}%</span> <span className="text-2xl font-bold text-gray-500">{pctNotEvaluated}%</span>
<span className="text-sm text-gray-600">Non évaluées</span> <span className="text-sm text-gray-600">Non évaluées</span>
</div> </div>
@ -577,10 +577,10 @@ export default function ParentHomePage() {
})()} })()}
{/* Notes par matière - Vue simplifiée */} {/* Notes par matière - Vue simplifiée */}
<div className="border border-gray-100 rounded-lg p-4"> <div className="border border-gray-100 rounded-md p-4">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<Award className="w-5 h-5 text-primary" /> <Award className="w-5 h-5 text-primary" />
<h3 className="text-lg font-semibold text-gray-800"> <h3 className="font-headline text-lg font-semibold text-gray-800">
Notes par matière Notes par matière
</h3> </h3>
</div> </div>
@ -617,7 +617,7 @@ export default function ParentHomePage() {
return ( return (
<div <div
key={group.name} key={group.name}
className="rounded-lg p-4 border" className="rounded-md p-4 border"
style={{ style={{
backgroundColor: `${group.color}10`, backgroundColor: `${group.color}10`,
borderColor: `${group.color}40`, borderColor: `${group.color}40`,
@ -657,28 +657,28 @@ export default function ParentHomePage() {
{/* Fin bloc période */} {/* Fin bloc période */}
{/* Section Absences — toute l'année scolaire */} {/* Section Absences — toute l'année scolaire */}
<div className="bg-white rounded-lg border border-gray-200 p-4"> <div className="bg-white rounded-md border border-gray-200 p-4">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Clock className="w-5 h-5 text-primary" /> <Clock className="w-5 h-5 text-primary" />
<h3 className="text-lg font-semibold text-gray-800"> <h3 className="font-headline text-lg font-semibold text-gray-800">
Absences & Retards Absences & Retards
</h3> </h3>
</div> </div>
<p className="text-xs text-gray-400 mb-4">Toute l&apos;année scolaire</p> <p className="text-xs text-gray-400 mb-4">Toute l&apos;année scolaire</p>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="flex flex-col items-center p-3 bg-green-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-primary/5 rounded-md">
<span className="text-2xl font-bold text-green-600">{absenceStats.justifiedAbsence}</span> <span className="text-2xl font-bold text-primary">{absenceStats.justifiedAbsence}</span>
<span className="text-sm text-green-700 text-center">Absences justifiées</span> <span className="text-sm text-secondary text-center">Absences justifiées</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-red-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-red-50 rounded-md">
<span className="text-2xl font-bold text-red-500">{absenceStats.unjustifiedAbsence}</span> <span className="text-2xl font-bold text-red-500">{absenceStats.unjustifiedAbsence}</span>
<span className="text-sm text-red-600 text-center">Absences non justifiées</span> <span className="text-sm text-red-600 text-center">Absences non justifiées</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-blue-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-blue-50 rounded-md">
<span className="text-2xl font-bold text-blue-600">{absenceStats.justifiedLate}</span> <span className="text-2xl font-bold text-blue-600">{absenceStats.justifiedLate}</span>
<span className="text-sm text-blue-700 text-center">Retards justifiés</span> <span className="text-sm text-blue-700 text-center">Retards justifiés</span>
</div> </div>
<div className="flex flex-col items-center p-3 bg-orange-50 rounded-lg"> <div className="flex flex-col items-center p-3 bg-orange-50 rounded-md">
<span className="text-2xl font-bold text-orange-500">{absenceStats.unjustifiedLate}</span> <span className="text-2xl font-bold text-orange-500">{absenceStats.unjustifiedLate}</span>
<span className="text-sm text-orange-600 text-center">Retards non justifiés</span> <span className="text-sm text-orange-600 text-center">Retards non justifiés</span>
</div> </div>

View File

@ -39,9 +39,12 @@ export default function SettingsPage() {
}; };
return ( return (
<div className="p-4"> <div className="p-6">
<h2 className="text-xl mb-4">Paramètres du compte</h2> <h1 className="font-headline text-2xl font-bold text-gray-900 mb-6">
<form onSubmit={handleSubmit}> Paramètres du compte
</h1>
<div className="bg-white rounded-md border border-gray-200 shadow-sm p-6 max-w-md">
<form onSubmit={handleSubmit} className="space-y-4">
<InputText <InputText
type="email" type="email"
id="email" id="email"
@ -66,10 +69,11 @@ export default function SettingsPage() {
onChange={handleConfirmPasswordChange} onChange={handleConfirmPasswordChange}
required required
/> />
<div className="flex items-center justify-between"> <div className="flex items-center justify-between pt-2">
<Button type="submit" primary text={'Mettre à jour'} /> <Button type="submit" primary text={'Mettre à jour'} />
</div> </div>
</form> </form>
</div> </div>
</div>
); );
} }

View File

@ -80,16 +80,20 @@ export default function Page() {
return <Loader />; // Affichez le composant Loader return <Loader />; // Affichez le composant Loader
} else { } else {
return ( return (
<> <div
<div className="container max mx-auto p-4"> className="min-h-screen flex items-center justify-center p-4"
<div className="flex justify-center mb-4"> style={{
background: 'linear-gradient(135deg, #80fdd6 100%, #2bb180 100%)',
}}
>
<div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
<div className="flex justify-center mb-6">
<Logo className="h-150 w-150" /> <Logo className="h-150 w-150" />
</div> </div>
<h1 className="text-2xl font-bold text-center mb-4"> <h1 className="font-headline text-2xl font-bold text-center text-gray-900 mb-6">
Authentification Authentification
</h1> </h1>
<form <form
className="max-w-md mx-auto"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
handleFormLogin(new FormData(e.target)); handleFormLogin(new FormData(e.target));
@ -112,16 +116,14 @@ export default function Page() {
placeholder="Mot de passe" placeholder="Mot de passe"
className="w-full mb-5" className="w-full mb-5"
/> />
<div className="input-group mb-4"></div> <div className="flex justify-end mb-4">
<label>
<a <a
className="float-right mb-4" className="text-sm text-primary hover:text-secondary font-label transition-colors"
href={`${FE_USERS_NEW_PASSWORD_URL}`} href={`${FE_USERS_NEW_PASSWORD_URL}`}
> >
Mot de passe oublié ? Mot de passe oublié ?
</a> </a>
</label> </div>
<div className="form-group-submit mt-4">
<Button <Button
text="Se Connecter" text="Se Connecter"
className="w-full" className="w-full"
@ -129,10 +131,9 @@ export default function Page() {
type="submit" type="submit"
name="connect" name="connect"
/> />
</div>
</form> </form>
</div> </div>
</> </div>
); );
} }
} }

View File

@ -48,16 +48,15 @@ export default function Page() {
return <Loader />; return <Loader />;
} else { } else {
return ( return (
<> <div className="min-h-screen bg-neutral flex items-center justify-center p-4">
<div className="container max mx-auto p-4"> <div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-6">
<Logo className="h-150 w-150" /> <Logo className="h-150 w-150" />
</div> </div>
<h1 className="text-2xl font-bold text-center mb-4"> <h1 className="font-headline text-2xl font-bold text-center text-gray-900 mb-6">
Nouveau Mot de passe Nouveau Mot de passe
</h1> </h1>
<form <form
className="max-w-md mx-auto"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
validate(new FormData(e.target)); validate(new FormData(e.target));
@ -70,28 +69,23 @@ export default function Page() {
IconItem={User} IconItem={User}
label="Identifiant" label="Identifiant"
placeholder="Identifiant" placeholder="Identifiant"
className="w-full" className="w-full mb-6"
/> />
<div className="form-group-submit mt-4">
<Button <Button
text="Réinitialiser" text="Réinitialiser"
className="w-full" className="w-full mb-3"
primary primary
type="submit" type="submit"
name="validate" name="validate"
/> />
</div>
</form>
<br />
<div className="flex justify-center mt-2 max-w-md mx-auto">
<Button <Button
text="Annuler" text="Annuler"
className="w-full" className="w-full"
href={`${FE_USERS_LOGIN_URL}`} href={`${FE_USERS_LOGIN_URL}`}
/> />
</form>
</div> </div>
</div> </div>
</>
); );
} }
} }

View File

@ -61,16 +61,15 @@ export default function Page() {
return <Loader />; return <Loader />;
} else { } else {
return ( return (
<> <div className="min-h-screen bg-neutral flex items-center justify-center p-4">
<div className="container max mx-auto p-4"> <div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-6">
<Logo className="h-150 w-150" /> <Logo className="h-150 w-150" />
</div> </div>
<h1 className="text-2xl font-bold text-center mb-4"> <h1 className="font-headline text-2xl font-bold text-center text-gray-900 mb-6">
Réinitialisation du mot de passe Réinitialisation du mot de passe
</h1> </h1>
<form <form
className="max-w-md mx-auto"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
validate(new FormData(e.target)); validate(new FormData(e.target));
@ -91,28 +90,23 @@ export default function Page() {
IconItem={KeySquare} IconItem={KeySquare}
label="Confirmation mot de passe" label="Confirmation mot de passe"
placeholder="Confirmation mot de passe" placeholder="Confirmation mot de passe"
className="w-full" className="w-full mb-6"
/> />
<div className="form-group-submit mt-4">
<Button <Button
text="Enregistrer" text="Enregistrer"
className="w-full" className="w-full mb-3"
primary primary
type="submit" type="submit"
name="validate" name="validate"
/> />
</div>
</form>
<br />
<div className="flex justify-center mt-2 max-w-md mx-auto">
<Button <Button
text="Annuler" text="Annuler"
className="w-full" className="w-full"
href={`${FE_USERS_LOGIN_URL}`} href={`${FE_USERS_LOGIN_URL}`}
/> />
</form>
</div> </div>
</div> </div>
</>
); );
} }
} }

View File

@ -65,16 +65,15 @@ export default function Page() {
return <Loader />; return <Loader />;
} else { } else {
return ( return (
<> <div className="min-h-screen bg-neutral flex items-center justify-center p-4">
<div className="container max mx-auto p-4"> <div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-6">
<Logo className="h-150 w-150" /> <Logo className="h-150 w-150" />
</div> </div>
<h1 className="text-2xl font-bold text-center mb-4"> <h1 className="font-headline text-2xl font-bold text-center text-gray-900 mb-6">
Nouveau profil Nouveau profil
</h1> </h1>
<form <form
className="max-w-md mx-auto"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
subscribeFormSubmit(new FormData(e.target)); subscribeFormSubmit(new FormData(e.target));
@ -103,30 +102,23 @@ export default function Page() {
IconItem={KeySquare} IconItem={KeySquare}
label="Confirmation mot de passe" label="Confirmation mot de passe"
placeholder="Confirmation mot de passe" placeholder="Confirmation mot de passe"
className="w-full" className="w-full mb-6"
/> />
<div className="form-group-submit mt-4">
<Button <Button
text="Enregistrer" text="Enregistrer"
className="w-full" className="w-full mb-3"
primary primary
type="submit" type="submit"
name="validate" name="validate"
/> />
</div>
</form>
<br />
<div className="flex justify-center mt-2 max-w-md mx-auto">
<Button <Button
text="Annuler" text="Annuler"
className="w-full" className="w-full"
onClick={() => { onClick={() => router.push(`${FE_USERS_LOGIN_URL}`)}
router.push(`${FE_USERS_LOGIN_URL}`);
}}
/> />
</form>
</div> </div>
</div> </div>
</>
); );
} }
} }

View File

@ -1,19 +1,19 @@
import React from 'react'; import React from 'react';
import { getMessages } from 'next-intl/server'; import { getMessages } from 'next-intl/server';
import { Inter, Manrope } from 'next/font/google'; import localFont from 'next/font/local';
import Providers from '@/components/Providers'; import Providers from '@/components/Providers';
import ServiceWorkerRegister from '@/components/ServiceWorkerRegister'; import ServiceWorkerRegister from '@/components/ServiceWorkerRegister';
import '@/css/tailwind.css'; import '@/css/tailwind.css';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
const inter = Inter({ const inter = localFont({
subsets: ['latin'], src: '../fonts/Inter-Variable.woff2',
variable: '--font-inter', variable: '--font-inter',
display: 'swap', display: 'swap',
}); });
const manrope = Manrope({ const manrope = localFont({
subsets: ['latin'], src: '../fonts/Manrope-Variable.woff2',
variable: '--font-manrope', variable: '--font-manrope',
display: 'swap', display: 'swap',
}); });

View File

@ -3,16 +3,19 @@ import Logo from '../components/Logo';
export default function NotFound() { export default function NotFound() {
return ( return (
<div className="flex items-center justify-center min-h-screen bg-emerald-500"> <div className="flex items-center justify-center min-h-screen bg-primary">
<div className="text-center p-6 "> <div className="text-center p-6 bg-white rounded-md shadow-sm border border-gray-200">
<Logo className="w-32 h-32 mx-auto mb-4" /> <Logo className="w-32 h-32 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-emerald-900 mb-4"> <h2 className="font-headline text-2xl font-bold text-secondary mb-4">
404 | Page non trouvée 404 | Page non trouvée
</h2> </h2>
<p className="text-emerald-900 mb-4"> <p className="font-body text-gray-600 mb-4">
La ressource que vous souhaitez consulter n&apos;existe pas ou plus. La ressource que vous souhaitez consulter n&apos;existe pas ou plus.
</p> </p>
<Link className="text-gray-900 hover:underline" href="/"> <Link
className="inline-flex items-center justify-center min-h-[44px] px-4 py-2 rounded font-label font-medium bg-primary hover:bg-secondary text-white transition-colors"
href="/"
>
Retour Accueil Retour Accueil
</Link> </Link>
</div> </div>

View File

@ -14,7 +14,7 @@ export default function AnnouncementScheduler({ csrfToken }) {
return ( return (
<div className="p-4 bg-white rounded shadow"> <div className="p-4 bg-white rounded shadow">
<h2 className="text-xl font-bold mb-4">Planifier une Annonce</h2> <h2 className="font-headline text-xl font-bold mb-4">Planifier une Annonce</h2>
<div className="mb-4"> <div className="mb-4">
<label className="block font-medium">Titre</label> <label className="block font-medium">Titre</label>
<input <input

View File

@ -39,7 +39,7 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
value={classe.id} value={classe.id}
checked={formData.classeAssocie_id === classe.id} checked={formData.classeAssocie_id === classe.id}
onChange={handleChange} onChange={handleChange}
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3" className="form-radio h-3 w-3 text-primary focus:ring-primary hover:ring-tertiary checked:bg-primary checked:h-3 checked:w-3"
/> />
<label <label
htmlFor={`classe-${classe.id}`} htmlFor={`classe-${classe.id}`}
@ -57,7 +57,7 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${ className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
!formData.classeAssocie_id !formData.classeAssocie_id
? 'bg-gray-300 text-gray-700 cursor-not-allowed' ? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-primary text-white hover:bg-primary'
}`} }`}
disabled={!formData.classeAssocie_id} disabled={!formData.classeAssocie_id}
> >

View File

@ -7,7 +7,6 @@ const AlertMessage = ({
actionLabel, actionLabel,
onAction, onAction,
}) => { }) => {
// Définir les styles en fonction du type d'alerte
const typeStyles = { const typeStyles = {
info: 'bg-blue-100 border-blue-500 text-blue-700', info: 'bg-blue-100 border-blue-500 text-blue-700',
warning: 'bg-yellow-100 border-yellow-500 text-yellow-700', warning: 'bg-yellow-100 border-yellow-500 text-yellow-700',
@ -18,13 +17,13 @@ const AlertMessage = ({
const alertStyle = typeStyles[type] || typeStyles.info; const alertStyle = typeStyles[type] || typeStyles.info;
return ( return (
<div className={`alert centered border-l-4 p-4 ${alertStyle}`} role="alert"> <div className={`alert centered border-l-4 p-4 rounded ${alertStyle}`} role="alert">
<h3 className="font-bold">{title}</h3> <h3 className="font-headline font-bold">{title}</h3>
<p className="mt-2">{message}</p> <p className="mt-2">{message}</p>
{actionLabel && onAction && ( {actionLabel && onAction && (
<div className="alert-actions mt-4"> <div className="alert-actions mt-4">
<button <button
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600" className="bg-primary text-white font-label font-medium rounded px-4 py-2 hover:bg-secondary transition-colors min-h-[44px]"
onClick={onAction} onClick={onAction}
> >
{actionLabel} {actionLabel}

View File

@ -14,11 +14,11 @@ const AlertWithModal = ({ title, message, buttonText }) => {
className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"
role="alert" role="alert"
> >
<h3 className="font-bold">{title}</h3> <h3 className="font-headline font-bold">{title}</h3>
<p className="mt-2">{message}</p> <p className="mt-2">{message}</p>
<div className="alert-actions mt-4"> <div className="alert-actions mt-4">
<button <button
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600 flex items-center" className="btn primary bg-primary text-white rounded-md px-4 py-2 hover:bg-primary flex items-center"
onClick={openModal} onClick={openModal}
> >
{buttonText} <UserPlus size={20} className="ml-2" /> {buttonText} <UserPlus size={20} className="ml-2" />

View File

@ -4,7 +4,7 @@ const AlphabetPaginationNumber = ({ letter, active, onClick }) => (
<button <button
className={`w-8 h-8 flex items-center justify-center rounded ${ className={`w-8 h-8 flex items-center justify-center rounded ${
active active
? 'bg-emerald-500 text-white' ? 'bg-primary text-white'
: 'text-gray-600 bg-gray-200 hover:bg-gray-50' : 'text-gray-600 bg-gray-200 hover:bg-gray-50'
}`} }`}
onClick={onClick} onClick={onClick}

View File

@ -126,7 +126,7 @@ const Calendar = ({ modeSet, onDateClick, onEventClick, planningClassName = '',
onClick={() => setShowDatePicker(!showDatePicker)} onClick={() => setShowDatePicker(!showDatePicker)}
className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md" className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md"
> >
<h2 className="text-xl font-semibold"> <h2 className="font-headline text-xl font-semibold">
{format(currentDate, viewType === 'year' ? 'yyyy' : 'MMMM yyyy', { locale: fr })} {format(currentDate, viewType === 'year' ? 'yyyy' : 'MMMM yyyy', { locale: fr })}
</h2> </h2>
<ChevronDown className="w-4 h-4" /> <ChevronDown className="w-4 h-4" />
@ -185,7 +185,7 @@ const Calendar = ({ modeSet, onDateClick, onEventClick, planningClassName = '',
{(planningMode === PlanningModes.PLANNING || (planningMode === PlanningModes.CLASS_SCHEDULE && !parentView)) && ( {(planningMode === PlanningModes.PLANNING || (planningMode === PlanningModes.CLASS_SCHEDULE && !parentView)) && (
<button <button
onClick={onDateClick} onClick={onDateClick}
className="w-10 h-10 flex items-center justify-center bg-emerald-600 text-white rounded-full hover:bg-emerald-700 shadow-md transition-colors" className="w-10 h-10 flex items-center justify-center bg-primary text-white rounded-full hover:bg-secondary shadow-md transition-colors"
> >
<Plus className="w-5 h-5" /> <Plus className="w-5 h-5" />
</button> </button>

View File

@ -131,7 +131,7 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
<button <button
onClick={() => onDateClick?.(currentDate)} onClick={() => onDateClick?.(currentDate)}
className="w-9 h-9 flex items-center justify-center bg-emerald-600 text-white rounded-full hover:bg-emerald-700 shadow-md transition-colors" className="w-9 h-9 flex items-center justify-center bg-primary text-white rounded-full hover:bg-secondary shadow-md transition-colors"
> >
<Plus className="w-4 h-4" /> <Plus className="w-4 h-4" />
</button> </button>
@ -145,9 +145,9 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
onClick={() => setCurrentDate(day)} onClick={() => setCurrentDate(day)}
className={`flex flex-col items-center min-w-[2.75rem] px-1 py-1.5 rounded-xl transition-colors ${ className={`flex flex-col items-center min-w-[2.75rem] px-1 py-1.5 rounded-xl transition-colors ${
isSameDay(day, currentDate) isSameDay(day, currentDate)
? 'bg-emerald-600 text-white' ? 'bg-primary text-white'
: isToday(day) : isToday(day)
? 'border border-emerald-400 text-emerald-600' ? 'border border-tertiary text-primary'
: 'text-gray-600 hover:bg-gray-100' : 'text-gray-600 hover:bg-gray-100'
}`} }`}
> >
@ -163,10 +163,10 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
<div ref={scrollRef} className="flex-1 overflow-y-auto relative"> <div ref={scrollRef} className="flex-1 overflow-y-auto relative">
{isCurrentDay && ( {isCurrentDay && (
<div <div
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none" className="absolute left-0 right-0 z-10 border-primary border pointer-events-none"
style={{ top: getCurrentTimePosition() }} style={{ top: getCurrentTimePosition() }}
> >
<div className="absolute -left-2 -top-2 w-2 h-2 rounded-full bg-emerald-500" /> <div className="absolute -left-2 -top-2 w-2 h-2 rounded-full bg-primary" />
</div> </div>
)} )}
@ -181,7 +181,7 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
</div> </div>
<div <div
className={`h-20 relative border-b border-gray-100 ${ className={`h-20 relative border-b border-gray-100 ${
isCurrentDay ? 'bg-emerald-50/30' : 'bg-white' isCurrentDay ? 'bg-primary/5/30' : 'bg-white'
}`} }`}
onClick={ onClick={
parentView parentView

View File

@ -116,7 +116,7 @@ export default function EventModal({
onChange={(e) => onChange={(e) =>
setEventData({ ...eventData, title: e.target.value }) setEventData({ ...eventData, title: e.target.value })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
required required
/> />
</div> </div>
@ -131,7 +131,7 @@ export default function EventModal({
onChange={(e) => onChange={(e) =>
setEventData({ ...eventData, description: e.target.value }) setEventData({ ...eventData, description: e.target.value })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
rows="3" rows="3"
/> />
</div> </div>
@ -153,7 +153,7 @@ export default function EventModal({
color: selectedSchedule?.color || '#10b981', color: selectedSchedule?.color || '#10b981',
}); });
}} }}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
required required
> >
{schedules.map((schedule) => ( {schedules.map((schedule) => (
@ -196,7 +196,7 @@ export default function EventModal({
recursionType: e.target.value, recursionType: e.target.value,
}); });
}} }}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
> >
{recurrenceOptions.map((option) => ( {recurrenceOptions.map((option) => (
<option key={option.value} value={option.value}> <option key={option.value} value={option.value}>
@ -226,7 +226,7 @@ export default function EventModal({
}} }}
className={`px-3 py-1 rounded-full text-sm ${ className={`px-3 py-1 rounded-full text-sm ${
(eventData.selectedDays || []).includes(day.value) (eventData.selectedDays || []).includes(day.value)
? 'bg-emerald-100 text-emerald-800' ? 'bg-primary/10 text-secondary'
: 'bg-gray-100 text-gray-600' : 'bg-gray-100 text-gray-600'
}`} }`}
> >
@ -258,7 +258,7 @@ export default function EventModal({
: null, : null,
}) })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
/> />
</div> </div>
)} )}
@ -278,7 +278,7 @@ export default function EventModal({
start: new Date(e.target.value).toISOString(), start: new Date(e.target.value).toISOString(),
}) })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
required required
/> />
</div> </div>
@ -295,7 +295,7 @@ export default function EventModal({
end: new Date(e.target.value).toISOString(), end: new Date(e.target.value).toISOString(),
}) })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
required required
/> />
</div> </div>
@ -312,7 +312,7 @@ export default function EventModal({
onChange={(e) => onChange={(e) =>
setEventData({ ...eventData, location: e.target.value }) setEventData({ ...eventData, location: e.target.value })
} }
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-primary"
/> />
</div> </div>
@ -339,7 +339,7 @@ export default function EventModal({
</button> </button>
<button <button
type="submit" type="submit"
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700" className="px-4 py-2 bg-primary text-white rounded hover:bg-secondary"
> >
{eventData.id ? 'Modifier' : 'Créer'} {eventData.id ? 'Modifier' : 'Créer'}
</button> </button>

View File

@ -56,14 +56,14 @@ const MonthView = ({ onDateClick, onEventClick }) => {
key={day.toString()} key={day.toString()}
className={`p-2 overflow-y-auto relative flex flex-col className={`p-2 overflow-y-auto relative flex flex-col
${!isCurrentMonth ? 'bg-gray-100 text-gray-400' : ''} ${!isCurrentMonth ? 'bg-gray-100 text-gray-400' : ''}
${isCurrentDay ? 'bg-emerald-50' : ''} ${isCurrentDay ? 'bg-primary/5' : ''}
hover:bg-gray-100 cursor-pointer border-b border-r`} hover:bg-gray-100 cursor-pointer border-b border-r`}
onClick={() => handleDayClick(day)} onClick={() => handleDayClick(day)}
> >
<div className="flex justify-between items-center mb-1"> <div className="flex justify-between items-center mb-1">
<span <span
className={`text-sm font-medium rounded-full w-7 h-7 flex items-center justify-center className={`text-sm font-medium rounded-full w-7 h-7 flex items-center justify-center
${isCurrentDay ? 'bg-emerald-500 text-white' : ''} ${isCurrentDay ? 'bg-primary text-white' : ''}
${!isCurrentMonth ? 'text-gray-400' : ''}`} ${!isCurrentMonth ? 'text-gray-400' : ''}`}
> >
{format(day, 'd')} {format(day, 'd')}

View File

@ -247,7 +247,7 @@ export default function ScheduleNavigation({ classes, modeSet = 'event', isOpen
{/* Desktop : sidebar fixe */} {/* Desktop : sidebar fixe */}
<nav className="hidden md:flex flex-col w-64 border-r p-4 h-full overflow-y-auto shrink-0"> <nav className="hidden md:flex flex-col w-64 border-r p-4 h-full overflow-y-auto shrink-0">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h2 className="font-semibold">{title}</h2> <h2 className="font-headline font-semibold">{title}</h2>
<button onClick={() => setIsAddingNew(true)} className="p-1 hover:bg-gray-100 rounded"> <button onClick={() => setIsAddingNew(true)} className="p-1 hover:bg-gray-100 rounded">
<Plus className="w-4 h-4" /> <Plus className="w-4 h-4" />
</button> </button>
@ -268,7 +268,7 @@ export default function ScheduleNavigation({ classes, modeSet = 'event', isOpen
}`} }`}
> >
<div className="flex items-center justify-between p-4 border-b shrink-0"> <div className="flex items-center justify-between p-4 border-b shrink-0">
<h2 className="font-semibold">{title}</h2> <h2 className="font-headline font-semibold">{title}</h2>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button onClick={() => setIsAddingNew(true)} className="p-1 hover:bg-gray-100 rounded"> <button onClick={() => setIsAddingNew(true)} className="p-1 hover:bg-gray-100 rounded">
<Plus className="w-4 h-4" /> <Plus className="w-4 h-4" />

View File

@ -195,14 +195,14 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
key={day} key={day}
className={`h-14 p-2 text-center border-b className={`h-14 p-2 text-center border-b
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'} ${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
${isToday(day) ? 'bg-emerald-100 border-x border-emerald-600' : ''}`} ${isToday(day) ? 'bg-primary/10 border-x border-primary' : ''}`}
> >
<div className="text-xs font-medium text-gray-500"> <div className="text-xs font-medium text-gray-500">
{format(day, 'EEEE', { locale: fr })} {format(day, 'EEEE', { locale: fr })}
</div> </div>
<div <div
className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7 className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7
${isToday(day) ? 'bg-emerald-500 text-white' : ''}`} ${isToday(day) ? 'bg-primary text-white' : ''}`}
> >
{format(day, 'd', { locale: fr })} {format(day, 'd', { locale: fr })}
</div> </div>
@ -215,12 +215,12 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
{/* Ligne de temps actuelle */} {/* Ligne de temps actuelle */}
{isCurrentWeek && ( {isCurrentWeek && (
<div <div
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none" className="absolute left-0 right-0 z-10 border-primary border pointer-events-none"
style={{ style={{
top: getCurrentTimePosition(), top: getCurrentTimePosition(),
}} }}
> >
<div className="absolute -left-2 -top-2 w-2 h-2 rounded-full bg-emerald-500" /> <div className="absolute -left-2 -top-2 w-2 h-2 rounded-full bg-primary" />
</div> </div>
)} )}
@ -241,7 +241,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
key={`${hour}-${day}`} key={`${hour}-${day}`}
className={`h-20 relative border-b border-gray-100 className={`h-20 relative border-b border-gray-100
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'} ${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
${isToday(day) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}`} ${isToday(day) ? 'bg-primary/10/50 border-x border-primary' : ''}`}
onClick={ onClick={
parentView parentView
? undefined ? undefined

View File

@ -8,14 +8,14 @@ import { isSameMonth } from 'date-fns';
const MonthCard = ({ month, eventCount, onClick }) => ( const MonthCard = ({ month, eventCount, onClick }) => (
<div <div
className={`bg-white p-4 rounded shadow hover:shadow-lg cursor-pointer className={`bg-white p-4 rounded shadow hover:shadow-lg cursor-pointer
${isSameMonth(month, new Date()) ? 'ring-2 ring-emerald-500' : ''}`} ${isSameMonth(month, new Date()) ? 'ring-2 ring-primary' : ''}`}
onClick={onClick} onClick={onClick}
> >
<h3 className="font-medium text-center mb-2"> <h3 className="font-headline font-medium text-center mb-2">
{format(month, 'MMMM', { locale: fr })} {format(month, 'MMMM', { locale: fr })}
</h3> </h3>
<div className="text-center text-sm"> <div className="text-center text-sm">
<span className="inline-flex items-center justify-center bg-emerald-100 text-emerald-800 px-2 py-1 rounded-full"> <span className="inline-flex items-center justify-center bg-primary/10 text-secondary px-2 py-1 rounded-full">
{eventCount} événements {eventCount} événements
</span> </span>
</div> </div>

View File

@ -31,7 +31,7 @@ export default function LineChart({ data }) {
style={{ height: chartHeight }} style={{ height: chartHeight }}
> >
<div <div
className={`${isMax ? 'bg-emerald-400' : 'bg-blue-400'} rounded-t w-4`} className={`${isMax ? 'bg-tertiary' : 'bg-blue-400'} rounded-t w-4`}
style={{ height: `${barHeight}px`, transition: 'height 0.3s' }} style={{ height: `${barHeight}px`, transition: 'height 0.3s' }}
title={`${point.month}: ${point.value}`} title={`${point.month}: ${point.value}`}
/> />

View File

@ -51,7 +51,7 @@ const ConversationItem = ({
const getLastMessageText = () => { const getLastMessageText = () => {
if (isTyping) { if (isTyping) {
return ( return (
<span className="text-emerald-500 italic">Tape un message...</span> <span className="text-primary italic">Tape un message...</span>
); );
} }
@ -96,7 +96,7 @@ const ConversationItem = ({
const getPresenceColor = (status) => { const getPresenceColor = (status) => {
switch (status) { switch (status) {
case 'online': case 'online':
return 'bg-emerald-400'; return 'bg-tertiary';
case 'away': case 'away':
return 'bg-yellow-400'; return 'bg-yellow-400';
case 'busy': case 'busy':
@ -127,7 +127,7 @@ const ConversationItem = ({
<div <div
className={`group flex items-center p-3 cursor-pointer rounded-lg transition-all duration-200 hover:bg-gray-50 ${ className={`group flex items-center p-3 cursor-pointer rounded-lg transition-all duration-200 hover:bg-gray-50 ${
isSelected isSelected
? 'bg-emerald-50 border-l-4 border-emerald-500' ? 'bg-primary/5 border-l-4 border-primary'
: 'hover:bg-gray-50' : 'hover:bg-gray-50'
}`} }`}
onClick={onClick} onClick={onClick}
@ -154,8 +154,8 @@ const ConversationItem = ({
<div className="flex-1 ml-3 overflow-hidden"> <div className="flex-1 ml-3 overflow-hidden">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 <h3
className={`font-semibold truncate ${ className={`font-headline font-semibold truncate ${
isSelected ? 'text-emerald-700' : 'text-gray-900' isSelected ? 'text-secondary' : 'text-gray-900'
}`} }`}
> >
{getInterlocutorName()} {getInterlocutorName()}

View File

@ -116,7 +116,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
const handleWebSocketMessage = useCallback( const handleWebSocketMessage = useCallback(
(data) => { (data) => {
// Debug : vérifier userProfileId à chaque message // Debug : vérifier userProfileId à chaque message
logger.debug('🔍 handleWebSocketMessage appelé:', { logger.debug(' handleWebSocketMessage appelé:', {
messageType: data.type, messageType: data.type,
currentUserProfileId: userProfileId, currentUserProfileId: userProfileId,
userProfileIdType: typeof userProfileId, userProfileIdType: typeof userProfileId,
@ -153,7 +153,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
case 'new_message': case 'new_message':
const newMessage = data.message; const newMessage = data.message;
logger.debug('🆕 NOUVEAU MESSAGE WebSocket reçu:', { logger.debug(' NOUVEAU MESSAGE WebSocket reçu:', {
senderId: newMessage.sender?.id, senderId: newMessage.sender?.id,
content: newMessage.content?.substring(0, 50), content: newMessage.content?.substring(0, 50),
conversationId: conversationId:
@ -171,14 +171,14 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
// Vérifier si ce message a déjà été traité // Vérifier si ce message a déjà été traité
if (processedMessages.has(messageId)) { if (processedMessages.has(messageId)) {
logger.debug('🔍 Message déjà traité, ignoré:', { logger.debug(' Message déjà traité, ignoré:', {
messageId, messageId,
processedCount: processedMessages.size, processedCount: processedMessages.size,
}); });
break; break;
} }
logger.debug('🆔 ID unique généré pour le message:', messageId); logger.debug(' ID unique généré pour le message:', messageId);
// Marquer le message comme traité // Marquer le message comme traité
setProcessedMessages((prev) => { setProcessedMessages((prev) => {
@ -193,7 +193,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
}); });
// Debug: vérifier le type de message reçu // Debug: vérifier le type de message reçu
logger.debug('🔍 Message reçu:', { logger.debug(' Message reçu:', {
content: newMessage.content?.substring(0, 50), content: newMessage.content?.substring(0, 50),
message_type: newMessage.message_type, message_type: newMessage.message_type,
sender_id: newMessage.sender_id, sender_id: newMessage.sender_id,
@ -235,7 +235,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
String(newMessage.sender.id) === String(userProfileId); String(newMessage.sender.id) === String(userProfileId);
// Debug détaillé pour comprendre le problème d'incrémentation // Debug détaillé pour comprendre le problème d'incrémentation
logger.debug('🔍 Analyse du message pour compteur:', { logger.debug(' Analyse du message pour compteur:', {
messageId: messageId, messageId: messageId,
senderId: newMessage.sender.id, senderId: newMessage.sender.id,
userProfileId: userProfileId, userProfileId: userProfileId,
@ -253,12 +253,12 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
if (shouldIncrementUnread) { if (shouldIncrementUnread) {
logger.debug( logger.debug(
'🔺 INCRÉMENTATION du compteur non lu pour conversation:', ' INCRÉMENTATION du compteur non lu pour conversation:',
convId convId
); );
} else { } else {
logger.debug( logger.debug(
"➡️ Pas d'incrémentation (message de l'utilisateur connecté)" " Pas d'incrémentation (message de l'utilisateur connecté)"
); );
} }
@ -278,7 +278,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
case 'typing_status': case 'typing_status':
const { user_id, is_typing, conversation_id, user_name } = data; const { user_id, is_typing, conversation_id, user_name } = data;
logger.debug('📝 Typing status reçu:', { logger.debug(' Typing status reçu:', {
user_id, user_id,
is_typing, is_typing,
conversation_id, conversation_id,
@ -294,13 +294,13 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
String(currentConversationId) === String(conversation_id) String(currentConversationId) === String(conversation_id)
) { ) {
logger.debug( logger.debug(
'📝 Mise à jour typing pour conversation sélectionnée' ' Mise à jour typing pour conversation sélectionnée'
); );
setTypingUsers((prev) => { setTypingUsers((prev) => {
// Utiliser le nom de l'utilisateur s'il est disponible, sinon l'ID // Utiliser le nom de l'utilisateur s'il est disponible, sinon l'ID
const displayName = user_name || `Utilisateur ${user_id}`; const displayName = user_name || `Utilisateur ${user_id}`;
logger.debug( logger.debug(
'📝 Display name:', ' Display name:',
displayName, displayName,
'is_typing:', 'is_typing:',
is_typing is_typing
@ -311,21 +311,21 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
? prev ? prev
: [...prev, displayName]; : [...prev, displayName];
logger.debug( logger.debug(
'📝 Nouveaux utilisateurs en train de taper:', ' Nouveaux utilisateurs en train de taper:',
newUsers newUsers
); );
return newUsers; return newUsers;
} else { } else {
const newUsers = prev.filter((name) => name !== displayName); const newUsers = prev.filter((name) => name !== displayName);
logger.debug( logger.debug(
'📝 Utilisateurs en train de taper après suppression:', ' Utilisateurs en train de taper après suppression:',
newUsers newUsers
); );
return newUsers; return newUsers;
} }
}); });
} else { } else {
logger.debug('📝 Typing status pour une autre conversation:', { logger.debug(' Typing status pour une autre conversation:', {
selectedConversationId: currentConversationId, selectedConversationId: currentConversationId,
messageConversationId: conversation_id, messageConversationId: conversation_id,
}); });
@ -378,7 +378,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
setConversations(data || []); setConversations(data || []);
} catch (error) { } catch (error) {
logger.error(' Erreur lors du chargement des conversations:', error); logger.error(' Erreur lors du chargement des conversations:', error);
setConversations([]); setConversations([]);
} finally { } finally {
setLoading(false); setLoading(false);
@ -539,30 +539,30 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
// Sélectionner une conversation // Sélectionner une conversation
const selectConversation = useCallback( const selectConversation = useCallback(
(conversation) => { (conversation) => {
logger.debug('🔄 Sélection de la conversation:', conversation); logger.debug(' Sélection de la conversation:', conversation);
setSelectedConversation(conversation); setSelectedConversation(conversation);
setTypingUsers([]); setTypingUsers([]);
setIsMobileSidebarOpen(false); setIsMobileSidebarOpen(false);
// Utiliser id ou conversation_id selon ce qui est disponible // Utiliser id ou conversation_id selon ce qui est disponible
const conversationId = conversation.id || conversation.conversation_id; const conversationId = conversation.id || conversation.conversation_id;
logger.debug('🔄 ID de conversation extrait:', conversationId); logger.debug(' ID de conversation extrait:', conversationId);
if (conversationId) { if (conversationId) {
logger.debug( logger.debug(
'🔄 Chargement des messages pour conversation:', ' Chargement des messages pour conversation:',
conversationId conversationId
); );
loadMessages(conversationId); loadMessages(conversationId);
logger.debug( logger.debug(
'🔄 Tentative de rejoindre la conversation:', ' Tentative de rejoindre la conversation:',
conversationId conversationId
); );
const joinResult = joinConversation(conversationId); const joinResult = joinConversation(conversationId);
logger.debug('🔄 Résultat joinConversation:', joinResult); logger.debug(' Résultat joinConversation:', joinResult);
} else { } else {
logger.error(" Impossible de trouver l'ID de conversation"); logger.error(" Impossible de trouver l'ID de conversation");
} }
}, },
[loadMessages, joinConversation] [loadMessages, joinConversation]
@ -571,20 +571,20 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
// Envoyer un message // Envoyer un message
const handleSendMessage = useCallback( const handleSendMessage = useCallback(
(content, attachment = null) => { (content, attachment = null) => {
logger.debug('📤 handleSendMessage appelé:', { logger.debug(' handleSendMessage appelé:', {
content, content,
attachment, attachment,
selectedConversation, selectedConversation,
}); });
if (!selectedConversation) { if (!selectedConversation) {
logger.warn(' Aucune conversation sélectionnée'); logger.warn(' Aucune conversation sélectionnée');
return; return;
} }
// Vérifier qu'on a soit du contenu, soit un fichier // Vérifier qu'on a soit du contenu, soit un fichier
if (!content.trim() && !attachment) { if (!content.trim() && !attachment) {
logger.warn(' Aucun contenu ni fichier à envoyer'); logger.warn(' Aucun contenu ni fichier à envoyer');
return; return;
} }
@ -592,7 +592,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
const conversationId = const conversationId =
selectedConversation.id || selectedConversation.conversation_id; selectedConversation.id || selectedConversation.conversation_id;
if (!conversationId) { if (!conversationId) {
logger.error(" Impossible de trouver l'ID de la conversation"); logger.error(" Impossible de trouver l'ID de la conversation");
return; return;
} }
@ -608,23 +608,23 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
messageData.attachment = attachment; messageData.attachment = attachment;
} }
logger.debug('📤 Envoi du message via WebSocket:', messageData); logger.debug(' Envoi du message via WebSocket:', messageData);
logger.debug('📤 Type de message:', messageData.type); logger.debug(' Type de message:', messageData.type);
logger.debug('📤 État de la connexion:', { logger.debug(' État de la connexion:', {
isConnected, isConnected,
connectionStatus, connectionStatus,
}); });
const success = sendChatMessage(messageData); const success = sendChatMessage(messageData);
logger.debug('📤 Résultat envoi message:', success); logger.debug(' Résultat envoi message:', success);
if (!success) { if (!success) {
logger.error( logger.error(
" Impossible d'envoyer le message - WebSocket non connecté" " Impossible d'envoyer le message - WebSocket non connecté"
); );
} else { } else {
logger.debug(' Message envoyé avec succès'); logger.debug(' Message envoyé avec succès');
} }
}, },
[ [
@ -834,7 +834,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
{/* En-tête */} {/* En-tête */}
<div className="p-4 border-b border-gray-200"> <div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold text-gray-900">Messages</h2> <h2 className="font-headline text-lg font-semibold text-gray-900">Messages</h2>
<button <button
onClick={handleStartNewConversation} onClick={handleStartNewConversation}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors" className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
@ -857,7 +857,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
value={searchQuery} value={searchQuery}
onChange={(e) => handleSearch(e.target.value)} onChange={(e) => handleSearch(e.target.value)}
className={`w-full pl-10 pr-4 py-2 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 ${ className={`w-full pl-10 pr-4 py-2 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 ${
showSearch ? 'focus:ring-emerald-500' : 'focus:ring-emerald-500' showSearch ? 'focus:ring-primary' : 'focus:ring-primary'
}`} }`}
/> />
{showSearch && searchQuery && ( {showSearch && searchQuery && (
@ -896,7 +896,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
</div> </div>
) : showSearch && searchResults.length > 0 ? ( ) : showSearch && searchResults.length > 0 ? (
<div className="p-2"> <div className="p-2">
<h3 className="text-sm font-medium text-gray-700 mb-2 px-2"> <h3 className="font-headline text-sm font-medium text-gray-700 mb-2 px-2">
Résultats de recherche Résultats de recherche
</h3> </h3>
{searchResults.map((user) => ( {searchResults.map((user) => (
@ -915,7 +915,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
<div <div
className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 ${ className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 ${
userPresences[user.id]?.status === 'online' userPresences[user.id]?.status === 'online'
? 'bg-emerald-400' ? 'bg-tertiary'
: 'bg-gray-400' : 'bg-gray-400'
} border-2 border-white rounded-full`} } border-2 border-white rounded-full`}
title={ title={
@ -937,7 +937,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
<div <div
className={`text-xs ${ className={`text-xs ${
userPresences[user.id]?.status === 'online' userPresences[user.id]?.status === 'online'
? 'text-emerald-500' ? 'text-primary'
: 'text-gray-500' : 'text-gray-500'
}`} }`}
> >
@ -1019,7 +1019,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
selectedConversation.interlocuteur?.id && selectedConversation.interlocuteur?.id &&
userPresences[selectedConversation.interlocuteur.id] userPresences[selectedConversation.interlocuteur.id]
?.status === 'online' ?.status === 'online'
? 'bg-emerald-400' ? 'bg-tertiary'
: 'bg-gray-400' : 'bg-gray-400'
} border-2 border-white rounded-full`} } border-2 border-white rounded-full`}
title={ title={
@ -1032,7 +1032,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
></div> ></div>
</div> </div>
<div className="flex-1 ml-3 overflow-hidden"> <div className="flex-1 ml-3 overflow-hidden">
<h3 className="font-semibold text-gray-900"> <h3 className="font-headline font-semibold text-gray-900">
{selectedConversation.interlocuteur {selectedConversation.interlocuteur
? selectedConversation.interlocuteur.first_name && ? selectedConversation.interlocuteur.first_name &&
selectedConversation.interlocuteur.last_name selectedConversation.interlocuteur.last_name
@ -1046,7 +1046,7 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
selectedConversation.interlocuteur?.id && selectedConversation.interlocuteur?.id &&
userPresences[selectedConversation.interlocuteur.id] userPresences[selectedConversation.interlocuteur.id]
?.status === 'online' ?.status === 'online'
? 'text-emerald-500' ? 'text-primary'
: 'text-gray-500' : 'text-gray-500'
}`} }`}
> >
@ -1140,10 +1140,10 @@ const InstantChat = ({ userProfileId, establishmentId }) => {
) : ( ) : (
<div className="flex-1 flex items-center justify-center bg-gray-50"> <div className="flex-1 flex items-center justify-center bg-gray-50">
<div className="text-center"> <div className="text-center">
<div className="w-16 h-16 bg-emerald-200 rounded-full flex items-center justify-center mx-auto mb-4"> <div className="w-16 h-16 bg-primary/20 rounded-full flex items-center justify-center mx-auto mb-4">
<MessageSquare className="w-8 h-8 text-emerald-600" /> <MessageSquare className="w-8 h-8 text-primary" />
</div> </div>
<h3 className="text-lg font-medium text-gray-900 mb-2"> <h3 className="font-headline text-lg font-medium text-gray-900 mb-2">
Sélectionnez une conversation Sélectionnez une conversation
</h3> </h3>
<p className="text-gray-500"> <p className="text-gray-500">

View File

@ -60,19 +60,19 @@ const MessageInput = ({
const handleSend = () => { const handleSend = () => {
const trimmedMessage = message.trim(); const trimmedMessage = message.trim();
logger.debug('📝 MessageInput: handleSend appelé:', { logger.debug(' MessageInput: handleSend appelé:', {
message, message,
trimmedMessage, trimmedMessage,
disabled, disabled,
}); });
if (!trimmedMessage || disabled) { if (!trimmedMessage || disabled) {
logger.debug(' MessageInput: Message vide ou désactivé'); logger.debug(' MessageInput: Message vide ou désactivé');
return; return;
} }
logger.debug( logger.debug(
'📤 MessageInput: Appel de onSendMessage avec:', ' MessageInput: Appel de onSendMessage avec:',
trimmedMessage trimmedMessage
); );
onSendMessage(trimmedMessage); onSendMessage(trimmedMessage);

View File

@ -10,7 +10,7 @@ const ClasseDetails = ({ classe }) => {
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100); const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
const getColor = (pourcentage) => { const getColor = (pourcentage) => {
if (pourcentage < 50) return 'bg-emerald-500'; if (pourcentage < 50) return 'bg-primary';
if (pourcentage < 75) return 'bg-orange-500'; if (pourcentage < 75) return 'bg-orange-500';
return 'bg-red-500'; return 'bg-red-500';
}; };
@ -52,7 +52,7 @@ const ClasseDetails = ({ classe }) => {
</div> </div>
</div> </div>
<h3 className="text-xl font-semibold mb-4">Liste des élèves</h3> <h3 className="font-headline text-xl font-semibold mb-4">Liste des élèves</h3>
<div className="bg-white rounded-lg border border-gray-200 shadow-md"> <div className="bg-white rounded-lg border border-gray-200 shadow-md">
<Table <Table
columns={[ columns={[

View File

@ -50,7 +50,7 @@ const DateTab = ({
<div className="flex flex-col space-y-3"> <div className="flex flex-col space-y-3">
{dates[activeTab]?.map((date, index) => ( {dates[activeTab]?.map((date, index) => (
<div key={index} className="flex items-center space-x-3"> <div key={index} className="flex items-center space-x-3">
<span className="text-emerald-700 font-semibold"> <span className="text-secondary font-semibold">
Échéance {index + 1} Échéance {index + 1}
</span> </span>
<input <input
@ -63,7 +63,7 @@ const DateTab = ({
e.target.value e.target.value
) )
} }
className="p-2 border border-emerald-300 rounded focus:outline-none focus:ring-2 focus:ring-emerald-500 cursor-pointer" className="p-2 border border-primary/30 rounded focus:outline-none focus:ring-2 focus:ring-primary cursor-pointer"
/> />
{modifiedDates[`${activeTab}-${index}`] && ( {modifiedDates[`${activeTab}-${index}`] && (
<button <button
@ -74,7 +74,7 @@ const DateTab = ({
due_dates: dates[activeTab], due_dates: dates[activeTab],
}) })
} }
className="text-emerald-500 hover:text-emerald-800" className="text-primary hover:text-secondary"
> >
<Check className="w-5 h-5" /> <Check className="w-5 h-5" />
</button> </button>

View File

@ -0,0 +1,36 @@
import React from 'react';
import { Inbox } from 'lucide-react';
const EmptyState = ({
icon: Icon = Inbox,
title,
description,
actionLabel,
onAction,
actionIcon: ActionIcon,
}) => {
return (
<div className="flex flex-col items-center justify-center py-12 px-4 text-center">
<div className="bg-neutral p-4 rounded-full mb-4">
<Icon size={40} className="text-gray-400" />
</div>
<h3 className="font-headline text-lg font-semibold text-gray-700 mb-2">
{title}
</h3>
{description && (
<p className="text-sm text-gray-500 max-w-md mb-6">{description}</p>
)}
{actionLabel && onAction && (
<button
onClick={onAction}
className="flex items-center gap-2 bg-primary hover:bg-secondary text-white font-label font-medium px-6 py-2.5 rounded transition-colors min-h-[44px]"
>
{ActionIcon && <ActionIcon size={18} />}
{actionLabel}
</button>
)}
</div>
);
};
export default EmptyState;

View File

@ -74,7 +74,7 @@ export default function EvaluationForm({
className="space-y-4 p-4 bg-white rounded-lg border border-gray-200 shadow-sm" className="space-y-4 p-4 bg-white rounded-lg border border-gray-200 shadow-sm"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-semibold text-lg text-gray-800"> <h3 className="font-headline font-semibold text-lg text-gray-800">
{isEditing ? 'Modifier l\'évaluation' : 'Nouvelle évaluation'} {isEditing ? 'Modifier l\'évaluation' : 'Nouvelle évaluation'}
</h3> </h3>
<button <button

View File

@ -112,7 +112,7 @@ export default function EvaluationGradeTable({
<div className="p-4 border-b border-gray-200 bg-gray-50 rounded-t-lg"> <div className="p-4 border-b border-gray-200 bg-gray-50 rounded-t-lg">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h3 className="font-semibold text-lg text-gray-800"> <h3 className="font-headline font-semibold text-lg text-gray-800">
{evaluation.name} {evaluation.name}
</h3> </h3>
<div className="text-sm text-gray-500 flex gap-3"> <div className="text-sm text-gray-500 flex gap-3">
@ -191,7 +191,7 @@ export default function EvaluationGradeTable({
handleScoreChange(student.id, e.target.value) handleScoreChange(student.id, e.target.value)
} }
disabled={isAbsent} disabled={isAbsent}
className={`w-20 px-2 py-1 text-center border rounded focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 ${ className={`w-20 px-2 py-1 text-center border rounded focus:ring-2 focus:ring-primary focus:border-primary ${
isAbsent isAbsent
? 'bg-gray-100 text-gray-400 cursor-not-allowed' ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'border-gray-300' : 'border-gray-300'
@ -219,7 +219,7 @@ export default function EvaluationGradeTable({
handleCommentChange(student.id, e.target.value) handleCommentChange(student.id, e.target.value)
} }
placeholder="Commentaire..." placeholder="Commentaire..."
className="w-full px-2 py-1 border border-gray-300 rounded focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" className="w-full px-2 py-1 border border-gray-300 rounded focus:ring-2 focus:ring-primary focus:border-primary"
/> />
</td> </td>
{onDeleteGrade && ( {onDeleteGrade && (
@ -258,7 +258,7 @@ export default function EvaluationGradeTable({
<div className="flex gap-4 text-sm text-gray-600"> <div className="flex gap-4 text-sm text-gray-600">
<span> <span>
Moyenne:{' '} Moyenne:{' '}
<span className="font-semibold text-emerald-600"> <span className="font-semibold text-primary">
{stats.avg.toFixed(2)} {stats.avg.toFixed(2)}
</span> </span>
</span> </span>

View File

@ -123,14 +123,14 @@ export default function EvaluationStudentView({
<div className="space-y-4"> <div className="space-y-4">
{/* Moyenne générale */} {/* Moyenne générale */}
{generalAverage !== null && ( {generalAverage !== null && (
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4 flex items-center justify-between"> <div className="bg-primary/5 border border-primary/20 rounded-lg p-4 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<BookOpen className="text-emerald-600" size={24} /> <BookOpen className="text-primary" size={24} />
<span className="font-medium text-emerald-800">Moyenne générale</span> <span className="font-medium text-secondary">Moyenne générale</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{getAverageIcon(generalAverage)} {getAverageIcon(generalAverage)}
<span className="text-2xl font-bold text-emerald-700"> <span className="text-2xl font-bold text-secondary">
{generalAverage.toFixed(2)}/20 {generalAverage.toFixed(2)}/20
</span> </span>
</div> </div>
@ -233,7 +233,7 @@ export default function EvaluationStudentView({
<span className="text-gray-500">/{ev.max_score}</span> <span className="text-gray-500">/{ev.max_score}</span>
<button <button
onClick={() => handleSaveEdit(ev, studentEval)} onClick={() => handleSaveEdit(ev, studentEval)}
className="p-1 text-emerald-600 hover:bg-emerald-50 rounded" className="p-1 text-primary hover:bg-primary/5 rounded"
title="Enregistrer" title="Enregistrer"
> >
<Save size={16} /> <Save size={16} />

View File

@ -103,12 +103,12 @@ const EventCard = ({ title, start, end, date, description, type }) => {
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 mb-3 hover:shadow-md transition-shadow"> <div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 mb-3 hover:shadow-md transition-shadow">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-1"> <div className="flex-shrink-0 mt-1">
<CalendarCheck className="text-emerald-500" size={20} /> <CalendarCheck className="text-primary" size={20} />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900 mb-1">{title}</h4> <h4 className="font-medium text-gray-900 mb-1">{title}</h4>
<div className="flex flex-col text-sm text-gray-500"> <div className="flex flex-col text-sm text-gray-500">
<span className="font-medium text-emerald-600">{dayName}</span> <span className="font-medium text-primary">{dayName}</span>
<span>{formattedDate}</span> <span>{formattedDate}</span>
{timeRange && ( {timeRange && (
<span className="text-xs text-gray-600 mt-1">{timeRange}</span> <span className="text-xs text-gray-600 mt-1">{timeRange}</span>

View File

@ -159,7 +159,7 @@ export default function AddFieldModal({
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-xl w-full mx-4 max-h-[90vh] overflow-y-auto"> <div className="bg-white rounded-lg p-6 max-w-xl w-full mx-4 max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-semibold"> <h3 className="font-headline text-xl font-semibold">
{isEditing ? 'Modifier le champ' : 'Ajouter un champ'} {isEditing ? 'Modifier le champ' : 'Ajouter un champ'}
</h3> </h3>
<button <button
@ -720,7 +720,7 @@ export default function AddFieldModal({
<Button <Button
type="submit" type="submit"
text={isEditing ? 'Modifier' : 'Ajouter'} text={isEditing ? 'Modifier' : 'Ajouter'}
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600" className="px-4 py-2 bg-primary text-white rounded hover:bg-secondary"
/> />
<Button <Button
type="button" type="button"

View File

@ -12,8 +12,8 @@ const Button = ({
}) => { }) => {
const router = useRouter(); const router = useRouter();
const baseClass = const baseClass =
'px-4 py-2 rounded-md text-white h-8 flex items-center justify-center'; 'px-4 py-2 rounded font-label font-medium min-h-[44px] flex items-center justify-center transition-colors';
const primaryClass = 'bg-emerald-500 hover:bg-emerald-600'; const primaryClass = 'bg-primary hover:bg-secondary text-white';
const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black'; const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black';
const buttonClass = `${baseClass} ${primary && !disabled ? primaryClass : secondaryClass} ${className}`; const buttonClass = `${baseClass} ${primary && !disabled ? primaryClass : secondaryClass} ${className}`;

View File

@ -34,7 +34,7 @@ const CheckBox = ({
value={item.id} value={item.id}
checked={isChecked} checked={isChecked}
onChange={handleChange} onChange={handleChange}
className={`form-checkbox h-4 w-4 rounded-mg text-emerald-600 hover:ring-emerald-400 checked:bg-emerald-600 hover:border-emerald-500 hover:bg-emerald-500 cursor-pointer ${horizontal ? 'mt-1' : 'mr-2'}`} className={`form-checkbox h-4 w-4 rounded-mg text-primary hover:ring-tertiary checked:bg-primary hover:border-primary hover:bg-primary cursor-pointer ${horizontal ? 'mt-1' : 'mr-2'}`}
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }} style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
/> />
{!horizontal && ( {!horizontal && (

View File

@ -44,7 +44,7 @@ export default function FieldTypeSelector({ isOpen, onClose, onSelect }) {
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[85vh] overflow-y-auto"> <div className="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[85vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"> <h3 className="font-headline text-lg font-semibold">
Choisir un type de champ ({filteredFieldTypes.length} /{' '} Choisir un type de champ ({filteredFieldTypes.length} /{' '}
{FIELD_TYPES.length} types) {FIELD_TYPES.length} types)
</h3> </h3>

View File

@ -46,7 +46,7 @@ export default function FileUpload({
!enable ? 'bg-gray-100 cursor-not-allowed' : '' !enable ? 'bg-gray-100 cursor-not-allowed' : ''
}`} }`}
> >
<h3 className="text-lg font-semibold mb-4"> <h3 className="font-headline text-lg font-semibold mb-4">
{`${selectionMessage}`} {`${selectionMessage}`}
{required && <span className="text-red-500 ml-1">*</span>} {required && <span className="text-red-500 ml-1">*</span>}
</h3> </h3>
@ -54,7 +54,7 @@ export default function FileUpload({
className={`border-2 border-dashed p-6 rounded-lg flex flex-col items-center justify-center ${ className={`border-2 border-dashed p-6 rounded-lg flex flex-col items-center justify-center ${
!enable !enable
? 'border-gray-300 bg-gray-100 cursor-not-allowed' ? 'border-gray-300 bg-gray-100 cursor-not-allowed'
: 'border-gray-500 hover:border-emerald-500' : 'border-gray-500 hover:border-primary'
}`} }`}
onClick={() => enable && fileInputRef.current.click()} // Désactiver le clic si `enable` est false onClick={() => enable && fileInputRef.current.click()} // Désactiver le clic si `enable` est false
onDragOver={(e) => enable && e.preventDefault()} onDragOver={(e) => enable && e.preventDefault()}
@ -62,7 +62,7 @@ export default function FileUpload({
> >
<CloudUpload <CloudUpload
className={`w-12 h-12 mb-4 ${ className={`w-12 h-12 mb-4 ${
!enable ? 'text-gray-400' : 'text-emerald-500' !enable ? 'text-gray-400' : 'text-primary'
}`} }`}
/> />
<input <input
@ -93,7 +93,7 @@ export default function FileUpload({
{/* Affichage du fichier existant */} {/* Affichage du fichier existant */}
{existingFile && !localFileName && ( {existingFile && !localFileName && (
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm"> <div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
<CloudUpload className="w-6 h-6 text-emerald-500" /> <CloudUpload className="w-6 h-6 text-primary" />
<p className="text-sm font-medium text-gray-800"> <p className="text-sm font-medium text-gray-800">
<span className="font-semibold"> <span className="font-semibold">
{typeof existingFile === 'string' {typeof existingFile === 'string'
@ -107,7 +107,7 @@ export default function FileUpload({
{/* Affichage du fichier sélectionné */} {/* Affichage du fichier sélectionné */}
{localFileName && ( {localFileName && (
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm"> <div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
<CloudUpload className="w-6 h-6 text-emerald-500" /> <CloudUpload className="w-6 h-6 text-primary" />
<p className="text-sm font-medium text-gray-800"> <p className="text-sm font-medium text-gray-800">
<span className="font-semibold">{localFileName}</span> <span className="font-semibold">{localFileName}</span>
</p> </p>

View File

@ -200,7 +200,7 @@ export default function FormRenderer({
className={formContainerClass} className={formContainerClass}
> >
{csrfToken ? <DjangoCSRFToken csrfToken={csrfToken} /> : null} {csrfToken ? <DjangoCSRFToken csrfToken={csrfToken} /> : null}
<h2 className="text-2xl font-bold text-center mb-4"> <h2 className="font-headline text-2xl font-bold text-center mb-4">
{formConfig?.title || 'Formulaire'} {formConfig?.title || 'Formulaire'}
</h2> </h2>
@ -210,13 +210,13 @@ export default function FormRenderer({
className="flex flex-col mt-4" className="flex flex-col mt-4"
> >
{field.type === 'heading1' && ( {field.type === 'heading1' && (
<h1 className="text-3xl font-bold mb-3">{field.text}</h1> <h1 className="font-headline text-3xl font-bold mb-3">{field.text}</h1>
)} )}
{field.type === 'heading2' && ( {field.type === 'heading2' && (
<h2 className="text-2xl font-bold mb-3">{field.text}</h2> <h2 className="font-headline text-2xl font-bold mb-3">{field.text}</h2>
)} )}
{field.type === 'heading3' && ( {field.type === 'heading3' && (
<h3 className="text-xl font-bold mb-2">{field.text}</h3> <h3 className="font-headline text-xl font-bold mb-2">{field.text}</h3>
)} )}
{field.type === 'heading4' && ( {field.type === 'heading4' && (
<h4 className="text-lg font-bold mb-2">{field.text}</h4> <h4 className="text-lg font-bold mb-2">{field.text}</h4>
@ -516,7 +516,7 @@ export default function FormRenderer({
type="submit" type="submit"
primary primary
text={formConfig?.submitLabel || 'Envoyer'} text={formConfig?.submitLabel || 'Envoyer'}
className="mb-1 px-4 py-2 rounded-md shadow bg-emerald-500 text-white hover:bg-emerald-600 w-full" className="mb-1 px-4 py-2 rounded-md shadow bg-primary text-white hover:bg-primary w-full"
/> />
</div> </div>
</form> </form>

View File

@ -459,14 +459,14 @@ export default function FormTemplateBuilder({
} }
> >
<div className="bg-white p-6 rounded-lg shadow"> <div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-xl font-bold mb-6"> <h2 className="font-headline text-xl font-bold mb-6">
Configuration du formulaire Configuration du formulaire
</h2> </h2>
{/* Configuration générale */} {/* Configuration générale */}
<div className="space-y-4 mb-6"> <div className="space-y-4 mb-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h3 className="text-lg font-semibold mr-4"> <h3 className="font-headline text-lg font-semibold mr-4">
Paramètres généraux Paramètres généraux
</h3> </h3>
<div className="flex gap-2"> <div className="flex gap-2">
@ -509,7 +509,7 @@ export default function FormTemplateBuilder({
className={`p-3 rounded ${ className={`p-3 rounded ${
saveMessage.type === 'error' saveMessage.type === 'error'
? 'bg-red-100 text-red-700' ? 'bg-red-100 text-red-700'
: 'bg-green-100 text-green-700' : 'bg-primary/10 text-secondary'
}`} }`}
> >
{saveMessage.text} {saveMessage.text}
@ -578,7 +578,7 @@ export default function FormTemplateBuilder({
{/* Liste des champs */} {/* Liste des champs */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h3 className="text-lg font-semibold mr-4"> <h3 className="font-headline text-lg font-semibold mr-4">
Champs du formulaire ({formConfig.fields.length}) Champs du formulaire ({formConfig.fields.length})
</h3> </h3>
<button <button
@ -587,7 +587,7 @@ export default function FormTemplateBuilder({
setSelectedFieldType(null); setSelectedFieldType(null);
setShowFieldTypeSelector(true); setShowFieldTypeSelector(true);
}} }}
className="p-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors" className="p-2 bg-primary text-white rounded hover:bg-secondary transition-colors"
title="Ajouter un champ" title="Ajouter un champ"
> >
<PlusCircle size={18} /> <PlusCircle size={18} />
@ -605,7 +605,7 @@ export default function FormTemplateBuilder({
setSelectedFieldType(null); setSelectedFieldType(null);
setShowFieldTypeSelector(true); setShowFieldTypeSelector(true);
}} }}
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors inline-flex items-center gap-2" className="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors inline-flex items-center gap-2"
> >
<PlusCircle size={18} /> <PlusCircle size={18} />
<span>Ajouter mon premier champ</span> <span>Ajouter mon premier champ</span>
@ -639,7 +639,7 @@ export default function FormTemplateBuilder({
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="bg-white p-6 rounded-lg shadow h-full"> <div className="bg-white p-6 rounded-lg shadow h-full">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold mr-4">JSON généré</h3> <h3 className="font-headline text-lg font-semibold mr-4">JSON généré</h3>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={exportJson} onClick={exportJson}
@ -673,7 +673,7 @@ export default function FormTemplateBuilder({
{/* Aperçu */} {/* Aperçu */}
<div className="mt-6"> <div className="mt-6">
<div className="bg-white p-6 rounded-lg shadow"> <div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-semibold mb-4">Aperçu du formulaire</h3> <h3 className="font-headline text-lg font-semibold mb-4">Aperçu du formulaire</h3>
<div className="border-2 border-dashed border-gray-300 p-6 rounded"> <div className="border-2 border-dashed border-gray-300 p-6 rounded">
{formConfig.fields.length > 0 ? ( {formConfig.fields.length > 0 ? (
<FormRenderer formConfig={formConfig} masterFile={masterFile} /> <FormRenderer formConfig={formConfig} masterFile={masterFile} />

View File

@ -42,7 +42,7 @@ export default function IconSelector({
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[85vh] overflow-y-auto"> <div className="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[85vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"> <h3 className="font-headline text-lg font-semibold">
Choisir une icône ({filteredIcons.length} / {allIcons.length}{' '} Choisir une icône ({filteredIcons.length} / {allIcons.length}{' '}
icônes) icônes)
</h3> </h3>

View File

@ -45,7 +45,7 @@ const MultiSelect = ({
<div className="relative mt-1"> <div className="relative mt-1">
<button <button
type="button" type="button"
className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm hover:border-emerald-500 focus:border-emerald-500" className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm hover:border-primary focus:border-primary"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
> >
{selectedOptions.length > 0 ? ( {selectedOptions.length > 0 ? (
@ -53,7 +53,7 @@ const MultiSelect = ({
{selectedOptions.map((option) => ( {selectedOptions.map((option) => (
<span <span
key={option.id} key={option.id}
className="bg-emerald-100 text-emerald-700 px-2 py-1 rounded-md text-sm" className="bg-primary/10 text-secondary px-2 py-1 rounded-md text-sm"
> >
{option.name} {option.name}
</span> </span>
@ -71,8 +71,8 @@ const MultiSelect = ({
key={option.id} key={option.id}
className={`cursor-pointer select-none relative py-2 pl-3 pr-9 ${ className={`cursor-pointer select-none relative py-2 pl-3 pr-9 ${
selectedOptions.some((selected) => selected.id === option.id) selectedOptions.some((selected) => selected.id === option.id)
? 'text-white bg-emerald-600' ? 'text-white bg-primary'
: 'text-gray-900 hover:bg-emerald-100 hover:text-emerald-900' : 'text-gray-900 hover:bg-primary/10 hover:text-secondary'
}`} }`}
onClick={() => handleSelect(option)} onClick={() => handleSelect(option)}
> >

View File

@ -14,7 +14,7 @@ const RadioList = ({
return ( return (
<div className={`mb-4 ${className}`}> <div className={`mb-4 ${className}`}>
{sectionLabel && ( {sectionLabel && (
<h3 className="text-lg font-semibold text-gray-800 mb-2"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-2">
{sectionLabel} {sectionLabel}
{required && <span className="text-red-500 ml-1">*</span>} {required && <span className="text-red-500 ml-1">*</span>}
</h3> </h3>
@ -35,7 +35,7 @@ const RadioList = ({
value={item.id} value={item.id}
checked={parseInt(formData[fieldName], 10) === item.id} checked={parseInt(formData[fieldName], 10) === item.id}
onChange={handleChange} onChange={handleChange}
className="form-radio h-4 w-4 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 cursor-pointer" className="form-radio h-4 w-4 text-primary focus:ring-primary hover:ring-tertiary checked:bg-primary cursor-pointer"
style={{ outline: 'none', boxShadow: 'none' }} style={{ outline: 'none', boxShadow: 'none' }}
disabled={disabled} disabled={disabled}
/> />

View File

@ -20,12 +20,12 @@ const ToggleSwitch = ({ name, label, checked, onChange }) => {
id={name} id={name}
checked={checked} checked={checked}
onChange={handleChange} onChange={handleChange}
className="hover:text-emerald-500 absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer border-emerald-500 checked:right-0 checked:border-emerald-500 checked:bg-emerald-500 hover:border-emerald-500 hover:bg-emerald-500 focus:outline-none focus:ring-0" className="hover:text-primary absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer border-primary checked:right-0 checked:border-primary checked:bg-primary hover:border-primary hover:bg-primary focus:outline-none focus:ring-0"
ref={inputRef} // Reference to the input element ref={inputRef} // Reference to the input element
/> />
<label <label
htmlFor={name} htmlFor={name}
className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer transition-colors duration-200 ${checked ? 'bg-emerald-300' : 'bg-gray-300'}`} className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer transition-colors duration-200 ${checked ? 'bg-primary/30' : 'bg-gray-300'}`}
></label> ></label>
</div> </div>
</div> </div>

View File

@ -3,16 +3,16 @@ import React from 'react';
export default function AcademicResults({ results }) { export default function AcademicResults({ results }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Résultats académiques</h2> <h2 className="font-headline text-xl font-semibold mb-4">Résultats académiques</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{results.map((result, idx) => ( {results.map((result, idx) => (
<div <div
key={idx} key={idx}
className="p-4 rounded-lg bg-emerald-50 flex flex-col gap-2 shadow" className="p-4 rounded-lg bg-primary/5 flex flex-col gap-2 shadow"
> >
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="font-medium">{result.subject}</span> <span className="font-medium">{result.subject}</span>
<span className="text-emerald-700 font-bold text-lg"> <span className="text-secondary font-bold text-lg">
{result.grade}/20 {result.grade}/20
</span> </span>
</div> </div>

View File

@ -21,16 +21,16 @@ export default function Attendance({
return ( return (
<div className="w-full bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200"> <div className="w-full bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Présence et assiduité</h2> <h2 className="font-headline text-xl font-semibold mb-4">Présence et assiduité</h2>
{absences.length === 0 ? ( {absences.length === 0 ? (
<div className="text-center text-emerald-600 font-medium py-8"> <div className="text-center text-primary font-medium py-8">
Aucune absence enregistrée 🎉 Aucune absence enregistrée
</div> </div>
) : ( ) : (
<ol className="relative border-l border-emerald-200"> <ol className="relative border-l border-primary/20">
{absences.map((absence, idx) => ( {absences.map((absence, idx) => (
<li key={idx} className="mb-6 ml-4"> <li key={idx} className="mb-6 ml-4">
<div className="absolute w-3 h-3 bg-emerald-400 rounded-full mt-1.5 -left-1.5 border border-white" /> <div className="absolute w-3 h-3 bg-tertiary rounded-full mt-1.5 -left-1.5 border border-white" />
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
{/* Infos principales à gauche */} {/* Infos principales à gauche */}
<div className="flex flex-col"> <div className="flex flex-col">
@ -45,7 +45,7 @@ export default function Attendance({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-medium">{absence.type}</span> <span className="font-medium">{absence.type}</span>
<span <span
className={`text-xs px-2 py-1 rounded ${absence.justified ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`} className={`text-xs px-2 py-1 rounded ${absence.justified ? 'bg-primary/10 text-secondary' : 'bg-red-100 text-red-700'}`}
> >
{absence.justified ? 'Justifiée' : 'Non justifiée'} {absence.justified ? 'Justifiée' : 'Non justifiée'}
</span> </span>
@ -92,7 +92,7 @@ export default function Attendance({
}); });
}); });
}} }}
className="mb-1 px-4 py-2 rounded-md shadow bg-emerald-500 text-white hover:bg-emerald-600 w-full" className="mb-1 px-4 py-2 rounded-md shadow bg-primary text-white hover:bg-primary w-full"
icon={<Trash2 className="w-6 h-6" />} icon={<Trash2 className="w-6 h-6" />}
text="Supprimer" text="Supprimer"
title="Evaluez l'élève" title="Evaluez l'élève"

View File

@ -16,7 +16,7 @@ const getGradeStyle = (grade) => {
case 2: case 2:
return 'bg-yellow-50 border-yellow-200'; return 'bg-yellow-50 border-yellow-200';
case 3: case 3:
return 'bg-emerald-50 border-emerald-200'; return 'bg-primary/5 border-primary/20';
default: default:
return 'bg-gray-50 border-gray-200'; return 'bg-gray-50 border-gray-200';
} }
@ -68,7 +68,7 @@ export default function GradeView({ data, grades, onGradeChange }) {
return ( return (
<div className="w-full"> <div className="w-full">
<div className="mb-4 mr-4 text-right text-emerald-700 font-semibold"> <div className="mb-4 mr-4 text-right text-secondary font-semibold">
{totalCompetencies} compétence{totalCompetencies > 1 ? 's' : ''} au {totalCompetencies} compétence{totalCompetencies > 1 ? 's' : ''} au
total total
</div> </div>
@ -76,19 +76,19 @@ export default function GradeView({ data, grades, onGradeChange }) {
<div key={domaine.domaine_id} className="mb-8"> <div key={domaine.domaine_id} className="mb-8">
<div <div
className={ className={
'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-emerald-50 border border-emerald-200 shadow-sm hover:bg-emerald-100' 'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-primary/5 border border-primary/20 shadow-sm hover:bg-primary/10'
} }
onClick={() => toggleDomain(domaine.domaine_id)} onClick={() => toggleDomain(domaine.domaine_id)}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BookOpen className="w-7 h-7 text-emerald-600" /> <BookOpen className="w-7 h-7 text-primary" />
<span className="text-2xl font-bold text-emerald-800"> <span className="text-2xl font-bold text-secondary">
{domaine.domaine_nom} {domaine.domaine_nom}
</span> </span>
</div> </div>
{openDomains[domaine.domaine_id] {openDomains[domaine.domaine_id]
? <ChevronDown className="w-5 h-5 text-emerald-700" /> ? <ChevronDown className="w-5 h-5 text-secondary" />
: <ChevronRight className="w-5 h-5 text-emerald-700" /> : <ChevronRight className="w-5 h-5 text-secondary" />
} }
</div> </div>
{openDomains[domaine.domaine_id] && ( {openDomains[domaine.domaine_id] && (
@ -97,7 +97,7 @@ export default function GradeView({ data, grades, onGradeChange }) {
<div key={categorie.categorie_id} className="mb-10 mr-4"> <div key={categorie.categorie_id} className="mb-10 mr-4">
<button <button
type="button" type="button"
className="flex items-center gap-2 text-lg font-semibold text-emerald-700 mb-4 hover:underline" className="flex items-center gap-2 text-lg font-semibold text-secondary mb-4 hover:underline"
onClick={() => toggleCategory(categorie.categorie_id)} onClick={() => toggleCategory(categorie.categorie_id)}
> >
{openCategories[categorie.categorie_id] {openCategories[categorie.categorie_id]
@ -115,7 +115,7 @@ export default function GradeView({ data, grades, onGradeChange }) {
key={competence.competence_id} key={competence.competence_id}
className={`border rounded-xl p-6 flex flex-col shadow transition hover:shadow-md ${getGradeStyle(grade)}`} className={`border rounded-xl p-6 flex flex-col shadow transition hover:shadow-md ${getGradeStyle(grade)}`}
> >
<div className="mb-4 pb-4 border-b border-emerald-800 flex items-center min-h-[48px]"> <div className="mb-4 pb-4 border-b border-secondary flex items-center min-h-[48px]">
<span className="text-gray-900 font-semibold text-base"> <span className="text-gray-900 font-semibold text-base">
{competence.nom} {competence.nom}
</span> </span>
@ -154,7 +154,7 @@ export default function GradeView({ data, grades, onGradeChange }) {
))} ))}
</div> </div>
)} )}
<hr className="my-6 border-emerald-100" /> <hr className="my-6 border-primary/10" />
</div> </div>
))} ))}
</div> </div>

View File

@ -27,13 +27,13 @@ export default function GradesDomainBarChart({ studentCompetencies }) {
if (avg > 0 && avg <= 1) return 'bg-gradient-to-r from-red-200 to-red-400'; if (avg > 0 && avg <= 1) return 'bg-gradient-to-r from-red-200 to-red-400';
if (avg > 1 && avg <= 2) if (avg > 1 && avg <= 2)
return 'bg-gradient-to-r from-yellow-200 to-yellow-400'; return 'bg-gradient-to-r from-yellow-200 to-yellow-400';
if (avg > 2) return 'bg-gradient-to-r from-emerald-200 to-emerald-500'; if (avg > 2) return 'bg-gradient-to-r from-primary/20 to-primary';
return 'bg-gray-200'; return 'bg-gray-200';
}; };
return ( return (
<div className="w-full flex flex-col items-center gap-4 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200"> <div className="w-full flex flex-col items-center gap-4 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-2">Moyenne par domaine</h2> <h2 className="font-headline text-xl font-semibold mb-2">Moyenne par domaine</h2>
<div className="w-full flex flex-col gap-2"> <div className="w-full flex flex-col gap-2">
{domainStats.map((d) => ( {domainStats.map((d) => (
<div key={d.name} className="flex items-center w-full"> <div key={d.name} className="flex items-center w-full">
@ -41,7 +41,7 @@ export default function GradesDomainBarChart({ studentCompetencies }) {
{d.name} {d.name}
</span> </span>
<div className="flex items-center" style={{ width: '40%' }}> <div className="flex items-center" style={{ width: '40%' }}>
<div className="w-full bg-emerald-100 h-3 rounded overflow-hidden"> <div className="w-full bg-primary/10 h-3 rounded overflow-hidden">
<div <div
className={`h-3 rounded ${getBarGradient(d.avg)}`} className={`h-3 rounded ${getBarGradient(d.avg)}`}
style={{ width: `${d.avg * 33.33}%` }} style={{ width: `${d.avg * 33.33}%` }}
@ -49,7 +49,7 @@ export default function GradesDomainBarChart({ studentCompetencies }) {
</div> </div>
</div> </div>
<span <span
className="text-xs font-semibold text-emerald-700 text-left pl-2 flex-shrink-0" className="text-xs font-semibold text-secondary text-left pl-2 flex-shrink-0"
style={{ width: '10%' }} style={{ width: '10%' }}
> >
{d.avg} {d.avg}

View File

@ -15,7 +15,7 @@ export default function GradesStatsCircle({ grades }) {
return ( return (
<div className="w-full flex flex-col items-center gap-4 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200"> <div className="w-full flex flex-col items-center gap-4 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-2">Statistiques globales</h2> <h2 className="font-headline text-xl font-semibold mb-2">Statistiques globales</h2>
<div style={{ width: 120, height: 120 }}> <div style={{ width: 120, height: 120 }}>
<CircularProgressbar <CircularProgressbar
value={percent} value={percent}
@ -28,7 +28,7 @@ export default function GradesStatsCircle({ grades }) {
/> />
</div> </div>
<div className="flex flex-col items-center text-sm mt-2"> <div className="flex flex-col items-center text-sm mt-2">
<span className="text-emerald-700 font-semibold"> <span className="text-secondary font-semibold">
{acquired} acquis {acquired} acquis
</span> </span>
<span className="text-yellow-600">{inProgress} en cours</span> <span className="text-yellow-600">{inProgress} en cours</span>

View File

@ -3,7 +3,7 @@ import React from 'react';
export default function Homeworks({ homeworks }) { export default function Homeworks({ homeworks }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Suivi des devoirs</h2> <h2 className="font-headline text-xl font-semibold mb-4">Suivi des devoirs</h2>
<ul className="divide-y divide-gray-100"> <ul className="divide-y divide-gray-100">
{homeworks.map((hw, idx) => ( {homeworks.map((hw, idx) => (
<li <li
@ -15,7 +15,7 @@ export default function Homeworks({ homeworks }) {
<span className="ml-2 text-xs text-gray-400">{hw.dueDate}</span> <span className="ml-2 text-xs text-gray-400">{hw.dueDate}</span>
</div> </div>
<span <span
className={`text-xs px-2 py-1 rounded ${hw.status === 'Rendu' ? 'bg-emerald-100 text-emerald-700' : 'bg-yellow-100 text-yellow-700'}`} className={`text-xs px-2 py-1 rounded ${hw.status === 'Rendu' ? 'bg-primary/10 text-secondary' : 'bg-yellow-100 text-yellow-700'}`}
> >
{hw.status} {hw.status}
</span> </span>

View File

@ -3,7 +3,7 @@ import React from 'react';
export default function Orientation({ orientation }) { export default function Orientation({ orientation }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Orientation & conseils</h2> <h2 className="font-headline text-xl font-semibold mb-4">Orientation & conseils</h2>
<ul className="divide-y divide-gray-100"> <ul className="divide-y divide-gray-100">
{orientation.map((item, idx) => ( {orientation.map((item, idx) => (
<li <li

View File

@ -3,7 +3,7 @@ import React from 'react';
export default function Remarks({ remarks }) { export default function Remarks({ remarks }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Remarques & observations</h2> <h2 className="font-headline text-xl font-semibold mb-4">Remarques & observations</h2>
<ul className="divide-y divide-gray-100"> <ul className="divide-y divide-gray-100">
{remarks.map((remark, idx) => ( {remarks.map((remark, idx) => (
<li <li

View File

@ -3,7 +3,7 @@ import React from 'react';
export default function SpecificEvaluations({ specificEvaluations }) { export default function SpecificEvaluations({ specificEvaluations }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Évaluations spécifiques</h2> <h2 className="font-headline text-xl font-semibold mb-4">Évaluations spécifiques</h2>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{specificEvaluations.map((evalItem, idx) => ( {specificEvaluations.map((evalItem, idx) => (
<div <div

View File

@ -3,14 +3,14 @@ import React from 'react';
export default function WorkPlan({ workPlan }) { export default function WorkPlan({ workPlan }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4"> <h2 className="font-headline text-xl font-semibold mb-4">
Plan de travail personnalisé Plan de travail personnalisé
</h2> </h2>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{workPlan.map((plan, idx) => ( {workPlan.map((plan, idx) => (
<div <div
key={idx} key={idx}
className="p-3 rounded border border-emerald-100 bg-emerald-50 flex flex-col sm:flex-row sm:items-center sm:justify-between" className="p-3 rounded border border-primary/10 bg-primary/5 flex flex-col sm:flex-row sm:items-center sm:justify-between"
> >
<div> <div>
<span className="font-medium">{plan.objective}</span> <span className="font-medium">{plan.objective}</span>
@ -18,7 +18,7 @@ export default function WorkPlan({ workPlan }) {
({plan.support}) ({plan.support})
</span> </span>
</div> </div>
<span className="text-xs px-2 py-1 rounded bg-emerald-200 text-emerald-800 mt-1 sm:mt-0"> <span className="text-xs px-2 py-1 rounded bg-primary/20 text-secondary mt-1 sm:mt-0">
{plan.followUp} {plan.followUp}
</span> </span>
</div> </div>

View File

@ -262,7 +262,7 @@ export default function DynamicFormsList({
<div className="mt-8 mb-4 w-full mx-auto flex flex-col lg:flex-row gap-8 overflow-x-hidden"> <div className="mt-8 mb-4 w-full mx-auto flex flex-col lg:flex-row gap-8 overflow-x-hidden">
{/* Liste des formulaires */} {/* Liste des formulaires */}
<div className="w-full lg:w-1/4 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200"> <div className="w-full lg:w-1/4 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Formulaires à compléter Formulaires à compléter
</h3> </h3>
<div className="text-sm text-gray-600 mb-4"> <div className="text-sm text-gray-600 mb-4">
@ -313,16 +313,16 @@ export default function DynamicFormsList({
if (isValidated === true) { if (isValidated === true) {
statusLabel = 'Validé'; statusLabel = 'Validé';
statusColor = 'emerald'; statusColor = 'emerald';
icon = <CheckCircle className="w-5 h-5 text-emerald-600" />; icon = <CheckCircle className="w-5 h-5 text-primary" />;
bgClass = 'bg-emerald-50'; bgClass = 'bg-primary/5';
borderClass = 'border border-emerald-200'; borderClass = 'border border-primary/20';
textClass = 'text-emerald-700'; textClass = 'text-secondary';
bgClass = isActive ? 'bg-emerald-200' : bgClass; bgClass = isActive ? 'bg-primary/20' : bgClass;
borderClass = isActive borderClass = isActive
? 'border border-emerald-300' ? 'border border-primary/30'
: borderClass; : borderClass;
textClass = isActive textClass = isActive
? 'text-emerald-900 font-semibold' ? 'text-secondary font-semibold'
: textClass; : textClass;
canEdit = false; canEdit = false;
} else if (isValidated === false) { } else if (isValidated === false) {
@ -409,12 +409,12 @@ export default function DynamicFormsList({
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 overflow-x-hidden"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 overflow-x-hidden">
<div className="mb-6"> <div className="mb-6">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<h3 className="text-xl font-semibold text-gray-800"> <h3 className="font-headline text-xl font-semibold text-gray-800">
{currentTemplate.name} {currentTemplate.name}
</h3> </h3>
{/* Label d'état */} {/* Label d'état */}
{currentTemplate.isValidated === true ? ( {currentTemplate.isValidated === true ? (
<span className="px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-sm font-semibold"> <span className="px-2 py-0.5 rounded bg-primary/10 text-secondary text-sm font-semibold">
Validé Validé
</span> </span>
) : currentTemplate.isValidated === false ? ( ) : currentTemplate.isValidated === false ? (
@ -478,7 +478,9 @@ export default function DynamicFormsList({
currentTemplate.formTemplateData?.submitLabel || 'Valider', currentTemplate.formTemplateData?.submitLabel || 'Valider',
}} }}
masterFile={ masterFile={
currentTemplate.master_file_url || currentTemplate.file || null currentTemplate.master_file_url ||
currentTemplate.file ||
null
} }
initialValues={ initialValues={
extractResponses(formsData[currentTemplate.id]) || extractResponses(formsData[currentTemplate.id]) ||
@ -509,9 +511,13 @@ export default function DynamicFormsList({
{currentTemplate.isValidated !== true && ( {currentTemplate.isValidated !== true && (
<div className="flex flex-col items-center gap-4 w-full"> <div className="flex flex-col items-center gap-4 w-full">
{/* Bouton télécharger le document source (fichier maître) */} {/* Bouton télécharger le document source (fichier maître) */}
{(currentTemplate.master_file_url || currentTemplate.file) && ( {(currentTemplate.master_file_url ||
currentTemplate.file) && (
<a <a
href={getSecureFileUrl(currentTemplate.master_file_url || currentTemplate.file)} href={getSecureFileUrl(
currentTemplate.master_file_url ||
currentTemplate.file
)}
className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition" className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
download download
> >
@ -528,7 +534,9 @@ export default function DynamicFormsList({
onFileSelect={(file) => onFileSelect={(file) =>
handleUpload(file, currentTemplate) handleUpload(file, currentTemplate)
} }
existingFile={currentTemplate.file_url || currentTemplate.file} existingFile={
currentTemplate.file_url || currentTemplate.file
}
required required
enable={true} enable={true}
/> />
@ -544,7 +552,7 @@ export default function DynamicFormsList({
{currentTemplateIndex >= schoolFileTemplates.length && ( {currentTemplateIndex >= schoolFileTemplates.length && (
<div className="text-center py-8"> <div className="text-center py-8">
<CheckCircle className="w-16 h-16 text-green-600 mx-auto mb-4" /> <CheckCircle className="w-16 h-16 text-green-600 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-green-600 mb-2"> <h3 className="font-headline text-lg font-semibold text-green-600 mb-2">
Tous les formulaires ont été complétés ! Tous les formulaires ont été complétés !
</h3> </h3>
<p className="text-gray-600"> <p className="text-gray-600">

View File

@ -107,7 +107,7 @@ const FilesModal = ({
{/* Section Fiche élève */} {/* Section Fiche élève */}
{files.registrationFile && ( {files.registrationFile && (
<div> <div>
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Fiche élève Fiche élève
</h3> </h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -127,7 +127,7 @@ const FilesModal = ({
{/* Section Documents fusionnés */} {/* Section Documents fusionnés */}
{files.fusionFile && ( {files.fusionFile && (
<div> <div>
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Documents fusionnés Documents fusionnés
</h3> </h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -148,7 +148,7 @@ const FilesModal = ({
{/* Section Fichiers École */} {/* Section Fichiers École */}
<div> <div>
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Formulaires de l&apos;établissement Formulaires de l&apos;établissement
</h3> </h3>
<ul className="space-y-2"> <ul className="space-y-2">
@ -184,7 +184,7 @@ const FilesModal = ({
{/* Section Fichiers Parent */} {/* Section Fichiers Parent */}
<div> <div>
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Pièces fournies Pièces fournies
</h3> </h3>
<ul className="space-y-2"> <ul className="space-y-2">
@ -218,7 +218,7 @@ const FilesModal = ({
{/* Section Mandat SEPA */} {/* Section Mandat SEPA */}
<div> <div>
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Mandat SEPA Mandat SEPA
</h3> </h3>
{files.sepaFile ? ( {files.sepaFile ? (

View File

@ -4,7 +4,7 @@ import Table from '@/components/Table';
export default function FilesToSign({ fileTemplates, columns }) { export default function FilesToSign({ fileTemplates, columns }) {
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800"> <h2 className="font-headline text-xl font-bold mb-4 text-gray-800">
Fichiers à remplir Fichiers à remplir
</h2> </h2>
<Table <Table

View File

@ -185,8 +185,8 @@ export default function FilesToUpload({
<button <button
className={`flex items-center justify-center w-8 h-8 rounded-full ${ className={`flex items-center justify-center w-8 h-8 rounded-full ${
actionType === 'upload' && selectedFile?.id === row.id actionType === 'upload' && selectedFile?.id === row.id
? 'bg-emerald-100 text-emerald-600 ring-3 ring-emerald-500' ? 'bg-primary/10 text-primary ring-3 ring-primary'
: 'text-emerald-500 hover:text-emerald-700' : 'text-primary hover:text-secondary'
}`} }`}
onClick={() => { onClick={() => {
if (actionType === 'upload' && selectedFile?.id === row.id) { if (actionType === 'upload' && selectedFile?.id === row.id) {
@ -212,11 +212,11 @@ export default function FilesToUpload({
<div className="mt-8 mb-4 w-3/5"> <div className="mt-8 mb-4 w-3/5">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="bg-emerald-100 p-3 rounded-full shadow-md"> <div className="bg-primary/10 p-3 rounded-full shadow-md">
<FileText className="w-8 h-8 text-emerald-600" /> <FileText className="w-8 h-8 text-primary" />
</div> </div>
<div> <div>
<h2 className="text-2xl font-bold text-gray-800"> <h2 className="font-headline text-2xl font-bold text-gray-800">
Pièces à fournir Pièces à fournir
</h2> </h2>
<p className="text-sm text-gray-500 italic"> <p className="text-sm text-gray-500 italic">

View File

@ -820,8 +820,7 @@ export default function InscriptionFormShared({
? `${navButtonBaseClass} bg-gray-200 text-gray-500 cursor-not-allowed` ? `${navButtonBaseClass} bg-gray-200 text-gray-500 cursor-not-allowed`
: `${navButtonBaseClass} bg-primary hover:bg-secondary text-white`; : `${navButtonBaseClass} bg-primary hover:bg-secondary text-white`;
const navSecondaryClass = const navSecondaryClass = `${navButtonBaseClass} bg-neutral text-secondary border border-gray-300 hover:bg-white`;
`${navButtonBaseClass} bg-neutral text-secondary border border-gray-300 hover:bg-white`;
// Rendu du composant // Rendu du composant
return ( return (
@ -985,7 +984,6 @@ export default function InscriptionFormShared({
/> />
)} )}
</div> </div>
</div> </div>
); );
} }

View File

@ -76,7 +76,7 @@ export default function PaymentMethodSelector({
<> <>
{/* Frais d'inscription */} {/* Frais d'inscription */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2"> <h2 className="font-headline text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">
Frais d&apos;inscription Frais d&apos;inscription
</h2> </h2>
@ -155,7 +155,7 @@ export default function PaymentMethodSelector({
{/* Frais de scolarité */} {/* Frais de scolarité */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mt-12"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mt-12">
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2"> <h2 className="font-headline text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">
Frais de scolarité Frais de scolarité
</h2> </h2>

View File

@ -132,7 +132,7 @@ export default function ResponsableInputFields({
{guardians.map((item, index) => ( {guardians.map((item, index) => (
<div className="p-6 " key={index}> <div className="p-6 " key={index}>
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-bold"> <h3 className="font-headline text-xl font-bold">
{t('responsable')} {index + 1} {t('responsable')} {index + 1}
</h3> </h3>
{guardians.length > 1 && ( {guardians.length > 1 && (
@ -259,11 +259,11 @@ export default function ResponsableInputFields({
className={`w-8 h-8 ${ className={`w-8 h-8 ${
guardians.length >= MAX_GUARDIANS guardians.length >= MAX_GUARDIANS
? 'text-gray-400 cursor-not-allowed' ? 'text-gray-400 cursor-not-allowed'
: 'text-green-500 cursor-pointer hover:text-green-700' : 'text-primary cursor-pointer hover:text-secondary'
} transition-colors border-2 ${ } transition-colors border-2 ${
guardians.length >= MAX_GUARDIANS guardians.length >= MAX_GUARDIANS
? 'border-gray-400' ? 'border-gray-400'
: 'border-green-500 hover:border-green-700' : 'border-primary hover:border-secondary'
} rounded-full p-1`} } rounded-full p-1`}
onClick={(e) => { onClick={(e) => {
if (guardians.length < MAX_GUARDIANS) { if (guardians.length < MAX_GUARDIANS) {

View File

@ -103,7 +103,7 @@ export default function SiblingInputFields({
{siblings.map((item, index) => ( {siblings.map((item, index) => (
<div className="p-6" key={index}> <div className="p-6" key={index}>
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-bold">Frère/Sœur {index + 1}</h3> <h3 className="font-headline text-xl font-bold">Frère/Sœur {index + 1}</h3>
<Trash2 <Trash2
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors" className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
onClick={() => deleteSibling(index)} onClick={() => deleteSibling(index)}
@ -160,7 +160,7 @@ export default function SiblingInputFields({
{enable && ( {enable && (
<div className="flex justify-center"> <div className="flex justify-center">
<Plus <Plus
className="w-8 h-8 text-green-500 cursor-pointer hover:text-green-700 transition-colors border-2 border-green-500 hover:border-green-700 rounded-full p-1" className="w-8 h-8 text-primary cursor-pointer hover:text-secondary transition-colors border-2 border-primary hover:border-secondary rounded-full p-1"
onClick={addSibling} onClick={addSibling}
/> />
</div> </div>

View File

@ -213,7 +213,7 @@ export default function ValidateSubscription({
<div className="w-3/4"> <div className="w-3/4">
{currentTemplateIndex < allTemplates.length && ( {currentTemplateIndex < allTemplates.length && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
{allTemplates[currentTemplateIndex].name || 'Document sans nom'} {allTemplates[currentTemplateIndex].name || 'Document sans nom'}
</h3> </h3>
<iframe <iframe
@ -241,7 +241,7 @@ export default function ValidateSubscription({
<div className="w-1/4 flex flex-col flex-1 gap-4 h-full"> <div className="w-1/4 flex flex-col flex-1 gap-4 h-full">
{/* Liste des documents */} {/* Liste des documents */}
<div className="flex-1 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200 overflow-y-auto"> <div className="flex-1 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200 overflow-y-auto">
<h3 className="text-lg font-semibold text-gray-800 mb-4"> <h3 className="font-headline text-lg font-semibold text-gray-800 mb-4">
Liste des documents Liste des documents
</h3> </h3>
<ul className="space-y-2"> <ul className="space-y-2">
@ -269,7 +269,7 @@ export default function ValidateSubscription({
<button <button
type="button" type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1 className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1
${docStatuses[index] === 'accepted' ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white text-emerald-600 border-emerald-300'}`} ${docStatuses[index] === 'accepted' ? 'bg-primary text-white border-primary' : 'bg-white text-primary border-primary/30'}`}
aria-pressed={docStatuses[index] === 'accepted'} aria-pressed={docStatuses[index] === 'accepted'}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -414,7 +414,7 @@ export default function ValidateSubscription({
!allChecked || !allChecked ||
(allChecked && allValidated && !formData.associated_class) (allChecked && allValidated && !formData.associated_class)
? 'bg-gray-300 text-gray-700 cursor-not-allowed' ? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-primary text-white hover:bg-primary'
}`} }`}
disabled={ disabled={
!allChecked || !allChecked ||

View File

@ -1,7 +1,7 @@
const Loader = () => { const Loader = () => {
return ( return (
<div className="flex justify-center items-center h-screen"> <div className="flex justify-center items-center h-screen">
<div className="w-9 h-9 border-4 border-t-4 border-t-emerald-500 border-gray-200 rounded-full animate-spin"></div> <div className="w-9 h-9 border-4 border-t-4 border-t-primary border-gray-200 rounded-full animate-spin"></div>
</div> </div>
); );
}; };

View File

@ -48,7 +48,7 @@ const PaginationButton = ({ text, onClick }) => (
const PaginationNumber = ({ number, active, onClick }) => ( const PaginationNumber = ({ number, active, onClick }) => (
<button <button
className={`w-8 h-8 flex items-center justify-center rounded ${ className={`w-8 h-8 flex items-center justify-center rounded ${
active ? 'bg-emerald-500 text-white' : 'text-gray-600 hover:bg-gray-50' active ? 'bg-primary text-white' : 'text-gray-600 hover:bg-gray-50'
}`} }`}
onClick={onClick} onClick={onClick}
> >

View File

@ -69,22 +69,24 @@ const PaymentModeSelector = ({
}; };
return ( return (
<div className="space-y-4"> <div className="space-y-3">
<div className="flex items-center mb-4"> <div className="flex items-center gap-2 mb-3">
<DollarSign className="w-6 h-6 text-emerald-500 mr-2" /> <DollarSign className="w-5 h-5 text-primary" />
<h2 className="text-xl font-semibold">Modes de paiement</h2> <h3 className="font-headline text-base font-semibold text-gray-800">
Modes de paiement
</h3>
</div> </div>
<div className="grid grid-cols-2 gap-4 mt-4"> <div className="grid grid-cols-2 gap-3">
{paymentModesOptions.map((mode) => ( {paymentModesOptions.map((mode) => (
<button <button
key={mode.id} key={mode.id}
type="button" type="button"
onClick={() => handleModeToggle(mode.id)} onClick={() => handleModeToggle(mode.id)}
className={`p-4 rounded-lg shadow-md text-center text-gray-700 ${ className={`py-2 px-3 rounded border text-sm font-label font-medium text-center transition-colors min-h-[44px] ${
activePaymentModes.includes(mode.id) activePaymentModes.includes(mode.id)
? 'bg-emerald-100' ? 'bg-primary/10 border-primary text-primary'
: 'bg-stone-50' : 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50'
} hover:bg-emerald-200`} }`}
> >
{mode.name} {mode.name}
</button> </button>

View File

@ -1,10 +1,7 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { Calendar } from 'lucide-react'; import { Calendar } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
import CheckBox from '@/components/Form/CheckBox';
const paymentPlansOptions = [ const paymentPlansOptions = [
{ id: 1, name: '1 fois', frequency: 1 }, { id: 1, name: '1 fois', frequency: 1 },
@ -13,19 +10,6 @@ const paymentPlansOptions = [
{ id: 4, name: '12 fois', frequency: 12 }, { id: 4, name: '12 fois', frequency: 12 },
]; ];
/**
* Affiche les plans de paiement communs aux deux types de frais.
* Quand `allPaymentPlans` est fourni (mode unifié), un plan coché est créé pour
* les deux types (inscription 0 ET scolarité 1) en même temps.
*
* Props (mode unifié) :
* allPaymentPlans : [{plan_type, type, ...}, ...] - liste combinée des deux types
* handleCreate : (data) => Promise - avec type et establishment déjà présent dans data
* handleDelete : (id) => Promise
*
* Props (mode legacy) :
* paymentPlans, handleCreate, handleDelete, type
*/
const PaymentPlanSelector = ({ const PaymentPlanSelector = ({
paymentPlans, paymentPlans,
allPaymentPlans, allPaymentPlans,
@ -33,8 +17,6 @@ const PaymentPlanSelector = ({
handleDelete, handleDelete,
type, type,
}) => { }) => {
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState('');
const { selectedEstablishmentId } = useEstablishment(); const { selectedEstablishmentId } = useEstablishment();
const [checkedPlans, setCheckedPlans] = useState([]); const [checkedPlans, setCheckedPlans] = useState([]);
@ -49,12 +31,10 @@ const PaymentPlanSelector = ({
); );
const unified = !!allPaymentPlans; const unified = !!allPaymentPlans;
// Un plan est coché si au moins un enregistrement existe pour cette option
const isChecked = (planOption) => checkedPlans.includes(planOption.id); const isChecked = (planOption) => checkedPlans.includes(planOption.id);
const handlePlanToggle = (planOption) => { const handlePlanToggle = (planOption) => {
if (isChecked(planOption)) { if (isChecked(planOption)) {
// Supprimer tous les enregistrements correspondant à cette option (les deux types en mode unifié)
const toDelete = plans.filter( const toDelete = plans.filter(
(p) => (p) =>
(typeof p.plan_type === 'object' ? p.plan_type.id : p.plan_type) === (typeof p.plan_type === 'object' ? p.plan_type.id : p.plan_type) ===
@ -67,7 +47,6 @@ const PaymentPlanSelector = ({
} else { } else {
setCheckedPlans((prev) => [...prev, planOption.id]); setCheckedPlans((prev) => [...prev, planOption.id]);
if (unified) { if (unified) {
// Créer pour inscription (0) et scolarité (1)
[0, 1].forEach((t) => [0, 1].forEach((t) =>
handleCreate({ handleCreate({
plan_type: planOption.id, plan_type: planOption.id,
@ -97,50 +76,29 @@ const PaymentPlanSelector = ({
}, [plans]); }, [plans]);
return ( return (
<div className="space-y-4"> <div className="space-y-3">
<div className="flex items-center mb-4"> <div className="flex items-center gap-2 mb-3">
<Calendar className="w-6 h-6 text-emerald-500 mr-2" /> <Calendar className="w-5 h-5 text-primary" />
<h2 className="text-xl font-semibold">Paiement en plusieurs fois</h2> <h3 className="font-headline text-base font-semibold text-gray-800">
Paiement en plusieurs fois
</h3>
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-3">
<Table {paymentPlansOptions.map((option) => (
data={paymentPlansOptions} <button
columns={[ key={option.id}
{ name: 'OPTION', label: 'Option' }, type="button"
{ name: 'ACTIF', label: 'Actif' }, onClick={() => handlePlanToggle(option)}
]} className={`py-2 px-3 rounded border text-sm font-label font-medium text-center transition-colors min-h-[44px] ${
renderCell={(row, column) => { isChecked(option)
switch (column) { ? 'bg-primary/10 border-primary text-primary'
case 'OPTION': : 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50'
return ( }`}
<span className="text-sm font-medium text-gray-900"> >
{row.name} {option.name}
</span> </button>
); ))}
case 'ACTIF':
return (
<CheckBox
item={{ id: row.id }}
formData={{ checked: isChecked(row) }}
handleChange={() => handlePlanToggle(row)}
fieldName="checked"
itemLabelFunc={() => ''}
horizontal={true}
/>
);
default:
return null;
}
}}
/>
</div> </div>
<Popup
isOpen={popupVisible}
message={popupMessage}
onConfirm={() => setPopupVisible(false)}
onCancel={() => setPopupVisible(false)}
uniqueConfirmButton={true}
/>
</div> </div>
); );
}; };

View File

@ -53,10 +53,10 @@ const Popup = ({
</button> </button>
)} )}
<button <button
className="px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 focus:outline-none transition" className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary focus:outline-none transition"
onClick={() => { onClick={() => {
if (setIsOpen) setIsOpen(false); if (onConfirm) onConfirm();
else if (onConfirm) onConfirm(); else if (setIsOpen) setIsOpen(false);
}} }}
> >
{uniqueConfirmButton ? 'Fermer' : 'Confirmer'} {uniqueConfirmButton ? 'Fermer' : 'Confirmer'}

View File

@ -152,13 +152,13 @@ const ProfileSelector = ({ onRoleChange, className = '', compact = false }) => {
{user?.email} {user?.email}
</div> </div>
<div <div
className="font-semibold text-base text-emerald-700 text-left truncate max-w-full" className="font-semibold text-base text-secondary text-left truncate max-w-full"
title={selectedEstablishment?.name || ''} title={selectedEstablishment?.name || ''}
> >
{selectedEstablishment?.name || ''} {selectedEstablishment?.name || ''}
</div> </div>
<div <div
className="italic text-sm text-emerald-600 text-left truncate max-w-full" className="italic text-sm text-primary text-left truncate max-w-full"
title={getRightStr(selectedEstablishment?.role_type) || ''} title={getRightStr(selectedEstablishment?.role_type) || ''}
> >
{getRightStr(selectedEstablishment?.role_type) || ''} {getRightStr(selectedEstablishment?.role_type) || ''}

View File

@ -10,9 +10,9 @@ const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
text-sm font-semibold text-sm font-semibold
${ ${
isCompleted isCompleted
? 'bg-emerald-600 text-white' ? 'bg-primary text-white'
: isActive : isActive
? 'bg-emerald-600 text-white' ? 'bg-primary text-white'
: 'bg-gray-200 text-gray-600' : 'bg-gray-200 text-gray-600'
} }
`} `}
@ -38,7 +38,7 @@ const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
<span <span
className={` className={`
text-xs font-medium w-20 text-center block break-words text-xs font-medium w-20 text-center block break-words
${isActive ? 'text-emerald-600' : 'text-gray-500'} ${isActive ? 'text-primary' : 'text-gray-500'}
`} `}
> >
{title} {title}
@ -51,7 +51,7 @@ const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
const SpacerStep = ({ isCompleted }) => { const SpacerStep = ({ isCompleted }) => {
return ( return (
<div <div
className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`} className={`flex-1 h-0.5 ${isCompleted ? 'bg-primary' : 'bg-gray-200'}`}
/> />
); );
}; };

View File

@ -9,24 +9,24 @@ const SectionHeader = ({
button = false, button = false,
buttonOpeningModal = false, buttonOpeningModal = false,
onClick = null, onClick = null,
secondaryButton = null, // Bouton secondaire (ex: export) secondaryButton = null,
}) => { }) => {
return ( return (
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div <div
className={`${discountStyle ? 'bg-yellow-100' : 'bg-emerald-100'} p-3 rounded-full shadow-md`} className={`${discountStyle ? 'bg-yellow-100' : 'bg-primary/10'} p-3 rounded-full shadow-sm`}
> >
<Icon <Icon
className={ className={
discountStyle discountStyle
? 'w-8 h-8 text-yellow-600' ? 'w-8 h-8 text-yellow-600'
: 'w-8 h-8 text-emerald-600' : 'w-8 h-8 text-primary'
} }
/> />
</div> </div>
<div> <div>
<h2 className="text-2xl font-bold text-gray-800">{title}</h2> <h2 className="font-headline text-2xl font-bold text-gray-800">{title}</h2>
<p className="text-sm text-gray-500 italic">{description}</p> <p className="text-sm text-gray-500 italic">{description}</p>
</div> </div>
</div> </div>
@ -37,8 +37,8 @@ const SectionHeader = ({
onClick={onClick} onClick={onClick}
className={ className={
buttonOpeningModal buttonOpeningModal
? 'flex items-center bg-emerald-200 text-emerald-700 p-2 rounded-full shadow-sm hover:bg-emerald-300' ? 'flex items-center bg-primary/10 text-primary p-2 rounded-full shadow-sm hover:bg-primary/20 min-h-[44px] min-w-[44px] justify-center transition-colors'
: 'text-emerald-500 hover:bg-emerald-200 rounded-full p-2' : 'text-primary hover:bg-primary/10 rounded-full p-2 min-h-[44px] min-w-[44px] flex items-center justify-center transition-colors'
} }
> >
<Plus className="w-6 h-6" /> <Plus className="w-6 h-6" />

View File

@ -15,15 +15,15 @@ const THEME = {
buttonHover: 'hover:bg-blue-100', buttonHover: 'hover:bg-blue-100',
}, },
formulaire: { formulaire: {
bg: 'bg-emerald-50', bg: 'bg-primary/5',
border: 'border-emerald-200', border: 'border-primary/20',
iconBg: 'bg-emerald-100', iconBg: 'bg-primary/10',
icon: 'text-emerald-600', icon: 'text-primary',
title: 'text-emerald-800', title: 'text-secondary',
desc: 'text-emerald-600', desc: 'text-primary',
button: 'bg-emerald-500 text-white hover:bg-emerald-600', button: 'bg-primary text-white hover:bg-primary',
buttonText: 'text-emerald-700', buttonText: 'text-secondary',
buttonHover: 'hover:bg-emerald-100', buttonHover: 'hover:bg-primary/10',
}, },
parent: { parent: {
bg: 'bg-orange-50', bg: 'bg-orange-50',
@ -59,7 +59,7 @@ const SectionHeaderDocument = ({
</span> </span>
)} )}
<div> <div>
<h2 className={`text-lg font-semibold ${theme.title}`}>{title}</h2> <h2 className={`font-headline text-lg font-semibold ${theme.title}`}>{title}</h2>
{description && ( {description && (
<p className={`text-xs ${theme.desc}`}>{description}</p> <p className={`text-xs ${theme.desc}`}>{description}</p>
)} )}

View File

@ -4,14 +4,14 @@ const SectionTitle = ({ title, children }) => {
return ( return (
<div className="relative flex"> <div className="relative flex">
{/* Liseré vertical */} {/* Liseré vertical */}
<div className="w-1 bg-emerald-400"></div> <div className="w-1 bg-tertiary"></div>
{/* Contenu de la section */} {/* Contenu de la section */}
<div className="flex-1 pl-6"> <div className="flex-1 pl-6">
{/* Titre avec liseré horizontal */} {/* Titre avec liseré horizontal */}
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
<h2 className="text-emerald-700 font-bold text-xl">{title}</h2> <h2 className="font-headline text-secondary font-bold text-xl">{title}</h2>
<div className="flex-1 h-0.5 bg-emerald-200 ml-4"></div> <div className="flex-1 h-0.5 bg-primary/20 ml-4"></div>
</div> </div>
{/* Contenu passé en children */} {/* Contenu passé en children */}
{children} {children}

View File

@ -6,8 +6,8 @@ import ProfileSelector from '@/components/ProfileSelector';
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => ( const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
<div <div
onClick={onClick} onClick={onClick}
className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover:bg-emerald-100 ${ className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover:bg-primary/10 ${
active ? 'bg-emerald-50 text-emerald-600' : 'text-gray-600' active ? 'bg-primary/5 text-primary' : 'text-gray-600'
}`} }`}
> >
<Icon size={20} /> <Icon size={20} />

View File

@ -42,20 +42,17 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
return ( return (
<div className="flex flex-col h-full w-full"> <div className="flex flex-col h-full w-full">
{/* Tabs Header */} <div className="relative flex items-center bg-neutral border-b border-gray-200 shadow-sm">
<div className="relative flex items-center bg-gray-50 border-b border-gray-200 shadow-sm">
{/* Flèche gauche */}
{showLeftArrow && ( {showLeftArrow && (
<button <button
onClick={() => scroll('left')} onClick={() => scroll('left')}
className="absolute left-0 z-10 h-full w-10 flex items-center justify-center bg-gradient-to-r from-gray-50 via-gray-50 to-transparent text-gray-500 hover:text-emerald-600 active:text-emerald-700" className="absolute left-0 z-10 h-full w-10 flex items-center justify-center bg-gradient-to-r from-neutral via-neutral to-transparent text-gray-500 hover:text-primary active:text-secondary"
aria-label="Tabs précédents" aria-label="Tabs pr\u00e9c\u00e9dents"
> >
<ChevronLeft size={22} strokeWidth={2.5} /> <ChevronLeft size={22} strokeWidth={2.5} />
</button> </button>
)} )}
{/* Liste des onglets scrollable */}
<div <div
ref={scrollRef} ref={scrollRef}
className="flex overflow-x-auto scrollbar-none scroll-smooth" className="flex overflow-x-auto scrollbar-none scroll-smooth"
@ -65,10 +62,10 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
<button <button
key={tab.id} key={tab.id}
onClick={() => handleTabChange(tab.id)} onClick={() => handleTabChange(tab.id)}
className={`flex-shrink-0 whitespace-nowrap h-14 px-5 font-medium transition-colors duration-200 ${ className={`flex-shrink-0 whitespace-nowrap h-14 px-5 font-label font-medium transition-colors duration-200 min-h-[44px] ${
activeTab === tab.id activeTab === tab.id
? 'border-b-4 border-emerald-500 text-emerald-600 bg-emerald-50 font-semibold' ? 'border-b-4 border-primary text-primary bg-primary/5 font-semibold'
: 'text-gray-500 hover:text-emerald-500' : 'text-gray-500 hover:text-primary'
}`} }`}
> >
{tab.label} {tab.label}
@ -76,11 +73,10 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
))} ))}
</div> </div>
{/* Flèche droite */}
{showRightArrow && ( {showRightArrow && (
<button <button
onClick={() => scroll('right')} onClick={() => scroll('right')}
className="absolute right-0 z-10 h-full w-10 flex items-center justify-center bg-gradient-to-l from-gray-50 via-gray-50 to-transparent text-gray-500 hover:text-emerald-600 active:text-emerald-700" className="absolute right-0 z-10 h-full w-10 flex items-center justify-center bg-gradient-to-l from-neutral via-neutral to-transparent text-gray-500 hover:text-primary active:text-secondary"
aria-label="Tabs suivants" aria-label="Tabs suivants"
> >
<ChevronRight size={22} strokeWidth={2.5} /> <ChevronRight size={22} strokeWidth={2.5} />
@ -88,8 +84,7 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
)} )}
</div> </div>
{/* Tabs Content */} <div className="flex-1 flex flex-col overflow-hidden rounded-b-md shadow-inner">
<div className="flex-1 flex flex-col overflow-hidden rounded-b-lg shadow-inner">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{tabs.map( {tabs.map(
(tab) => (tab) =>

View File

@ -25,25 +25,25 @@ const Slider = ({ min, max, value, onChange }) => {
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="flex items-center space-x-2 w-1/2"> <div className="flex items-center space-x-2 w-1/2">
<span className="text-emerald-600">{value[0]}</span> <span className="text-primary">{value[0]}</span>
<input <input
type="range" type="range"
min={min} min={min}
max={max} max={max}
value={value[0]} value={value[0]}
onChange={handleMinChange} onChange={handleMinChange}
className="w-full h-2 bg-emerald-200 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-opacity-50" className="w-full h-2 bg-primary/20 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50"
/> />
</div> </div>
<div className="flex items-center space-x-2 w-1/2 justify-end"> <div className="flex items-center space-x-2 w-1/2 justify-end">
<span className="text-emerald-600">{value[1]}</span> <span className="text-primary">{value[1]}</span>
<input <input
type="range" type="range"
min={value[0] + 1} min={value[0] + 1}
max={max} max={max}
value={value[1]} value={value[1]}
onChange={handleMaxChange} onChange={handleMaxChange}
className="w-full h-2 bg-emerald-200 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-opacity-50" className="w-full h-2 bg-primary/20 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50"
/> />
</div> </div>
</div> </div>

View File

@ -1,12 +1,12 @@
// Composant StatCard pour afficher une statistique // Composant StatCard pour afficher une statistique
const StatCard = ({ title, value, icon, color = 'blue' }) => ( const StatCard = ({ title, value, icon, color = 'blue' }) => (
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100"> <div className="bg-neutral p-6 rounded-md shadow-sm border border-gray-100">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<h3 className="text-gray-500 text-sm font-medium">{title}</h3> <h3 className="font-headline text-gray-500 text-sm font-medium">{title}</h3>
<p className="text-2xl font-semibold mt-1">{value}</p> <p className="text-2xl font-semibold mt-1">{value}</p>
</div> </div>
<div className={`p-3 rounded-full bg-${color}-100`}>{icon}</div> <div className={`p-3 rounded bg-${color}-100`}>{icon}</div>
</div> </div>
</div> </div>
); );

View File

@ -230,7 +230,7 @@ export default function CompetenciesList({
/> />
{/* Zone filtres centrée et plus large */} {/* Zone filtres centrée et plus large */}
<div className="mb-6 flex justify-center"> <div className="mb-6 flex justify-center">
<div className="w-full max-w-3xl flex flex-col gap-4 p-6 rounded-lg border border-emerald-200 shadow-sm bg-white/80 backdrop-blur-sm"> <div className="w-full max-w-3xl flex flex-col gap-4 p-6 rounded-lg border border-primary/20 shadow-sm bg-white/80 backdrop-blur-sm">
<div className="flex flex-col sm:flex-row sm:items-start gap-8"> <div className="flex flex-col sm:flex-row sm:items-start gap-8">
{/* Select cycle */} {/* Select cycle */}
<div className="flex-1 min-w-[220px]"> <div className="flex-1 min-w-[220px]">
@ -285,7 +285,7 @@ export default function CompetenciesList({
className={`px-6 py-2 rounded-md shadow ${ className={`px-6 py-2 rounded-md shadow ${
!hasSelection !hasSelection
? 'bg-gray-300 text-gray-500 cursor-not-allowed' ? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-primary text-white hover:bg-primary'
}`} }`}
onClick={handleSubmit} onClick={handleSubmit}
primary primary
@ -295,11 +295,11 @@ export default function CompetenciesList({
</div> </div>
{/* Légende en dessous du bouton, alignée à gauche */} {/* Légende en dessous du bouton, alignée à gauche */}
<div className="flex flex-row items-center gap-4 mb-4"> <div className="flex flex-row items-center gap-4 mb-4">
<span className="flex items-center gap-2 text-emerald-700 font-bold"> <span className="flex items-center gap-2 text-secondary font-bold">
<CheckCircle className="w-4 h-4 text-emerald-500" /> <CheckCircle className="w-4 h-4 text-primary" />
Compétence requise Compétence requise
</span> </span>
<span className="flex items-center gap-2 text-emerald-600 font-semibold"> <span className="flex items-center gap-2 text-primary font-semibold">
Compétence sélectionnée ou créée Compétence sélectionnée ou créée
</span> </span>
<span className="flex items-center gap-2 text-gray-500"> <span className="flex items-center gap-2 text-gray-500">

Some files were not shown because too many files have changed in this diff Show More