feat(app): add studentOnly pages and new routes

Add routes for modules, users, notes import, recap, and islands edit.
Update middleware to filter pages based on user role.

feat(admin): add modal for assigning teaching, replace delete icon with SVG

refactor(server): rename port variable to uppercase and add env support
feat(admin): add enseignants, users, filtering and role colors

refactor(AdminRoles): improve role UI and add permission mapping

feat(admin-users): add role colors, role filter, and modal for creating users

feat(admin): add EditModule component for module editing

feat(admin): add EditUser page for editing users and managing enseignements

feat(promo-select): display id and name in options for promo dropdown

feat: add edit module/user routes, inline coeff editing, UI tweaks

refactor: UI – icons, modal overlay, grid, subtitles, import margin
This commit is contained in:
2026-04-29 09:12:55 +02:00
parent f71128a7f3
commit 04be659d6b
22 changed files with 1455 additions and 245 deletions
@@ -25,7 +25,7 @@ export default function AdminPromotions() {
const [anneeSco, setAnneeSco] = useState("");
const generatedId = anneeSco.trim()
? `${selectedAnnee}${selectedFiliere}${anneeSco.trim()}`
? `${selectedAnnee}${selectedFiliere}${anneeSco.trim().replace(/\//g, "-")}`
: "";
async function load() {
@@ -101,7 +101,7 @@ export default function AdminPromotions() {
<div class="promo-builder">
<p class="promo-builder-title">Créer une promotion</p>
<p class="promo-builder-subtitle">
POST /promotions idPromo est généré automatiquement
idPromo est généré automatiquement
</p>
<div class="promo-builder-row">
@@ -141,7 +141,7 @@ export default function AdminPromotions() {
<label>Année scolaire</label>
<input
class="form-input"
placeholder="ex: 25/26, 24/27…"
placeholder="ex: 25-26, 24-27…"
value={anneeSco}
onInput={(e) => setAnneeSco((e.target as HTMLInputElement).value)}
style="min-width: 9rem"
@@ -220,7 +220,18 @@ export default function AdminPromotions() {
class="btn btn-sm btn-danger"
onClick={() => deletePromo(p.id)}
>
🗑
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M3 6h18" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
<rect x="5" y="6" width="14" height="16" rx="1" />
</svg>
</button>
</td>
</tr>
@@ -67,6 +67,7 @@ export default function ConsultStudents() {
class="btn btn-primary"
href="/students/upload"
f-partial="/students/partials/upload"
style="margin-left: auto"
>
Importer xlsx
</a>
@@ -128,14 +129,34 @@ export default function ConsultStudents() {
href={`/students/edit/${s.numEtud}`}
f-client-nav={false}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
</svg>
</a>
<button
type="button"
class="btn btn-sm btn-danger"
onClick={() => deleteStudent(s.numEtud)}
>
🗑
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M3 6h18" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
<rect x="5" y="6" width="14" height="16" rx="1" />
</svg>
</button>
</div>
</td>
@@ -147,8 +147,6 @@ export default function EditStudents({ numEtud }: Props) {
{/* Section 1: Informations générales */}
<div class="edit-section">
<p class="edit-section-title">Informations générales</p>
<p class="edit-section-subtitle">PUT /students/{"{numEtud}"}</p>
<div class="form-grid">
<div class="form-field">
<label>Nom</label>
@@ -212,9 +210,6 @@ export default function EditStudents({ numEtud }: Props) {
{/* Section 2: Spécialisations */}
<div class="edit-section">
<p class="edit-section-title">Spécialisations</p>
<p class="edit-section-subtitle">
GET·POST·DELETE /spe5a plusieurs modules possibles
</p>
<p
class="state-empty"
style="padding: 1rem 0; text-align: left"
@@ -226,9 +221,6 @@ export default function EditStudents({ numEtud }: Props) {
{/* Section 3: Notes lecture seule */}
<div class="edit-section">
<p class="edit-section-title">Notes (lecture seule)</p>
<p class="edit-section-subtitle">
GET /students/{"{numEtud}"}/notes voir récap complet
</p>
<div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap">
<span class="col-dim" style="font-size: 0.82rem">
Voir le récap complet des notes et moyennes de cet étudiant