mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
refactor: Changement des IconTextInput en TextInput, modification du composant step
This commit is contained in:
@ -319,8 +319,8 @@ class ChildrenListView(APIView):
|
|||||||
"""
|
"""
|
||||||
# Récupération des élèves d'un parent
|
# Récupération des élèves d'un parent
|
||||||
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
|
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
|
||||||
def get(self, request, _idProfile):
|
def get(self, request, _id):
|
||||||
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=_idProfile)
|
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=_id)
|
||||||
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
||||||
return JsonResponse(students_serializer.data, safe=False)
|
return JsonResponse(students_serializer.data, safe=False)
|
||||||
|
|
||||||
|
|||||||
@ -1,31 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||||
import { useSearchParams, redirect, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||||
import { FE_PARENTS_HOME_URL} from '@/utils/Url';
|
import { FE_PARENTS_HOME_URL} from '@/utils/Url';
|
||||||
import { mockStudent } from '@/data/mockStudent';
|
import { editRegisterForm} from '@/app/lib/subscriptionAction';
|
||||||
import { fetchLastGuardian, fetchRegisterForm } from '@/app/lib/subscriptionAction';
|
|
||||||
|
|
||||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const idProfil = searchParams.get('id');
|
const idProfil = searchParams.get('id');
|
||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [initialData, setInitialData] = useState(null);
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const [currentProfil, setCurrentProfil] = useState("");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (data) => {
|
const handleSubmit = async (data) => {
|
||||||
if (useFakeData) {
|
|
||||||
console.log('Fake submit:', data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
const result = await editRegisterForm(studentId, data, csrfToken);
|
||||||
console.log('Success:', result);
|
console.log('Success:', result);
|
||||||
@ -41,7 +29,6 @@ export default function Page() {
|
|||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
cancelUrl={FE_PARENTS_HOME_URL}
|
cancelUrl={FE_PARENTS_HOME_URL}
|
||||||
isLoading={isLoading}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -17,9 +17,10 @@ export default function Layout({
|
|||||||
const router = useRouter(); // Définition de router
|
const router = useRouter(); // Définition de router
|
||||||
const [messages, setMessages] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
setUserId(userId)
|
setUserId(userId)
|
||||||
fetchMessages(userId)
|
fetchMessages(userId)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -30,8 +31,15 @@ export default function Layout({
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching data:', error);
|
console.error('Error fetching data:', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [userId]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { GraduationCap } from 'lucide-react';
|
|||||||
const ClasseDetails = ({ classe }) => {
|
const ClasseDetails = ({ classe }) => {
|
||||||
if (!classe) return null;
|
if (!classe) return null;
|
||||||
|
|
||||||
const nombreElevesInscrits = classe.eleves.length;
|
const nombreElevesInscrits = classe?.eleves?.length||0;
|
||||||
const capaciteTotale = classe.number_of_students;
|
const capaciteTotale = classe.number_of_students;
|
||||||
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import Button from '@/components/Button';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||||
import Navigation from '@/components/Navigation';
|
import SectionTitle from '@/components/SectionTitle';
|
||||||
import StepTitle from '@/components/StepTitle';
|
import ProgressStep from '@/components/ProgressStep';
|
||||||
|
|
||||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep }) => {
|
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@ -38,7 +38,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
5: 'Récapitulatif'
|
5: 'Récapitulatif'
|
||||||
};
|
};
|
||||||
|
|
||||||
const steps = ['1', '2', '3', '4', 'Récap'];
|
const steps = ['Élève', 'Responsable', 'Inscription', 'Scolarité', 'Récap'];
|
||||||
|
|
||||||
const isStep1Valid = formData.studentLastName && formData.studentFirstName;
|
const isStep1Valid = formData.studentLastName && formData.studentFirstName;
|
||||||
const isStep2Valid = (
|
const isStep2Valid = (
|
||||||
@ -224,12 +224,12 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 mt-6">
|
<div className="space-y-4 mt-6">
|
||||||
<Navigation
|
<ProgressStep
|
||||||
steps={steps}
|
steps={steps}
|
||||||
step={step}
|
stepTitles={stepTitles}
|
||||||
|
currentStep={step}
|
||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
isStepValid={isStepValid}
|
isStepValid={isStepValid}
|
||||||
stepTitles={stepTitles}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
@ -257,15 +257,6 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
|
|
||||||
{step === 2 && (
|
{step === 2 && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<InputTextIcon
|
|
||||||
name="guardianPhone"
|
|
||||||
type="tel"
|
|
||||||
IconItem={Phone}
|
|
||||||
placeholder="Numéro de téléphone (optionnel)"
|
|
||||||
value={formData.guardianPhone}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="w-full mt-4"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col space-y-4 mt-6">
|
<div className="flex flex-col space-y-4 mt-6">
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
@ -291,6 +282,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{formData.responsableType === 'new' && (
|
{formData.responsableType === 'new' && (
|
||||||
|
<>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
name="guardianEmail"
|
name="guardianEmail"
|
||||||
type="email"
|
type="email"
|
||||||
@ -300,6 +292,16 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
/>
|
/>
|
||||||
|
<InputTextIcon
|
||||||
|
name="guardianPhone"
|
||||||
|
type="tel"
|
||||||
|
IconItem={Phone}
|
||||||
|
placeholder="Numéro de téléphone (optionnel)"
|
||||||
|
value={formData.guardianPhone}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full mt-4"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formData.responsableType === 'existing' && (
|
{formData.responsableType === 'existing' && (
|
||||||
@ -364,7 +366,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
handleFeeSelection={handleRegistrationFeeSelection}
|
handleFeeSelection={handleRegistrationFeeSelection}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<StepTitle title='Réductions' />
|
<SectionTitle title='Réductions' />
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{registrationDiscounts.length > 0 ? (
|
{registrationDiscounts.length > 0 ? (
|
||||||
<DiscountsSection
|
<DiscountsSection
|
||||||
@ -381,7 +383,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<StepTitle title='Montant total' />
|
<SectionTitle title='Montant total' />
|
||||||
<Table
|
<Table
|
||||||
data={[ {id: 1}]}
|
data={[ {id: 1}]}
|
||||||
columns={[
|
columns={[
|
||||||
@ -419,7 +421,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
handleFeeSelection={handleTuitionFeeSelection}
|
handleFeeSelection={handleTuitionFeeSelection}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<StepTitle title='Réductions' />
|
<SectionTitle title='Réductions' />
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{tuitionDiscounts.length > 0 ? (
|
{tuitionDiscounts.length > 0 ? (
|
||||||
<DiscountsSection
|
<DiscountsSection
|
||||||
@ -436,7 +438,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<StepTitle title='Montant total' />
|
<SectionTitle title='Montant total' />
|
||||||
<Table
|
<Table
|
||||||
data={[ {id: 1}]}
|
data={[ {id: 1}]}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@ -39,7 +39,18 @@ export default function InscriptionFormShared({
|
|||||||
}) {
|
}) {
|
||||||
// États pour gérer les données du formulaire
|
// États pour gérer les données du formulaire
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [formData, setFormData] = useState({});
|
const [formData, setFormData] = useState({
|
||||||
|
id: '',
|
||||||
|
last_name: '',
|
||||||
|
first_name: '',
|
||||||
|
address: '',
|
||||||
|
birth_date: '',
|
||||||
|
birth_place: '',
|
||||||
|
birth_postal_code: '',
|
||||||
|
nationality: '',
|
||||||
|
attending_physician: '',
|
||||||
|
level: ''
|
||||||
|
});
|
||||||
|
|
||||||
const [guardians, setGuardians] = useState([]);
|
const [guardians, setGuardians] = useState([]);
|
||||||
|
|
||||||
|
|||||||
@ -5,9 +5,9 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
|
|||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
<Dialog.Content className="fixed inset-0 flex items-center justify-center">
|
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle" style={{ width: '600px', maxWidth: '90%' }}>
|
<div className="inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 min-w-[500px] w-max m-12 h-max">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||||
{title}
|
{title}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
@ -23,7 +23,7 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
|
|||||||
</button>
|
</button>
|
||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="w-full">
|
||||||
<ContentComponent />
|
<ContentComponent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import StepTitle from '@/components/StepTitle';
|
|
||||||
|
|
||||||
const Navigation = ({ steps, step, setStep, isStepValid, stepTitles }) => {
|
|
||||||
return (
|
|
||||||
<div className="relative mb-4">
|
|
||||||
<div className="relative flex justify-between">
|
|
||||||
{steps.map((stepLabel, index) => {
|
|
||||||
const isCurrentStep = step === index + 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={index} className="flex flex-col items-center">
|
|
||||||
<div className={`mb-4 ${isCurrentStep ? 'text-gray-500 font-extrabold' : 'text-gray-500'}`}>
|
|
||||||
{stepLabel}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`w-4 h-4 rounded-full mb-4 ${isStepValid(index + 1) ? 'bg-emerald-500' : 'bg-yellow-300'} cursor-pointer`}
|
|
||||||
onClick={() => setStep(index + 1)}
|
|
||||||
style={{ transform: 'translateY(-50%)' }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<StepTitle title={stepTitles[step]} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Navigation;
|
|
||||||
157
Front-End/src/components/ProgressStep.js
Normal file
157
Front-End/src/components/ProgressStep.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
||||||
|
<div className={`
|
||||||
|
w-8 h-8 rounded-full
|
||||||
|
flex items-center justify-center
|
||||||
|
text-sm font-semibold
|
||||||
|
${isCompleted
|
||||||
|
? 'bg-emerald-600 text-white'
|
||||||
|
: isActive
|
||||||
|
? 'bg-emerald-600 text-white'
|
||||||
|
: 'bg-gray-200 text-gray-600'
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
{isCompleted ? (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
number
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
||||||
|
<span className={`
|
||||||
|
text-xs font-medium w-20 text-center block break-words
|
||||||
|
${isActive ? 'text-emerald-600' : 'text-gray-500'}
|
||||||
|
`}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpacerStep = ({ isCompleted }) => {
|
||||||
|
return (
|
||||||
|
<div className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Dots = () => {
|
||||||
|
return (
|
||||||
|
<div className="text-gray-500 relative flex items-center mx-4">
|
||||||
|
<span>...</span>
|
||||||
|
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
||||||
|
<span className="text-xs font-medium w-20 text-center block">...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProgressStep = ({ steps, stepTitles, currentStep, setStep, isStepValid }) => {
|
||||||
|
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||||
|
const [visibleSteps, setVisibleSteps] = useState(steps);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => setWindowWidth(window.innerWidth);
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const calculateVisibleSteps = () => {
|
||||||
|
const minWidth = 150; // Largeur minimale estimée par étape
|
||||||
|
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
||||||
|
|
||||||
|
if (maxVisibleSteps >= steps.length) {
|
||||||
|
setVisibleSteps(steps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxVisibleSteps < 4) {
|
||||||
|
// Garder seulement première, dernière et courante
|
||||||
|
let filtered = [steps[0]];
|
||||||
|
if (currentStep > 1 && currentStep < steps.length) {
|
||||||
|
filtered.push('...');
|
||||||
|
filtered.push(steps[currentStep - 1]);
|
||||||
|
}
|
||||||
|
if (currentStep < steps.length) {
|
||||||
|
filtered.push('...');
|
||||||
|
}
|
||||||
|
filtered.push(steps[steps.length - 1]);
|
||||||
|
setVisibleSteps(filtered);
|
||||||
|
} else {
|
||||||
|
// Garder première, dernière, courante et quelques étapes adjacentes
|
||||||
|
let filtered = [steps[0]];
|
||||||
|
if (currentStep > 2) filtered.push('...');
|
||||||
|
if (currentStep > 1 && currentStep < steps.length) {
|
||||||
|
filtered.push(steps[currentStep - 1]);
|
||||||
|
}
|
||||||
|
if (currentStep < steps.length - 1) filtered.push('...');
|
||||||
|
filtered.push(steps[steps.length - 1]);
|
||||||
|
setVisibleSteps(filtered);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
calculateVisibleSteps();
|
||||||
|
}, [windowWidth, currentStep, steps]);
|
||||||
|
|
||||||
|
const handleStepClick = (stepIndex) => {
|
||||||
|
// Vérifie si on peut naviguer vers l'étape (toutes les étapes précédentes doivent être valides)
|
||||||
|
const canNavigate = Array.from({ length: stepIndex }, (_, i) => i + 1)
|
||||||
|
.every(step => isStepValid(step));
|
||||||
|
|
||||||
|
if (canNavigate) {
|
||||||
|
setStep(stepIndex + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full py-6">
|
||||||
|
<div className="flex items-center min-h-[100px]">
|
||||||
|
{visibleSteps.map((step, index) => {
|
||||||
|
if (step === '...') {
|
||||||
|
return (
|
||||||
|
<div key={`dots-${index}`} className="flex-1 flex items-center justify-center">
|
||||||
|
<Dots />
|
||||||
|
{index !== visibleSteps.length - 1 && <SpacerStep isCompleted={false} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalIndex = steps.indexOf(step);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`
|
||||||
|
flex-1 relative
|
||||||
|
${Array.from({ length: originalIndex + 1 }, (_, i) => i + 1).every(s => isStepValid(s)) ? 'cursor-pointer' : 'cursor-not-allowed'}
|
||||||
|
`}
|
||||||
|
onClick={() => handleStepClick(originalIndex)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="w-full flex items-center">
|
||||||
|
<Step
|
||||||
|
number={originalIndex + 1}
|
||||||
|
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
||||||
|
isActive={currentStep === originalIndex + 1}
|
||||||
|
isCompleted={currentStep > originalIndex + 1}
|
||||||
|
isValid={isStepValid(originalIndex + 1)}
|
||||||
|
/>
|
||||||
|
{index !== visibleSteps.length - 1 && (
|
||||||
|
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProgressStep;
|
||||||
@ -14,6 +14,9 @@ const ProtectedRoute = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [userId, router]);
|
}, [userId, router]);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
// Afficher les enfants seulement si l'utilisateur est connecté
|
// Afficher les enfants seulement si l'utilisateur est connecté
|
||||||
return userId ? children : null;
|
return userId ? children : null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const StepTitle = ({ title }) => {
|
const SectionTitle = ({ title }) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative mb-4">
|
<div className="relative mb-4">
|
||||||
<div className="absolute inset-0 flex items-center">
|
<div className="absolute inset-0 flex items-center">
|
||||||
@ -13,4 +13,4 @@ const StepTitle = ({ title }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StepTitle;
|
export default SectionTitle;
|
||||||
@ -8,6 +8,7 @@ import { createProfile, updateProfile } from '@/app/lib/authAction';
|
|||||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
SPECIALITY: 'speciality',
|
SPECIALITY: 'speciality',
|
||||||
@ -203,7 +204,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
|
|||||||
switch (column) {
|
switch (column) {
|
||||||
case 'NOM':
|
case 'NOM':
|
||||||
return (
|
return (
|
||||||
<InputTextIcon
|
<InputText
|
||||||
name="last_name"
|
name="last_name"
|
||||||
value={currentData.last_name}
|
value={currentData.last_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -213,7 +214,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
|
|||||||
);
|
);
|
||||||
case 'PRENOM':
|
case 'PRENOM':
|
||||||
return (
|
return (
|
||||||
<InputTextIcon
|
<InputText
|
||||||
name="first_name"
|
name="first_name"
|
||||||
value={currentData.first_name}
|
value={currentData.first_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -223,7 +224,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
|
|||||||
);
|
);
|
||||||
case 'EMAIL':
|
case 'EMAIL':
|
||||||
return (
|
return (
|
||||||
<InputTextIcon
|
<InputText
|
||||||
name="email"
|
name="email"
|
||||||
value={currentData.email}
|
value={currentData.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|||||||
@ -35,14 +35,14 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
|
|||||||
|
|
||||||
setSelectedLevel(niveau);
|
setSelectedLevel(niveau);
|
||||||
|
|
||||||
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === niveau);
|
const currentPlanning = selectedClass.plannings_read?.find(planning => planning.niveau === niveau);
|
||||||
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||||
}
|
}
|
||||||
}, [selectedClass, niveauxLabels]);
|
}, [selectedClass, niveauxLabels]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedClass && selectedLevel) {
|
if (selectedClass && selectedLevel) {
|
||||||
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel);
|
const currentPlanning = selectedClass.plannings_read?.find(planning => planning.niveau === selectedLevel);
|
||||||
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||||
}
|
}
|
||||||
}, [selectedClass, selectedLevel]);
|
}, [selectedClass, selectedLevel]);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Plus, Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
|
import { Plus, Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
|
||||||
const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedDiscounts, handleDiscountSelection }) => {
|
const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedDiscounts, handleDiscountSelection }) => {
|
||||||
const [editingDiscount, setEditingDiscount] = useState(null);
|
const [editingDiscount, setEditingDiscount] = useState(null);
|
||||||
@ -103,7 +103,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
|
|||||||
|
|
||||||
const renderInputField = (field, value, onChange, placeholder) => (
|
const renderInputField = (field, value, onChange, placeholder) => (
|
||||||
<div>
|
<div>
|
||||||
<InputTextIcon
|
<InputText
|
||||||
name={field}
|
name={field}
|
||||||
type={field === 'amount' ? 'number' : 'text'}
|
type={field === 'amount' ? 'number' : 'text'}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Plus, Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react';
|
import { Plus, Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
|
||||||
const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedFees, handleFeeSelection }) => {
|
const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedFees, handleFeeSelection }) => {
|
||||||
const [editingFee, setEditingFee] = useState(null);
|
const [editingFee, setEditingFee] = useState(null);
|
||||||
@ -112,7 +112,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
|
|||||||
|
|
||||||
const renderInputField = (field, value, onChange, placeholder) => (
|
const renderInputField = (field, value, onChange, placeholder) => (
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
<InputTextIcon
|
<InputText
|
||||||
name={field}
|
name={field}
|
||||||
type={field === 'base_amount' ? 'number' : 'text'}
|
type={field === 'base_amount' ? 'number' : 'text'}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@ -176,17 +176,21 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
const groupSpecialitiesBySubject = (teachers) => {
|
const groupSpecialitiesBySubject = (teachers) => {
|
||||||
const groupedSpecialities = {};
|
const groupedSpecialities = {};
|
||||||
|
|
||||||
|
if (!teachers) return [];
|
||||||
|
|
||||||
teachers.forEach(teacher => {
|
teachers.forEach(teacher => {
|
||||||
teacher.specialites.forEach(specialite => {
|
if (teacher && teacher.specialites) {
|
||||||
if (!groupedSpecialities[specialite.id]) {
|
teacher.specialites.forEach(specialite => {
|
||||||
groupedSpecialities[specialite.id] = {
|
if (!groupedSpecialities[specialite.id]) {
|
||||||
...specialite,
|
groupedSpecialities[specialite.id] = {
|
||||||
teachers: [`${teacher.nom} ${teacher.prenom}`],
|
...specialite,
|
||||||
};
|
teachers: [`${teacher.nom} ${teacher.prenom}`],
|
||||||
} else {
|
};
|
||||||
groupedSpecialities[specialite.id].teachers.push(`${teacher.nom} ${teacher.prenom}`);
|
} else {
|
||||||
}
|
groupedSpecialities[specialite.id].teachers.push(`${teacher.nom} ${teacher.prenom}`);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(groupedSpecialities);
|
return Object.values(groupedSpecialities);
|
||||||
|
|||||||
Reference in New Issue
Block a user