mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 16:03:21 +00:00
chore: Initial Commit
feat: Gestion des inscriptions [#1] feat(frontend): Création des vues pour le paramétrage de l'école [#2] feat: Gestion du login [#6] fix: Correction lors de la migration des modèle [#8] feat: Révision du menu principal [#9] feat: Ajout d'un footer [#10] feat: Création des dockers compose pour les environnements de développement et de production [#12] doc(ci): Mise en place de Husky et d'un suivi de version automatique [#14]
This commit is contained in:
113
Front-End/src/app/[locale]/parents/editInscription/page.js
Normal file
113
Front-End/src/app/[locale]/parents/editInscription/page.js
Normal file
@ -0,0 +1,113 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||
import { useSearchParams, redirect, useRouter } from 'next/navigation';
|
||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||
import { FR_PARENTS_HOME_URL,
|
||||
BK_GESTIONINSCRIPTION_ELEVE_URL,
|
||||
BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL,
|
||||
BK_GESTIONINSCRIPTION_RECUPEREDERNIER_RESPONSABLE_URL } from '@/utils/Url';
|
||||
import { mockStudent } from '@/data/mockStudent';
|
||||
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const idProfil = searchParams.get('id');
|
||||
const idEleve = searchParams.get('idEleve');
|
||||
const router = useRouter();
|
||||
|
||||
const [initialData, setInitialData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const csrfToken = useCsrfToken();
|
||||
const [currentProfil, setCurrentProfil] = useState("");
|
||||
const [lastIdResponsable, setLastIdResponsable] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (!idEleve || !idProfil) {
|
||||
console.error('Missing idEleve or idProfil');
|
||||
return;
|
||||
}
|
||||
|
||||
if (useFakeData) {
|
||||
setInitialData(mockStudent);
|
||||
setLastIdResponsable(999);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
Promise.all([
|
||||
// Fetch eleve data
|
||||
fetch(`${BK_GESTIONINSCRIPTION_ELEVE_URL}/${idEleve}`),
|
||||
// Fetch last responsable ID
|
||||
fetch(BK_GESTIONINSCRIPTION_RECUPEREDERNIER_RESPONSABLE_URL)
|
||||
])
|
||||
.then(async ([eleveResponse, responsableResponse]) => {
|
||||
const eleveData = await eleveResponse.json();
|
||||
const responsableData = await responsableResponse.json();
|
||||
|
||||
const formattedData = {
|
||||
id: eleveData.id,
|
||||
nom: eleveData.nom,
|
||||
prenom: eleveData.prenom,
|
||||
adresse: eleveData.adresse,
|
||||
dateNaissance: eleveData.dateNaissance,
|
||||
lieuNaissance: eleveData.lieuNaissance,
|
||||
codePostalNaissance: eleveData.codePostalNaissance,
|
||||
nationalite: eleveData.nationalite,
|
||||
medecinTraitant: eleveData.medecinTraitant,
|
||||
niveau: eleveData.niveau,
|
||||
responsables: eleveData.responsables || []
|
||||
};
|
||||
|
||||
setInitialData(formattedData);
|
||||
setLastIdResponsable(responsableData.lastid);
|
||||
|
||||
let profils = eleveData.profils;
|
||||
const currentProf = profils.find(profil => profil.id === idProfil);
|
||||
if (currentProf) {
|
||||
setCurrentProfil(currentProf);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [idEleve, idProfil]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
if (useFakeData) {
|
||||
console.log('Fake submit:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL}/${idEleve}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Success:', result);
|
||||
router.push(FR_PARENTS_HOME_URL);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InscriptionFormShared
|
||||
initialData={initialData}
|
||||
csrfToken={csrfToken}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FR_PARENTS_HOME_URL}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
92
Front-End/src/app/[locale]/parents/layout.js
Normal file
92
Front-End/src/app/[locale]/parents/layout.js
Normal file
@ -0,0 +1,92 @@
|
||||
'use client'
|
||||
// src/components/Layout.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
import { useRouter } from 'next/navigation'; // Ajout de l'importation
|
||||
import { Bell, User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home
|
||||
import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo
|
||||
import { FR_PARENTS_HOME_URL,FR_PARENTS_MESSAGERIE_URL,FR_PARENTS_SETTINGS_URL, BK_GESTIONINSCRIPTION_MESSAGES_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}) {
|
||||
|
||||
const router = useRouter(); // Définition de router
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setUserId(userId);
|
||||
fetch(`${BK_GESTIONINSCRIPTION_MESSAGES_URL}/${userId}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setMessages(data);
|
||||
}
|
||||
console.log('Success :', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
});
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||
{/* Entête */}
|
||||
<header className="bg-white border-b border-gray-200 px-8 py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Logo className="h-8 w-8" /> {/* Utilisation du composant Logo */}
|
||||
|
||||
<div className="text-xl font-semibold">Accueil</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
className="p-2 rounded-full hover:bg-gray-200"
|
||||
onClick={() => { router.push(FR_PARENTS_HOME_URL); }} // Utilisation de router pour revenir à l'accueil parent
|
||||
>
|
||||
<Home />
|
||||
</button>
|
||||
|
||||
|
||||
<div className="relative">
|
||||
<button
|
||||
className="p-2 rounded-full hover:bg-gray-200"
|
||||
onClick={() => { router.push(FR_PARENTS_MESSAGERIE_URL); }} // Utilisation de router
|
||||
>
|
||||
<MessageSquare />
|
||||
|
||||
</button>
|
||||
{messages.length > 0 && (
|
||||
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-emerald-600"></span>
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenu
|
||||
buttonContent={<User />}
|
||||
items={[
|
||||
{ label: 'Se déconnecter', icon: LogOut, onClick: () => {} },
|
||||
{ label: 'Settings', icon: Settings , onClick: () => { router.push(FR_PARENTS_SETTINGS_URL); } }
|
||||
]}
|
||||
buttonClassName="p-2 rounded-full hover:bg-gray-200"
|
||||
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg"
|
||||
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content */}
|
||||
<div className="pt-20 p-8 flex-1"> {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
106
Front-End/src/app/[locale]/parents/messagerie/page.js
Normal file
106
Front-End/src/app/[locale]/parents/messagerie/page.js
Normal file
@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { SendHorizontal } from 'lucide-react';
|
||||
|
||||
const contacts = [
|
||||
{ id: 1, name: 'Facturation', profilePic: 'https://i.pravatar.cc/32' },
|
||||
{ id: 2, name: 'Enseignant 1', profilePic: 'https://i.pravatar.cc/32' },
|
||||
{ id: 3, name: 'Contact', profilePic: 'https://i.pravatar.cc/32' },
|
||||
];
|
||||
|
||||
export default function MessageriePage() {
|
||||
const [selectedContact, setSelectedContact] = useState(null);
|
||||
const [messages, setMessages] = useState({});
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (newMessage.trim() && selectedContact) {
|
||||
const contactMessages = messages[selectedContact.id] || [];
|
||||
setMessages({
|
||||
...messages,
|
||||
[selectedContact.id]: [...contactMessages, { id: contactMessages.length + 1, text: newMessage, date: new Date() }],
|
||||
});
|
||||
setNewMessage('');
|
||||
simulateContactResponse(selectedContact.id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const simulateContactResponse = (contactId) => {
|
||||
setTimeout(() => {
|
||||
setMessages((prevMessages) => {
|
||||
const contactMessages = prevMessages[contactId] || [];
|
||||
return {
|
||||
...prevMessages,
|
||||
[contactId]: [...contactMessages, { id: contactMessages.length + 2, text: 'Réponse automatique', isResponse: true, date: new Date() }],
|
||||
};
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex" style={{ height: 'calc(100vh - 128px )' }}> {/* Utilisation de calc pour soustraire la hauteur de l'entête */}
|
||||
<div className="w-1/4 border-r border-gray-200 p-4 overflow-y-auto h-full ">
|
||||
{contacts.map((contact) => (
|
||||
<div
|
||||
key={contact.id}
|
||||
className={`p-2 cursor-pointer ${selectedContact?.id === contact.id ? 'bg-gray-200' : ''}`}
|
||||
onClick={() => setSelectedContact(contact)}
|
||||
>
|
||||
<img src={contact.profilePic} alt={`${contact.name}'s profile`} className="w-8 h-8 rounded-full inline-block mr-2" />
|
||||
{contact.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto p-4 h-full">
|
||||
{selectedContact && (messages[selectedContact.id] || []).map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
|
||||
style={{ borderRadius: message.isResponse ? '20px 20px 0 20px' : '20px 20px 20px 0', minWidth: '25%' }}
|
||||
>
|
||||
<div className="flex items-center mb-1">
|
||||
<img src={selectedContact.profilePic} alt={`${selectedContact.name}'s profile`} className="w-8 h-8 rounded-full inline-block mr-2" />
|
||||
<span className="text-xs text-gray-600">{selectedContact.name}</span>
|
||||
<span className="text-xs text-gray-400 ml-2">{new Date(message.date).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
{message.text}
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="p-4 border-t border-gray-200 flex">
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
className="w-full p-2 border border-gray-300 rounded"
|
||||
placeholder="Écrire un message..."
|
||||
onKeyDown={handleKeyPress}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
className="p-2 bg-emerald-500 text-white rounded mr-2"
|
||||
>
|
||||
<SendHorizontal />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
90
Front-End/src/app/[locale]/parents/page.js
Normal file
90
Front-End/src/app/[locale]/parents/page.js
Normal file
@ -0,0 +1,90 @@
|
||||
'use client'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Table from '@/components/Table';
|
||||
import { Edit } from 'lucide-react';
|
||||
import StatusLabel from '@/components/StatusLabel';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { BK_GESTIONINSCRIPTION_ENFANTS_URL , FR_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
||||
|
||||
export default function ParentHomePage() {
|
||||
const [actions, setActions] = useState([]);
|
||||
const [children, setChildren] = useState([]);
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId) return;
|
||||
|
||||
const fetchActions = async () => {
|
||||
const response = await fetch('/api/actions');
|
||||
const data = await response.json();
|
||||
setActions(data);
|
||||
};
|
||||
|
||||
const fetchEleves = async () => {
|
||||
const response = await fetch(`${BK_GESTIONINSCRIPTION_ENFANTS_URL}/${userId}`);
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
setChildren(data);
|
||||
};
|
||||
|
||||
fetchEleves();
|
||||
}, [userId]);
|
||||
|
||||
function handleEdit(eleveId) {
|
||||
// Logique pour éditer le dossier de l'élève
|
||||
console.log(`Edit dossier for eleve id: ${eleveId}`);
|
||||
router.push(`${FR_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&idEleve=${eleveId}`);
|
||||
}
|
||||
const actionColumns = [
|
||||
{ name: 'Action', transform: (row) => row.action },
|
||||
];
|
||||
|
||||
const getShadowColor = (etat) => {
|
||||
switch (etat) {
|
||||
case 1:
|
||||
return 'shadow-blue-500'; // Couleur d'ombre plus visible
|
||||
case 2:
|
||||
return 'shadow-orange-500'; // Couleur d'ombre plus visible
|
||||
case 3:
|
||||
return 'shadow-purple-500'; // Couleur d'ombre plus visible
|
||||
case 4:
|
||||
return 'shadow-red-500'; // Couleur d'ombre plus visible
|
||||
case 5:
|
||||
return 'shadow-green-500'; // Couleur d'ombre plus visible
|
||||
case 6:
|
||||
return 'shadow-red-500'; // Couleur d'ombre plus visible
|
||||
default:
|
||||
return 'shadow-green-500'; // Couleur d'ombre plus visible
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">Dernières actions à effectuer</h2>
|
||||
<Table data={actions} columns={actionColumns} itemsPerPage={5} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">Enfants</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{children.map((child) => (
|
||||
<div key={child.eleve.id} className={`border p-4 rounded shadow ${getShadowColor(child.etat)}`}>
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold">{child.eleve.nom} {child.eleve.prenom}</h3>
|
||||
<Edit className="cursor-pointer" onClick={() => handleEdit(child.eleve.id)} />
|
||||
</div>
|
||||
<StatusLabel etat={child.etat } showDropdown={false}/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
74
Front-End/src/app/[locale]/parents/settings/page.js
Normal file
74
Front-End/src/app/[locale]/parents/settings/page.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react';
|
||||
import Button from '@/components/Button';
|
||||
import InputText from '@/components/InputText';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
|
||||
const handleEmailChange = (e) => {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
|
||||
const handleConfirmPasswordChange = (e) => {
|
||||
setConfirmPassword(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (password !== confirmPassword) {
|
||||
alert('Les mots de passe ne correspondent pas');
|
||||
return;
|
||||
}
|
||||
// Logique pour mettre à jour l'email et le mot de passe
|
||||
console.log('Email:', email);
|
||||
console.log('Password:', password);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl mb-4">Paramètres du compte</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<InputText
|
||||
type="email"
|
||||
id="email"
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
type="password"
|
||||
id="password"
|
||||
label="Nouveau mot de passe"
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
label="Confirmer le mot de passe"
|
||||
value={confirmPassword}
|
||||
onChange={handleConfirmPasswordChange}
|
||||
required
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
type="submit"
|
||||
primary
|
||||
text={" Mettre à jour"}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user