{@render children()} @@ -131,9 +259,8 @@ display: flex; justify-content: space-between; align-items: center; - flex-wrap: wrap; - height: auto; - padding: 0.75rem 0; + height: 56px; + padding: 0; gap: 0.75rem; } @@ -150,23 +277,64 @@ color: var(--accent-primary, #0ea5e9); } - .header-nav { + /* Mobile section label */ + .mobile-section-label { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-primary, #1f2937); + flex: 1; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + /* Hamburger button */ + .hamburger-button { display: flex; + flex-direction: column; + justify-content: center; align-items: center; - width: 100%; - justify-content: flex-end; - flex-wrap: wrap; - gap: 0.5rem; + gap: 5px; + width: 40px; + height: 40px; + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 0.5rem; + flex-shrink: 0; + } + + .hamburger-button:hover { + background: var(--surface-primary, #f8fafc); + } + + .hamburger-line { + display: block; + width: 20px; + height: 2px; + background: var(--text-secondary, #64748b); + border-radius: 1px; + } + + /* Desktop nav — hidden on mobile */ + .desktop-nav { + display: none; + align-items: center; + gap: 0.125rem; } .nav-link { - padding: 0.5rem 1rem; - font-size: 0.875rem; + padding: 0.375rem 0.625rem; + font-size: 0.8125rem; font-weight: 500; color: var(--text-secondary, #64748b); text-decoration: none; border-radius: 0.5rem; transition: all 0.2s; + white-space: nowrap; + flex-shrink: 0; } .nav-link:hover { @@ -180,8 +348,8 @@ } .nav-button { - padding: 0.5rem 1rem; - font-size: 0.875rem; + padding: 0.375rem 0.625rem; + font-size: 0.8125rem; font-weight: 500; color: var(--text-secondary, #64748b); background: transparent; @@ -189,6 +357,8 @@ border-radius: 0.5rem; cursor: pointer; transition: all 0.2s; + white-space: nowrap; + flex-shrink: 0; } .nav-button:hover { @@ -199,9 +369,9 @@ .logout-button { display: inline-flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - font-size: 0.875rem; + gap: 0.375rem; + padding: 0.375rem 0.625rem; + font-size: 0.8125rem; font-weight: 500; color: var(--text-secondary, #64748b); background: transparent; @@ -209,6 +379,8 @@ border-radius: 0.5rem; cursor: pointer; transition: all 0.2s; + white-space: nowrap; + flex-shrink: 0; } .logout-button:hover:not(:disabled) { @@ -221,6 +393,118 @@ cursor: not-allowed; } + /* Mobile overlay */ + .mobile-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + z-index: 200; + animation: fadeIn 0.2s ease-out; + } + + /* Mobile drawer */ + .mobile-drawer { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: min(320px, 85vw); + background: var(--surface-elevated, #fff); + z-index: 201; + display: flex; + flex-direction: column; + animation: slideInLeft 0.25s ease-out; + } + + .mobile-drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border-subtle, #e2e8f0); + } + + .mobile-close { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background: none; + border: none; + cursor: pointer; + font-size: 1.5rem; + color: var(--text-secondary, #64748b); + border-radius: 0.5rem; + } + + .mobile-close:hover { + background: var(--surface-primary, #f8fafc); + color: var(--text-primary, #1f2937); + } + + .mobile-drawer-body { + flex: 1; + overflow-y: auto; + padding: 0.75rem 0; + } + + .mobile-role-switcher { + padding: 0.5rem 1.25rem 0.75rem; + border-bottom: 1px solid var(--border-subtle, #e2e8f0); + margin-bottom: 0.5rem; + } + + .mobile-nav-link { + display: flex; + align-items: center; + width: 100%; + padding: 0.75rem 1.25rem; + font-size: 0.9375rem; + font-weight: 500; + color: var(--text-secondary, #64748b); + text-decoration: none; + border: none; + background: none; + cursor: pointer; + border-left: 3px solid transparent; + transition: all 0.15s; + } + + .mobile-nav-link:hover { + background: var(--surface-primary, #f8fafc); + color: var(--text-primary, #1f2937); + } + + .mobile-nav-link.active { + color: var(--accent-primary, #0ea5e9); + border-left-color: var(--accent-primary, #0ea5e9); + background: var(--accent-primary-light, #e0f2fe); + } + + .mobile-logout { + color: var(--color-alert, #ef4444); + } + + .mobile-logout:hover { + background: #fef2f2; + } + + .mobile-drawer-footer { + border-top: 1px solid var(--border-subtle, #e2e8f0); + padding: 0.5rem 0; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes slideInLeft { + from { transform: translateX(-100%); } + to { transform: translateX(0); } + } + .spinner { width: 14px; height: 14px; @@ -246,18 +530,33 @@ } } - @media (min-width: 768px) { + @media (min-width: 1200px) { .header-content { - flex-wrap: nowrap; height: 64px; - padding: 0; gap: 0; } - .header-nav { - width: auto; - flex-wrap: nowrap; - justify-content: flex-start; + .mobile-section-label { + display: none; + } + + .hamburger-button { + display: none; + } + + .desktop-nav { + display: flex; + } + + .nav-link, + .nav-button { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + } + + .logout-button { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; } .admin-main { diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte deleted file mode 100644 index 51eda47..0000000 --- a/frontend/src/routes/admin/+page.svelte +++ /dev/null @@ -1,173 +0,0 @@ - - - - Administration - Classeo - - -
- - -
-
- {classCount ?? '–'} - Classes -
-
- {subjectCount ?? '–'} - Matières -
-
- - -
- - diff --git a/frontend/src/routes/admin/+page.ts b/frontend/src/routes/admin/+page.ts new file mode 100644 index 0000000..4f0ddad --- /dev/null +++ b/frontend/src/routes/admin/+page.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export function load() { + redirect(302, '/admin/users'); +} diff --git a/frontend/src/routes/admin/assignments/+page.svelte b/frontend/src/routes/admin/assignments/+page.svelte index 378f6f5..93ae729 100644 --- a/frontend/src/routes/admin/assignments/+page.svelte +++ b/frontend/src/routes/admin/assignments/+page.svelte @@ -352,11 +352,11 @@ {#each assignments as assignment (assignment.id)} - + {assignment.teacherFirstName} {assignment.teacherLastName} - {assignment.className} - + {assignment.className} + {#if getSubjectColor(assignment.subjectId)} - + Active - + {new Date(assignment.startDate).toLocaleDateString('fr-FR')} - + + + + {#if error} +
+ ! + {error} + +
+ {/if} + + {#if successMessage} +
+ {successMessage} +
+ {/if} + + + + {#if isLoading} +
+
+

Chargement des remplacements...

+
+ {:else if replacements.length === 0} +
+ 🔄 + {#if searchTerm} +

Aucun résultat

+

Aucun remplacement ne correspond à votre recherche

+ + {:else} +

Aucun remplacement actif

+

Désignez un remplaçant pour un enseignant absent

+ + {/if} +
+ {:else} +
+ + + + + + + + + + + + + {#each replacements as replacement (replacement.id)} + {@const days = daysRemaining(replacement.endDate)} + + + + + + + + + {/each} + +
Enseignant remplacéRemplaçantClasses / MatièresPériodeStatutActions
+ {getTeacherName(replacement.replacedTeacherId)} + + {getTeacherName(replacement.replacementTeacherId)} + + {#each replacement.classes as pair} + {@const color = getSubjectColor(pair.subjectId)} + + {getClassName(pair.classId)} + {#if color} + + {getSubjectName(pair.subjectId)} + + {:else} + + {getSubjectName(pair.subjectId)} + + {/if} + + {/each} + +
+ {new Date(replacement.startDate).toLocaleDateString('fr-FR')} + → + {new Date(replacement.endDate).toLocaleDateString('fr-FR')} +
+ {#if replacement.status === 'active'} + + {#if days > 1} + {days} jours restants + {:else if days === 1} + 1 jour restant + {:else if days === 0} + Dernier jour + {:else} + Expiré + {/if} + + {/if} +
+ {#if replacement.status === 'active'} + Actif + {:else} + Terminé + {/if} + + {#if replacement.status === 'active'} + + {/if} +
+
+ + {/if} +