refactor: adaptation mobile

This commit is contained in:
Luc SORIGNET
2025-03-01 14:54:25 +01:00
parent d62be6b309
commit 4b8f85e68d
6 changed files with 237 additions and 89 deletions

View File

@ -12,10 +12,12 @@ import {
Calendar, Calendar,
Settings, Settings,
FileText, FileText,
LogOut LogOut,
Menu,
X
} from 'lucide-react'; } from 'lucide-react';
import DropdownMenu from '@/components/DropdownMenu'; import DropdownMenu from '@/components/DropdownMenu';
import Logo from '@/components/Logo';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import { import {
FE_ADMIN_HOME_URL, FE_ADMIN_HOME_URL,
@ -31,11 +33,13 @@ import { useSession } from 'next-auth/react';
import { fetchEstablishment } from '@/app/actions/schoolAction'; import { fetchEstablishment } from '@/app/actions/schoolAction';
import ProtectedRoute from '@/components/ProtectedRoute'; import ProtectedRoute from '@/components/ProtectedRoute';
import { getGravatarUrl } from '@/utils/gravatar'; import { getGravatarUrl } from '@/utils/gravatar';
import Footer from '@/components/Footer';
export default function Layout({ export default function Layout({
children, children,
}) { }) {
const t = useTranslations('sidebar'); const t = useTranslations('sidebar');
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const sidebarItems = { const sidebarItems = {
"admin": { "id": "admin", "name": t('dashboard'), "url": FE_ADMIN_HOME_URL, "icon": Home }, "admin": { "id": "admin", "name": t('dashboard'), "url": FE_ADMIN_HOME_URL, "icon": Home },
@ -58,7 +62,7 @@ export default function Layout({
const headerTitle = sidebarItems[currentPage]?.name || t('dashboard'); const headerTitle = sidebarItems[currentPage]?.name || t('dashboard');
const softwareName = "N3WT School"; const softwareName = "N3WT School";
const softwareVersion = `v${process.env.NEXT_PUBLIC_APP_VERSION}`; const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
const handleDisconnect = () => { const handleDisconnect = () => {
setIsPopupVisible(true); setIsPopupVisible(true);
@ -77,6 +81,15 @@ export default function Layout({
}, },
]; ];
const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};
useEffect(() => {
// Fermer la sidebar quand on change de page sur mobile
setIsSidebarOpen(false);
}, [pathname]);
useEffect(() => { useEffect(() => {
setIsLoading(true); setIsLoading(true);
fetchEstablishment() fetchEstablishment()
@ -101,12 +114,43 @@ export default function Layout({
return ( return (
<ProtectedRoute> <ProtectedRoute>
{!isLoading && ( {!isLoading && (
<div className="flex min-h-screen bg-gray-50"> <div className="flex min-h-screen bg-gray-50 relative">
<Sidebar establishment={establishment} currentPage={currentPage} items={Object.values(sidebarItems)} className="h-full" /> {/* Sidebar avec hauteur forcée */}
<div className="flex flex-col flex-1"> <div
{/* Header - h-16 = 64px */} className={`md:block ${isSidebarOpen ? 'block' : 'hidden'} fixed md:relative inset-y-0 left-0 z-30 h-full`}
<header className="h-16 bg-white border-b border-gray-200 px-8 py-4 flex items-center justify-between z-9"> style={{ height: '100vh' }} // Force la hauteur à 100% de la hauteur de la vue
<div className="text-xl font-semibold">{headerTitle}</div> >
<Sidebar
establishment={establishment}
currentPage={currentPage}
items={Object.values(sidebarItems)}
onCloseMobile={toggleSidebar}
/>
</div>
{/* Overlay pour fermer la sidebar en cliquant à l'extérieur sur mobile */}
{isSidebarOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-20 md:hidden"
onClick={toggleSidebar}
/>
)}
<div className="flex-1 flex flex-col">
{/* Header responsive */}
<header className="h-16 bg-white border-b border-gray-200 px-4 md:px-8 py-4 flex items-center justify-between z-10">
<div className="flex items-center">
<button
className="mr-4 md:hidden text-gray-600 hover:text-gray-900"
onClick={toggleSidebar}
aria-label="Toggle menu"
>
{isSidebarOpen ? <X size={24} /> : <Menu size={24} />}
</button>
<div className="text-lg md:text-xl font-semibold">{headerTitle}</div>
</div>
<DropdownMenu <DropdownMenu
buttonContent={ buttonContent={
<Image <Image
@ -125,17 +169,11 @@ export default function Layout({
{/* Main Content */} {/* Main Content */}
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
{/* Content avec scroll si nécessaire */} {/* Content avec scroll si nécessaire */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto p-4 md:p-6">
{children} {children}
</div> </div>
{/* Footer - h-16 = 64px */} {/* Footer responsive */}
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between"> <Footer softwareName={softwareName} softwareVersion={softwareVersion} />
<div className="text-sm font-light">
<span>&copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
<div className="text-sm font-light">{softwareName} - {softwareVersion}</div>
</div>
<Logo className="w-8 h-8" />
</footer>
</div> </div>
</div> </div>
</div> </div>

View File

@ -66,48 +66,45 @@ export default function Layout({
<ProtectedRoute> <ProtectedRoute>
<div className="flex flex-col min-h-screen bg-gray-50"> <div className="flex flex-col min-h-screen bg-gray-50">
{/* Entête */} {/* 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"> <header className="bg-white border-b border-gray-200 px-4 py-2 md:px-8 md:py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Logo className="h-8 w-8" /> {/* Utilisation du composant Logo */} <Logo className="h-6 w-6 md:h-8 md:w-8" /> {/* Utilisation du composant Logo */}
<div className="text-xl font-semibold">Accueil</div> <div className="text-lg md:text-xl font-semibold">Accueil</div>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-2 md:space-x-4">
<button <button
className="p-2 rounded-full hover:bg-gray-200" className="p-1 md:p-2 rounded-full hover:bg-gray-200"
onClick={() => { router.push(FE_PARENTS_HOME_URL); }} // Utilisation de router pour revenir à l'accueil parent onClick={() => { router.push(FE_PARENTS_HOME_URL); }} // Utilisation de router pour revenir à l'accueil parent
> >
<Home /> <Home className="h-5 w-5 md:h-6 md:w-6" />
</button> </button>
<div className="relative"> <div className="relative">
<button <button
className="p-2 rounded-full hover:bg-gray-200" className="p-1 md:p-2 rounded-full hover:bg-gray-200"
onClick={() => { router.push(FE_PARENTS_MESSAGERIE_URL); }} // Utilisation de router onClick={() => { router.push(FE_PARENTS_MESSAGERIE_URL); }} // Utilisation de router
> >
<MessageSquare /> <MessageSquare className="h-5 w-5 md:h-6 md:w-6" />
</button> </button>
{messages.length > 0 && ( {messages.length > 0 && (
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-emerald-600"></span> <span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-emerald-600"></span>
)} )}
</div> </div>
<DropdownMenu <DropdownMenu
buttonContent={<User />} buttonContent={<User className="h-5 w-5 md:h-6 md:w-6" />}
items={[ items={[
{ label: 'Se déconnecter', icon: LogOut, onClick: handleDisconnect }, { label: 'Se déconnecter', icon: LogOut, onClick: handleDisconnect },
{ label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } } { label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } }
]} ]}
buttonClassName="p-2 rounded-full hover:bg-gray-200" buttonClassName="p-1 md: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" menuClassName="absolute right-0 mt-2 w-36 md:w-48 bg-white border border-gray-200 rounded-md shadow-lg"
/> />
</div> </div>
</header> </header>
{/* Content */} {/* Content */}
<div className="pt-20 p-8 flex-1"> {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */} <div className="pt-16 md:pt-20 p-4 md:p-8 flex-1"> {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */}
{children} {children}
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Table from '@/components/Table'; import Table from '@/components/Table';
import { Edit } from 'lucide-react'; import { Edit, Users } from 'lucide-react';
import StatusLabel from '@/components/StatusLabel'; import StatusLabel from '@/components/StatusLabel';
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url'; import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
import { fetchChildren } from '@/app/actions/subscriptionAction'; import { fetchChildren } from '@/app/actions/subscriptionAction';
@ -11,10 +11,11 @@ import { useSession } from 'next-auth/react';
import { FE_USERS_LOGIN_URL } from '@/utils/Url'; import { FE_USERS_LOGIN_URL } from '@/utils/Url';
export default function ParentHomePage() { export default function ParentHomePage() {
const [actions, setActions] = useState([]);
const [children, setChildren] = useState([]); const [children, setChildren] = useState([]);
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const [userId, setUserId] = useState(null); const [userId, setUserId] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const router = useRouter(); const router = useRouter();
@ -61,29 +62,62 @@ export default function ParentHomePage() {
} }
}; };
return ( // Définir les colonnes du tableau
<div> const childrenColumns = [
<div> { name: 'Nom', transform: (row) => `${row.student.last_name} ${row.student.first_name}` },
<h2 className="text-xl font-semibold mb-4">Dernières actions à effectuer</h2> {
<Table data={actions} columns={actionColumns} itemsPerPage={5} /> name: 'Statut',
transform: (row) => (
<div className="flex justify-center items-center">
<StatusLabel status={row.status} showDropdown={false}/>
</div> </div>
)
},
{
name: 'Actions',
transform: (row) => (
<div className="flex justify-center">
<button
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
onClick={(e) => {
e.stopPropagation();
handleEdit(row.student.id);
}}
aria-label="Modifier"
>
<Edit className="h-5 w-5" />
</button>
</div>
)
}
];
const itemsPerPage = 5;
const totalPages = Math.ceil(children.length / itemsPerPage) || 1;
const handlePageChange = (newPage) => {
setCurrentPage(newPage);
};
return (
<div className="px-2 py-4 md:px-4 max-w-full">
<div> <div>
<h2 className="text-xl font-semibold mb-4">Enfants</h2> <h2 className="text-xl font-semibold mb-3 px-1 flex items-center gap-2">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <Users className="h-6 w-6 text-emerald-600" />
{children.map((child) => ( Enfants
<div key={child.student.id} className={`border p-4 rounded shadow ${getShadowColor(child.status)}`}> </h2>
<div className="flex justify-between items-center"> <div className="overflow-x-auto">
<h3 className="text-lg font-semibold">{child.student.last_name} {child.student.first_name}</h3> <Table
<Edit className="cursor-pointer" onClick={() => handleEdit(child.student.id)} /> data={children}
</div> columns={childrenColumns}
<StatusLabel status={child.status } showDropdown={false}/> itemsPerPage={itemsPerPage}
</div> currentPage={currentPage}
))} totalPages={totalPages}
onPageChange={handlePageChange}
defaultTheme="bg-gray-50"
/>
</div> </div>
</div> </div>
</div> </div>
); );
} }

View File

@ -0,0 +1,17 @@
import Logo from '@/components/Logo';
export default function Footer ({softwareName, softwareVersion}) {
return (
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between">
<div className="text-sm font-light">
<span>&copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
</div>
<div className="text-sm font-light flex items-center justify-between">
<div className="text-sm font-light mr-4">{softwareName} - {softwareVersion}</div>
<Logo className="w-8 h-8" />
</div>
</footer>
)
}

View File

@ -29,7 +29,7 @@ function Sidebar({ establishment, currentPage, items }) {
return <> return <>
{/* Sidebar */} {/* Sidebar */}
<div className="w-64 bg-white border-r border-gray-200 py-6 px-4"> <div className="w-64 bg-white border-r h-full border-gray-200 py-6 px-4">
<div className="flex items-center mb-8 px-2"> <div className="flex items-center mb-8 px-2">
<div className="text-xl font-semibold">{establishment?.name}</div> <div className="text-xl font-semibold">{establishment?.name}</div>
</div> </div>

View File

@ -0,0 +1,62 @@
# N3wt School
Logiciel de gestion d'école
## Maquette
Maquette figma : https://www.figma.com/design/1BtWHIQlJDTeue2oYblefV/Maquette-Logiciel-de-gestion-Ecole?node-id=42-296&t=AdaSQYWkLLf1o5OI-0
## Installation
### Installation de docker
Lien de téléchargement : https://www.docker.com/get-started/
# Lancement de monteschool
```sh
docker compose up -d
```
Lancement du front end
```sh
npm run dev
```
se connecter à localhost:8080
# Installation et développement en local
* [Installation Manuelle](./docs/Installation_Manuelle.md)
* [Convention de codage](./docs/CODING_GUIDELINES.md)
# Installer la vérification de commit (dans le projet principal)
```
npm i
npm run prepare
```
# Faire une livraison Mise en Production
```sh
# Faire la première release (1.0.0)
npm run release -- --first-release
# Faire une prerelease (RC,alpha,beta)
npm run release -- --prerelease <name>
# Faire une release
npm run release
# Forcer la release sur un mode particulier (majeur, mineur ou patch)
# npm run script
npm run release -- --release-as minor
# Or
npm run release -- --release-as 1.1.0
# ignorer les hooks de commit lors de la release
npm run release -- --no-verify
```