Les utilisateurs Classeo étaient limités à un seul rôle, alors que dans la réalité scolaire un directeur peut aussi être enseignant, ou un parent peut avoir un rôle vie scolaire. Cette limitation obligeait à créer des comptes distincts par fonction. Le modèle User supporte désormais plusieurs rôles simultanés avec basculement via le header. L'admin peut attribuer/retirer des rôles depuis l'interface de gestion, avec des garde-fous : pas d'auto- destitution, pas d'escalade de privilèges (seul SUPER_ADMIN peut attribuer SUPER_ADMIN), vérification du statut actif pour le switch de rôle, et TTL explicite sur le cache de rôle actif.
169 lines
4.6 KiB
Svelte
169 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import type { DemoData } from '$types';
|
|
import demoData from '$lib/data/demo-data.json';
|
|
import DashboardParent from '$lib/components/organisms/Dashboard/DashboardParent.svelte';
|
|
import DashboardTeacher from '$lib/components/organisms/Dashboard/DashboardTeacher.svelte';
|
|
import DashboardStudent from '$lib/components/organisms/Dashboard/DashboardStudent.svelte';
|
|
import DashboardAdmin from '$lib/components/organisms/Dashboard/DashboardAdmin.svelte';
|
|
import { getActiveRole, getIsLoading } from '$features/roles/roleContext.svelte';
|
|
|
|
type DashboardView = 'parent' | 'teacher' | 'student' | 'admin';
|
|
|
|
const ROLE_TO_VIEW: Record<string, DashboardView> = {
|
|
ROLE_PARENT: 'parent',
|
|
ROLE_PROF: 'teacher',
|
|
ROLE_ELEVE: 'student',
|
|
ROLE_ADMIN: 'admin',
|
|
ROLE_SUPER_ADMIN: 'admin',
|
|
ROLE_VIE_SCOLAIRE: 'admin',
|
|
ROLE_SECRETARIAT: 'admin'
|
|
};
|
|
|
|
// Fallback demo role when not authenticated or roles not loaded
|
|
let demoRole = $state<DashboardView>('parent');
|
|
|
|
// Use real role context if available, otherwise fallback to demo
|
|
let dashboardView = $derived<DashboardView>(
|
|
ROLE_TO_VIEW[getActiveRole() ?? ''] ?? demoRole
|
|
);
|
|
|
|
// True when roles come from the API (user is authenticated)
|
|
let hasRoleContext = $derived(getActiveRole() !== null);
|
|
|
|
// True when role loading has started (indicates an authenticated user)
|
|
let isRoleLoading = $derived(getIsLoading());
|
|
|
|
// Simulated first login detection (in real app, this comes from API)
|
|
let isFirstLogin = $state(true);
|
|
|
|
// Serenity score preference (in real app, this is stored in backend)
|
|
let serenityEnabled = $state(true);
|
|
|
|
// Use demo data for now (no real data available yet)
|
|
const hasRealData = false;
|
|
|
|
// Demo child name for personalized messages
|
|
const childName = 'Emma';
|
|
|
|
function handleToggleSerenity(enabled: boolean) {
|
|
serenityEnabled = enabled;
|
|
}
|
|
|
|
// Cast demo data to proper type
|
|
const typedDemoData = demoData as DemoData;
|
|
|
|
function switchDemoRole(role: DashboardView) {
|
|
demoRole = role;
|
|
isFirstLogin = false;
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Tableau de bord - Classeo</title>
|
|
</svelte:head>
|
|
|
|
<!-- Loading state when roles are being fetched -->
|
|
{#if isRoleLoading}
|
|
<div class="loading-state">
|
|
<div class="spinner"></div>
|
|
<p>Chargement du tableau de bord...</p>
|
|
</div>
|
|
{:else if !hasRoleContext && !isRoleLoading}
|
|
<!-- Demo role switcher shown when not authenticated (no role context from API) -->
|
|
<!-- The RoleSwitcher in the header handles multi-role switching for authenticated users -->
|
|
<div class="demo-controls">
|
|
<span class="demo-label">Démo - Changer de rôle :</span>
|
|
<button class:active={demoRole === 'parent'} onclick={() => switchDemoRole('parent')}>Parent</button>
|
|
<button class:active={demoRole === 'teacher'} onclick={() => switchDemoRole('teacher')}>Enseignant</button>
|
|
<button class:active={demoRole === 'student'} onclick={() => switchDemoRole('student')}>Élève</button>
|
|
<button class:active={demoRole === 'admin'} onclick={() => switchDemoRole('admin')}>Admin</button>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if dashboardView === 'parent'}
|
|
<DashboardParent
|
|
demoData={typedDemoData}
|
|
{isFirstLogin}
|
|
isLoading={false}
|
|
{hasRealData}
|
|
{serenityEnabled}
|
|
{childName}
|
|
onToggleSerenity={handleToggleSerenity}
|
|
/>
|
|
{:else if dashboardView === 'teacher'}
|
|
<DashboardTeacher isLoading={false} {hasRealData} />
|
|
{:else if dashboardView === 'student'}
|
|
<DashboardStudent demoData={typedDemoData} isLoading={false} {hasRealData} isMinor={true} />
|
|
{:else if dashboardView === 'admin'}
|
|
<DashboardAdmin
|
|
isLoading={false}
|
|
{hasRealData}
|
|
establishmentName="École Alpha"
|
|
/>
|
|
{/if}
|
|
|
|
<style>
|
|
.loading-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 3rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.spinner {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border: 3px solid #e5e7eb;
|
|
border-top-color: #3b82f6;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.demo-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1rem;
|
|
margin-bottom: 1rem;
|
|
background: #fef3c7;
|
|
border: 1px solid #fcd34d;
|
|
border-radius: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.demo-label {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
color: #92400e;
|
|
}
|
|
|
|
.demo-controls button {
|
|
padding: 0.375rem 0.75rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
background: white;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 0.375rem;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.demo-controls button:hover {
|
|
background: #f3f4f6;
|
|
}
|
|
|
|
.demo-controls button.active {
|
|
background: #3b82f6;
|
|
border-color: #3b82f6;
|
|
color: white;
|
|
}
|
|
</style>
|