This commit is contained in:
Louis Mazin 2026-09-15 22:55:03 +02:00
parent ba33de6564
commit 2dcf62c597
14 changed files with 296 additions and 281 deletions

View File

@ -1,16 +1,83 @@
#root { #root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100dvh;
min-height: 100vh; min-height: 100vh;
} }
main { main {
width: min(var(--content-max), 100%); width: min(var(--content-max), 100%);
flex: 1; min-height: 0;
flex: 1 1 auto;
margin: 0 auto; margin: 0 auto;
overflow-y: auto;
padding: 2rem clamp(1.25rem, 3vw, 2.75rem); padding: 2rem clamp(1.25rem, 3vw, 2.75rem);
} }
.page-route {
width: 100%;
max-width: var(--content-max);
min-height: 100%;
margin: 0 auto;
}
main.main--bounded {
overflow: hidden;
}
main.main--bounded .page-route {
height: 100%;
overflow: hidden;
}
main.main--flow .page-route {
min-height: 100%;
}
.page-route > .card,
.page-route > .page-panel,
.page-route > .page-container,
.page-route > .weekly-schedule,
.page-route > .home-page {
width: 100%;
max-width: none;
margin-right: 0;
margin-left: 0;
}
.page-container {
width: 100%;
max-width: var(--content-max);
margin: 0 auto;
}
.page-panel,
.card {
padding: clamp(1.35rem, 4vw, 2.25rem);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.page-heading {
margin-bottom: 1.5rem;
}
.page-heading h2,
.page-panel > h2,
.card > h2 {
margin: 0;
color: var(--text);
font-size: clamp(1.35rem, 3vw, 1.8rem);
letter-spacing: -0.02em;
}
.page-heading p {
margin: 0.5rem 0 0;
color: var(--text-muted);
}
footer { footer {
flex-shrink: 0; flex-shrink: 0;
padding: 1.5rem; padding: 1.5rem;
@ -87,10 +154,6 @@ input:focus-visible {
border-color: var(--danger); border-color: var(--danger);
} }
.admin-page h2 {
margin-bottom: 1.25rem;
}
.admin-toolbar { .admin-toolbar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -157,16 +220,6 @@ input:focus-visible {
margin-top: 0.45rem; margin-top: 0.45rem;
} }
form,
.card,
.profile-card {
padding: clamp(1.35rem, 4vw, 2.25rem);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
form { form {
max-width: none; max-width: none;
margin: 0; margin: 0;
@ -176,29 +229,14 @@ form {
box-shadow: none; box-shadow: none;
} }
.card {
width: 100%;
max-width: var(--content-max);
margin: 0 auto;
}
.admin-page, .admin-page,
.weekly-summary-page, .weekly-summary-page,
.time-clock-page, .time-clock-page,
.profile-card, .home-page {
.home-container {
width: 100%; width: 100%;
max-width: var(--content-max); max-width: var(--content-max);
} }
.card h2,
.profile-card h2 {
margin: 0 0 1.5rem;
color: var(--text);
font-size: clamp(1.35rem, 3vw, 1.8rem);
letter-spacing: -0.02em;
}
.form-group { .form-group {
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
} }
@ -211,9 +249,9 @@ form {
font-weight: 700; font-weight: 700;
} }
.form-group input, input,
input[type="text"], select,
input[type="password"] { textarea {
width: 100%; width: 100%;
min-height: 46px; min-height: 46px;
padding: 0.7rem 0.8rem; padding: 0.7rem 0.8rem;
@ -224,14 +262,24 @@ input[type="password"] {
transition: var(--transition); transition: var(--transition);
} }
.form-group input:focus, input:focus,
input[type="text"]:focus, select:focus,
input[type="password"]:focus { textarea:focus {
border-color: var(--accent); border-color: var(--accent);
outline: none; outline: none;
box-shadow: 0 0 0 3px rgba(168, 93, 42, 0.12); box-shadow: 0 0 0 3px rgba(168, 93, 42, 0.12);
} }
.role-admin {
color: var(--accent);
font-weight: 600;
}
.role-user {
color: var(--text);
font-weight: 600;
}
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;

View File

@ -1,4 +1,4 @@
import { Routes, Route, Navigate } from 'react-router-dom'; import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import LoginPage from './pages/LoginPage'; import LoginPage from './pages/LoginPage';
import HomePage from './pages/HomePage'; import HomePage from './pages/HomePage';
import ProtectedRoute from './components/ProtectedRoute'; import ProtectedRoute from './components/ProtectedRoute';
@ -11,10 +11,15 @@ import WeeklySummaryPage from './pages/WeeklySummaryPage';
import './App.css'; import './App.css';
function App() { function App() {
const { pathname } = useLocation();
const boundedPages = ['/weekly-summary'];
const layoutMode = boundedPages.includes(pathname) ? 'bounded' : 'flow';
return ( return (
<div id="root"> <div id="root">
<Header /> <Header />
<main> <main className={`main--${layoutMode}`}>
<div className="page-route">
<Routes> <Routes>
<Route path="/login" element={<LoginPage />} /> <Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} /> <Route path="/register" element={<RegisterPage />} />
@ -23,14 +28,11 @@ function App() {
<TimeClockPage /> <TimeClockPage />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route <Route path="/" element={
path="/"
element={
<ProtectedRoute> <ProtectedRoute>
<HomePage /> <HomePage />
</ProtectedRoute> </ProtectedRoute>
} } />
/>
<Route path="/users" element={ <Route path="/users" element={
<AdminRoute> <AdminRoute>
<AdminUsersPage /> <AdminUsersPage />
@ -43,6 +45,7 @@ function App() {
} /> } />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
</div>
</main> </main>
<footer className="site-footer"> <footer className="site-footer">
<p>&copy; 2026 Le Fournil du Bocage. Tous droits réservés.</p> <p>&copy; 2026 Le Fournil du Bocage. Tous droits réservés.</p>

View File

@ -46,6 +46,8 @@
.hamburger-btn { .hamburger-btn {
display: flex; display: flex;
position: relative;
z-index: 1001;
width: 44px; width: 44px;
height: 44px; height: 44px;
flex-direction: column; flex-direction: column;
@ -95,14 +97,17 @@
.hamburger-menu-overlay { .hamburger-menu-overlay {
position: fixed; position: fixed;
inset: 76px 0 0; inset: 76px 0 0;
z-index: 99; z-index: 1000;
overflow: hidden;
background: rgba(15, 23, 42, 0.32); background: rgba(15, 23, 42, 0.32);
animation: fade-in 0.2s ease; animation: fade-in 0.2s ease;
} }
.hamburger-menu-content { .hamburger-menu-content {
width: min(360px, 100%); width: min(360px, 100%);
min-height: calc(100% - 1rem); height: 100%;
max-height: 100%;
overflow-y: auto;
margin-left: auto; margin-left: auto;
padding: 1.25rem; padding: 1.25rem;
background: var(--surface); background: var(--surface);
@ -215,6 +220,6 @@
} }
.hamburger-menu-content { .hamburger-menu-content {
min-height: 100%; height: 100%;
} }
} }

View File

@ -12,6 +12,12 @@ const Header = () => {
setIsMenuOpen((isOpen) => !isOpen); setIsMenuOpen((isOpen) => !isOpen);
}; };
useEffect(() => {
document.body.classList.toggle('menu-open', isMenuOpen);
return () => document.body.classList.remove('menu-open');
}, [isMenuOpen]);
useEffect(() => { useEffect(() => {
const handleEscape = (event) => { const handleEscape = (event) => {
if (event.key === 'Escape') { if (event.key === 'Escape') {
@ -30,6 +36,7 @@ const Header = () => {
}; };
return ( return (
<>
<header className="site-header"> <header className="site-header">
<div className="header-container"> <div className="header-container">
<Link to="/" className="brand" aria-label="Retour à l'accueil"> <Link to="/" className="brand" aria-label="Retour à l'accueil">
@ -48,8 +55,8 @@ const Header = () => {
<span className="hamburger-line" /> <span className="hamburger-line" />
</button> </button>
</div> </div>
</header>
{/* Menu hamburger */}
{isMenuOpen && ( {isMenuOpen && (
<div className="hamburger-menu-overlay" onClick={toggleMenu}> <div className="hamburger-menu-overlay" onClick={toggleMenu}>
<div <div
@ -60,12 +67,8 @@ const Header = () => {
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
<div className="menu-heading"> <div className="menu-heading">
<div>
<span className="menu-eyebrow">Navigation</span>
<strong>{user ? `Bonjour ${user.username}` : 'Bienvenue'}</strong> <strong>{user ? `Bonjour ${user.username}` : 'Bienvenue'}</strong>
</div> </div>
<button className="close-btn" onClick={toggleMenu} aria-label="Fermer le menu">×</button>
</div>
<div className="menu-links"> <div className="menu-links">
{user ? ( {user ? (
<> <>
@ -86,7 +89,7 @@ const Header = () => {
</div> </div>
</div> </div>
)} )}
</header> </>
); );
}; };

View File

@ -82,7 +82,7 @@ const AdminUsersPage = () => {
if (loading) { if (loading) {
return ( return (
<div className="card admin-page"> <div className="card page-container admin-page">
<h2>Gestion des comptes</h2> <h2>Gestion des comptes</h2>
<p>Chargement des utilisateurs...</p> <p>Chargement des utilisateurs...</p>
</div> </div>
@ -90,7 +90,7 @@ const AdminUsersPage = () => {
} }
return ( return (
<div className="card admin-page"> <div className="card page-container admin-page">
<h2>Gestion des comptes</h2> <h2>Gestion des comptes</h2>
{error && <div className="alert alert-danger">{error}</div>} {error && <div className="alert alert-danger">{error}</div>}
@ -135,9 +135,9 @@ const AdminUsersPage = () => {
<td>{user.username}</td> <td>{user.username}</td>
<td> <td>
{user.is_admin ? ( {user.is_admin ? (
<span style={{ color: 'var(--accent)', fontWeight: '500' }}>Administrateur</span> <span className="role-admin">Administrateur</span>
) : ( ) : (
<span style={{ color: 'var(--text)', fontWeight: '500' }}>Utilisateur</span> <span className="role-user">Utilisateur</span>
)} )}
</td> </td>
<td>{new Date(user.date_joined).toLocaleDateString('fr-FR')}</td> <td>{new Date(user.date_joined).toLocaleDateString('fr-FR')}</td>

View File

@ -1,12 +1,3 @@
.home-container {
margin: 0 auto;
}
.profile-card {
margin-right: auto;
margin-left: auto;
}
.user-info { .user-info {
display: grid; display: grid;
gap: 0.75rem; gap: 0.75rem;

View File

@ -26,8 +26,8 @@ const HomePage = () => {
} }
return ( return (
<div className="home-container"> <div className="page-panel page-container home-page">
<div className="profile-card"> <div className="page-heading">
<h2>Profil Utilisateur</h2> <h2>Profil Utilisateur</h2>
{userData && ( {userData && (
<div className="user-info"> <div className="user-info">

View File

@ -1,24 +1,24 @@
.login-container { .auth-page {
display: grid; display: grid;
min-height: min(620px, calc(100vh - 190px)); min-height: min(620px, calc(100vh - 190px));
place-items: center; place-items: center;
} }
.login-card { .auth-card {
width: min(100%, 460px); width: min(100%, 460px);
padding: clamp(1.35rem, 5vw, 2.5rem); padding: clamp(1.35rem, 5vw, 2.5rem);
text-align: center; text-align: center;
} }
.login-card h2 { .auth-card h2 {
margin-bottom: 0.45rem; margin-bottom: 0.45rem;
} }
.login-card .form-group { .auth-card .form-group {
text-align: left; text-align: left;
} }
.login-card .btn { .auth-card .btn {
width: 100%; width: 100%;
margin-top: 0.4rem; margin-top: 0.4rem;
} }

View File

@ -26,8 +26,8 @@ const LoginPage = () => {
}; };
return ( return (
<div className="login-container"> <div className="auth-page">
<div className="card login-card"> <div className="page-panel auth-card">
<h2>Connexion</h2> <h2>Connexion</h2>
{error && <div className="alert alert-danger">{error}</div>} {error && <div className="alert alert-danger">{error}</div>}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>

View File

@ -23,16 +23,16 @@ const RegisterPage = () => {
}; };
if (!invite) { if (!invite) {
return <div className="login-container"><div className="card login-card"><h2>Lien invalide</h2><p>Un lien d’invitation est nécessaire pour créer un compte.</p></div></div>; return <div className="auth-page"><div className="page-panel auth-card"><h2>Lien invalide</h2><p>Un lien d’invitation est nécessaire pour créer un compte.</p></div></div>;
} }
if (isCreated) { if (isCreated) {
return <div className="login-container"><div className="card login-card"><h2>Compte créé</h2><p>Votre compte est prêt. Vous pouvez maintenant vous connecter.</p><button className="btn btn-primary" onClick={() => navigate('/login')}>Se connecter</button></div></div>; return <div className="auth-page"><div className="page-panel auth-card"><h2>Compte créé</h2><p>Votre compte est prêt. Vous pouvez maintenant vous connecter.</p><button className="btn btn-primary" onClick={() => navigate('/login')}>Se connecter</button></div></div>;
} }
return ( return (
<div className="login-container"> <div className="auth-page">
<div className="card login-card"> <div className="page-panel auth-card">
<h2>Créer un compte</h2> <h2>Créer un compte</h2>
{error && <div className="alert alert-danger">{error}</div>} {error && <div className="alert alert-danger">{error}</div>}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>

View File

@ -1,4 +1,5 @@
.time-clock-page { .time-clock-page {
min-height: 100%;
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
} }
@ -75,94 +76,58 @@
font-weight: 400; font-weight: 400;
} }
/* New styles for time tracking */
.time-tracking-section { .time-tracking-section {
margin-top: 30px; margin-top: 2rem;
} }
.current-time-display { .admin-time-layout {
text-align: center; display: grid;
margin: 20px 0; grid-template-columns: minmax(0, 1fr) minmax(280px, 0.42fr);
font-size: 18px; gap: 24px;
font-weight: bold; align-items: start;
}
.admin-entry-panel {
grid-column: 2;
grid-row: 1;
}
.admin-time-layout .time-entries-list {
grid-column: 1;
grid-row: 1;
min-width: 0;
margin-top: 20px;
} }
.time-entry-form { .time-entry-form {
background-color: #f8f9fa; margin: 1.25rem 0;
padding: 20px; padding: 1.25rem;
border: 1px solid var(--accent); background: var(--surface-muted);
border-radius: 8px; border: 1px solid var(--border);
margin: 20px 0; border-radius: var(--radius-sm);
} }
.time-entry-form h4 { .time-entry-form h4 {
margin-top: 0; margin: 0 0 1rem;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
} }
.time-entries-list { .time-entries-list {
margin: 20px 0; margin: 1.25rem 0;
} }
.time-entries-list h4 { .time-entries-list h4 {
margin-top: 0; margin: 0 0 1rem;
margin-bottom: 15px;
} }
.table-responsive { .table-responsive {
overflow-x: auto; overflow-x: auto;
} }
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f8f9fa;
font-weight: bold;
}
.btn-group {
display: flex;
gap: 5px;
}
.btn-small {
padding: 5px 10px;
font-size: 12px;
}
.total-hours { .total-hours {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 1.25rem;
padding: 15px; padding: 1rem;
background-color: #e9ecef; background: var(--surface-muted);
} }
.scan-conflict { .scan-conflict {
@ -183,3 +148,15 @@ th {
.scan-conflict input { .scan-conflict input {
width: 150px; width: 150px;
} }
@media (max-width: 900px) {
.admin-time-layout {
grid-template-columns: 1fr;
}
.admin-entry-panel,
.admin-time-layout .time-entries-list {
grid-column: auto;
grid-row: auto;
}
}

View File

@ -368,14 +368,10 @@ const TimeClockPage = () => {
{/* Time tracking section */} {/* Time tracking section */}
<div className="time-tracking-section"> <div className="time-tracking-section">
<div className={isAdmin ? 'admin-time-layout' : undefined}>
{/* Current time display */} {isAdmin && (
<div className="current-time-display"> <div className="admin-entry-panel">
<p>Heure actuelle: {currentTime.toLocaleTimeString('fr-FR')}</p> {editingEntry ? (
</div>
{ (isAdmin && editingEntry) ?
(
<div className="time-entry-form"> <div className="time-entry-form">
<h4>Modifier le pointage du {formatDate(editingEntry.date)}</h4> <h4>Modifier le pointage du {formatDate(editingEntry.date)}</h4>
<form onSubmit={handleUpdateEntry}> <form onSubmit={handleUpdateEntry}>
@ -405,12 +401,7 @@ const TimeClockPage = () => {
</div> </div>
</form> </form>
</div> </div>
) ) : (
:
(
<div>
{isAdmin &&
(
<div className="time-entry-form"> <div className="time-entry-form">
<h4>Ajouter un pointage manuel</h4> <h4>Ajouter un pointage manuel</h4>
<form onSubmit={handleAddEntry}> <form onSubmit={handleAddEntry}>
@ -454,11 +445,9 @@ const TimeClockPage = () => {
<button type="submit" className="btn btn-primary">Ajouter le pointage</button> <button type="submit" className="btn btn-primary">Ajouter le pointage</button>
</form> </form>
</div> </div>
) )}
}
</div> </div>
) )}
}
{/* Time entries list */} {/* Time entries list */}
<div className="time-entries-list"> <div className="time-entries-list">
@ -517,6 +506,7 @@ const TimeClockPage = () => {
</div> </div>
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -1,11 +1,9 @@
.weekly-schedule { .weekly-schedule {
margin: 0; display: flex;
padding: 24px; height: 100%;
min-height: 0;
flex-direction: column;
color: var(--text); color: var(--text);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
} }
.weekly-schedule.loading, .weekly-schedule.loading,
@ -111,6 +109,7 @@
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
scrollbar-color: #cbbbae transparent; scrollbar-color: #cbbbae transparent;
scrollbar-width: thin; scrollbar-width: thin;
max-height: 80vh; /* Ajout pour limiter la hauteur maximale */
} }
.schedule-day-header { .schedule-day-header {
@ -265,5 +264,4 @@
.nav-button { .nav-button {
width: 100%; width: 100%;
} }
} }

View File

@ -231,7 +231,7 @@ const WeeklySummaryPage = ({ userId }) => {
.map((username) => ({ username, color: getUserColor(username) })); .map((username) => ({ username, color: getUserColor(username) }));
return ( return (
<div className={`weekly-schedule${isAdmin ? ' multi-user-schedule' : ''}`}> <div className={`weekly-schedule card${isAdmin ? ' multi-user-schedule' : ''}`}>
<div className="schedule-header"> <div className="schedule-header">
<div> <div>
<span className="page-eyebrow">{isAdmin ? 'Vue équipe' : 'Votre présence'}</span> <span className="page-eyebrow">{isAdmin ? 'Vue équipe' : 'Votre présence'}</span>