mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
feat(frontend): refonte mobile planning et ameliorations suivi pedagogique [#NEWTS-4]
Fonction PWA et ajout du responsive design Planning mobile : - Nouvelle vue DayView avec bandeau semaine scrollable, date picker natif et navigation integree - ScheduleNavigation converti en drawer overlay sur mobile, sidebar fixe sur desktop - Suppression double barre navigation mobile, controles deplaces dans DayView - Date picker natif via label+input sur mobile Suivi pedagogique : - Refactorisation page grades avec composant Table partage - Colonnes stats par periode, absences, actions (Fiche + Evaluer) - Lien cliquable sur la classe vers SchoolClassManagement feat(backend): ajout associated_class_id dans StudentByRFCreationSerializer [#NEWTS-4] UI global : - Remplacement fleches texte par icones Lucide ChevronDown/ChevronRight - Pagination conditionnelle sur tous les tableaux plats - Layout responsive mobile : cartes separees fond transparent - Table.js : pagination optionnelle, wrapper md uniquement
This commit is contained in:
@ -1,8 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||
const [showRightArrow, setShowRightArrow] = useState(false);
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
const handleTabChange = (tabId) => {
|
||||
setActiveTab(tabId);
|
||||
@ -11,23 +15,77 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateArrows = () => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
setShowLeftArrow(el.scrollLeft > 0);
|
||||
setShowRightArrow(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateArrows();
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
el.addEventListener('scroll', updateArrows);
|
||||
window.addEventListener('resize', updateArrows);
|
||||
return () => {
|
||||
el.removeEventListener('scroll', updateArrows);
|
||||
window.removeEventListener('resize', updateArrows);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
const scroll = (direction) => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
el.scrollBy({ left: direction === 'left' ? -150 : 150, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full">
|
||||
{/* Tabs Header */}
|
||||
<div className="flex h-14 bg-gray-50 border-b border-gray-200 shadow-sm">
|
||||
{tabs.map((tab) => (
|
||||
<div className="relative flex items-center bg-gray-50 border-b border-gray-200 shadow-sm">
|
||||
{/* Flèche gauche */}
|
||||
{showLeftArrow && (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`flex-1 text-center p-4 font-medium transition-colors duration-200 ${
|
||||
activeTab === tab.id
|
||||
? 'border-b-4 border-emerald-500 text-emerald-600 bg-emerald-50 font-semibold'
|
||||
: 'text-gray-500 hover:text-emerald-500'
|
||||
}`}
|
||||
onClick={() => handleTabChange(tab.id)}
|
||||
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"
|
||||
aria-label="Tabs précédents"
|
||||
>
|
||||
{tab.label}
|
||||
<ChevronLeft size={22} strokeWidth={2.5} />
|
||||
</button>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* Liste des onglets scrollable */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex overflow-x-auto scrollbar-none scroll-smooth"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabChange(tab.id)}
|
||||
className={`flex-shrink-0 whitespace-nowrap h-14 px-5 font-medium transition-colors duration-200 ${
|
||||
activeTab === tab.id
|
||||
? 'border-b-4 border-emerald-500 text-emerald-600 bg-emerald-50 font-semibold'
|
||||
: 'text-gray-500 hover:text-emerald-500'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Flèche droite */}
|
||||
{showRightArrow && (
|
||||
<button
|
||||
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"
|
||||
aria-label="Tabs suivants"
|
||||
>
|
||||
<ChevronRight size={22} strokeWidth={2.5} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tabs Content */}
|
||||
@ -38,10 +96,10 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||
activeTab === tab.id && (
|
||||
<motion.div
|
||||
key={tab.id}
|
||||
initial={{ opacity: 0, x: 50 }} // Animation d'entrée
|
||||
animate={{ opacity: 1, x: 0 }} // Animation visible
|
||||
exit={{ opacity: 0, x: -50 }} // Animation de sortie
|
||||
transition={{ duration: 0.3 }} // Durée des animations
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex-1 flex flex-col h-full min-h-0"
|
||||
>
|
||||
{tab.content}
|
||||
|
||||
Reference in New Issue
Block a user