04be659d6b
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
129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import { useEffect, useState } from "preact/hooks";
|
|
|
|
type Perm = { id: string; nom: string };
|
|
type Role = { id: number; nom: string; permissions: string[] };
|
|
|
|
const ROLE_COLORS = [
|
|
"#22c55e",
|
|
"#d4a017",
|
|
"#e07020",
|
|
"#8b5cf6",
|
|
"#06b6d4",
|
|
"#ec4899",
|
|
];
|
|
|
|
function roleColor(roleId: number): string {
|
|
return ROLE_COLORS[(roleId - 1) % ROLE_COLORS.length];
|
|
}
|
|
|
|
export default function AdminPermissions() {
|
|
const [permissions, setPermissions] = useState<Perm[]>([]);
|
|
const [roles, setRoles] = useState<Role[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
try {
|
|
const [pRes, rRes] = await Promise.all([
|
|
fetch("/admin/api/permissions"),
|
|
fetch("/admin/api/roles"),
|
|
]);
|
|
if (!pRes.ok) throw new Error("Impossible de charger les permissions");
|
|
setPermissions(await pRes.json());
|
|
if (rRes.ok) setRoles(await rRes.json());
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : "Erreur");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
load();
|
|
}, []);
|
|
|
|
function rolesForPerm(permId: string): Role[] {
|
|
return roles.filter((r) => r.permissions.includes(permId));
|
|
}
|
|
|
|
const MAX_ROLE_CHIPS = 2;
|
|
|
|
return (
|
|
<div class="page-content">
|
|
<h2 class="page-title">Permissions</h2>
|
|
|
|
<div class="info-note" style="margin-top: 0; margin-bottom: 1.25rem">
|
|
<p>
|
|
Les permissions sont définies statiquement par le serveur.
|
|
</p>
|
|
<p class="info-note-dim">
|
|
Elles ne peuvent pas être créées ou supprimées via l'API.
|
|
</p>
|
|
</div>
|
|
|
|
{error && <p class="state-error">{error}</p>}
|
|
|
|
{loading
|
|
? <p class="state-loading">Chargement…</p>
|
|
: (
|
|
<div class="data-table-wrap">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>idPermission</th>
|
|
<th>nomPermission</th>
|
|
<th>Rôles associés</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{permissions.map((p) => {
|
|
const associated = rolesForPerm(p.id);
|
|
const shown = associated.slice(0, MAX_ROLE_CHIPS);
|
|
const overflow = associated.length - MAX_ROLE_CHIPS;
|
|
return (
|
|
<tr key={p.id}>
|
|
<td>
|
|
<span
|
|
class="col-promo"
|
|
style="font-family: monospace"
|
|
>
|
|
{p.id}
|
|
</span>
|
|
</td>
|
|
<td>{p.nom}</td>
|
|
<td>
|
|
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem">
|
|
{shown.map((r) => (
|
|
<span
|
|
key={r.id}
|
|
class="role-chip"
|
|
style={`border-color: ${
|
|
roleColor(r.id)
|
|
}; color: ${roleColor(r.id)}`}
|
|
>
|
|
{r.nom}
|
|
</span>
|
|
))}
|
|
{overflow > 0 && (
|
|
<span
|
|
class="col-dim"
|
|
style="font-size: 0.72rem; margin-left: 0.2rem"
|
|
>
|
|
+{overflow}
|
|
</span>
|
|
)}
|
|
{associated.length === 0 && (
|
|
<span class="col-dim">—</span>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|