mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
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
116 lines
3.9 KiB
JavaScript
116 lines
3.9 KiB
JavaScript
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);
|
|
if (onTabChange) {
|
|
onTabChange(tabId);
|
|
}
|
|
};
|
|
|
|
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="relative flex items-center bg-gray-50 border-b border-gray-200 shadow-sm">
|
|
{/* Flèche gauche */}
|
|
{showLeftArrow && (
|
|
<button
|
|
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"
|
|
>
|
|
<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 */}
|
|
<div className="flex-1 flex flex-col overflow-hidden rounded-b-lg shadow-inner">
|
|
<AnimatePresence mode="wait">
|
|
{tabs.map(
|
|
(tab) =>
|
|
activeTab === tab.id && (
|
|
<motion.div
|
|
key={tab.id}
|
|
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}
|
|
</motion.div>
|
|
)
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SidebarTabs;
|