Permet aux administrateurs de désigner un enseignant remplaçant pour un autre enseignant absent, sur des classes et matières précises, pour une période donnée. Le dashboard enseignant affiche les remplacements actifs avec les noms de classes/matières au lieu des identifiants bruts. Inclut les corrections de la code review : - Requête findActiveByTenant qui excluait les remplacements en cours mais incluait les futurs (manquait start_date <= :at) - Validation tenant et rôle enseignant dans le handler de désignation pour empêcher l'affectation cross-tenant ou de non-enseignants - Validation structurée du payload classes (Assert\Collection + UUID) pour éviter les erreurs serveur sur payloads malformés - API replaced-classes enrichie avec les noms classe/matière
254 lines
5.8 KiB
Svelte
254 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import DashboardSection from '$lib/components/molecules/DashboardSection.svelte';
|
|
import SkeletonList from '$lib/components/atoms/Skeleton/SkeletonList.svelte';
|
|
|
|
let {
|
|
isLoading = false,
|
|
hasRealData = false,
|
|
establishmentName = ''
|
|
}: {
|
|
isLoading?: boolean;
|
|
hasRealData?: boolean;
|
|
establishmentName?: string;
|
|
} = $props();
|
|
</script>
|
|
|
|
<div class="dashboard-admin">
|
|
<header class="dashboard-header">
|
|
<h1>Administration</h1>
|
|
{#if establishmentName}
|
|
<p class="dashboard-subtitle">{establishmentName}</p>
|
|
{:else}
|
|
<p class="dashboard-subtitle">Bienvenue dans votre espace d'administration</p>
|
|
{/if}
|
|
</header>
|
|
|
|
<div class="quick-actions">
|
|
<h2 class="sr-only">Actions de configuration</h2>
|
|
<div class="action-cards">
|
|
<a class="action-card" href="/admin/users">
|
|
<span class="action-icon">👥</span>
|
|
<span class="action-label">Gérer les utilisateurs</span>
|
|
<span class="action-hint">Inviter et gérer</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/classes">
|
|
<span class="action-icon">🏫</span>
|
|
<span class="action-label">Configurer les classes</span>
|
|
<span class="action-hint">Créer et gérer</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/subjects">
|
|
<span class="action-icon">📚</span>
|
|
<span class="action-label">Gérer les matières</span>
|
|
<span class="action-hint">Créer et gérer</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/assignments">
|
|
<span class="action-icon">📋</span>
|
|
<span class="action-label">Affectations</span>
|
|
<span class="action-hint">Enseignants et classes</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/replacements">
|
|
<span class="action-icon">🔄</span>
|
|
<span class="action-label">Remplacements</span>
|
|
<span class="action-hint">Enseignants absents</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/academic-year/periods">
|
|
<span class="action-icon">📅</span>
|
|
<span class="action-label">Périodes scolaires</span>
|
|
<span class="action-hint">Trimestres et semestres</span>
|
|
</a>
|
|
<a class="action-card" href="/admin/pedagogy">
|
|
<span class="action-icon">🎓</span>
|
|
<span class="action-label">Pédagogie</span>
|
|
<span class="action-hint">Mode de notation</span>
|
|
</a>
|
|
<div class="action-card disabled" aria-disabled="true">
|
|
<span class="action-icon">📤</span>
|
|
<span class="action-label">Importer des données</span>
|
|
<span class="action-hint">Bientôt disponible</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<DashboardSection
|
|
title="Utilisateurs"
|
|
isPlaceholder={!hasRealData}
|
|
placeholderMessage="Les statistiques utilisateurs s'afficheront une fois les comptes créés"
|
|
>
|
|
{#if hasRealData}
|
|
{#if isLoading}
|
|
<SkeletonList items={3} message="Chargement des statistiques..." />
|
|
{:else}
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<span class="stat-value">--</span>
|
|
<span class="stat-label">Élèves</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-value">--</span>
|
|
<span class="stat-label">Enseignants</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-value">--</span>
|
|
<span class="stat-label">Parents</span>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</DashboardSection>
|
|
|
|
<DashboardSection
|
|
title="Configuration"
|
|
isPlaceholder={!hasRealData}
|
|
placeholderMessage="L'état de la configuration sera affiché ici"
|
|
>
|
|
{#if hasRealData && isLoading}
|
|
<SkeletonList items={4} message="Vérification de la configuration..." />
|
|
{/if}
|
|
</DashboardSection>
|
|
|
|
<DashboardSection
|
|
title="Activité récente"
|
|
isPlaceholder={!hasRealData}
|
|
placeholderMessage="L'historique des actions s'affichera ici"
|
|
>
|
|
{#if hasRealData && isLoading}
|
|
<SkeletonList items={5} message="Chargement de l'activité..." />
|
|
{/if}
|
|
</DashboardSection>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.dashboard-admin {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.dashboard-header {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.dashboard-header h1 {
|
|
margin: 0;
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.dashboard-subtitle {
|
|
margin: 0.25rem 0 0;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
.quick-actions {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.action-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.action-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 1.25rem 1rem;
|
|
background: white;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 0.75rem;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.action-card:not(.disabled):hover {
|
|
border-color: #3b82f6;
|
|
background: #eff6ff;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.action-card.disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.action-icon {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.action-label {
|
|
font-weight: 600;
|
|
color: #374151;
|
|
text-align: center;
|
|
}
|
|
|
|
.action-hint {
|
|
font-size: 0.75rem;
|
|
color: #6b7280;
|
|
text-align: center;
|
|
}
|
|
|
|
.dashboard-grid {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.dashboard-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.dashboard-grid {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.stat-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 1rem;
|
|
background: #f9fafb;
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.75rem;
|
|
color: #6b7280;
|
|
}
|
|
</style>
|