-
Bienvenue sur Classeo
-
Application de gestion scolaire
-
-
-
Compteur: {count}
-
- Incrementer
-
+{#if isChecking}
+
+
-
-
+
+{:else}
+
+
+
Bienvenue sur Classeo
+
L'application de gestion scolaire qui simplifie le quotidien des familles et des enseignants
+
+
+
+ Se connecter
+
+
+
+
+
+
💚
+
Score Serenite
+
Suivez la scolarite de votre enfant en un coup d'oeil
+
+
+
📅
+
Emploi du temps
+
Consultez les cours et les modifications en temps reel
+
+
+
📝
+
Notes et devoirs
+
Restez informe des resultats et des travaux a faire
+
+
+
+
+{/if}
+
+
diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte
new file mode 100644
index 0000000..1d81d7c
--- /dev/null
+++ b/frontend/src/routes/dashboard/+layout.svelte
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+ {@render children()}
+
+
+
+
+
diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte
new file mode 100644
index 0000000..1a8c990
--- /dev/null
+++ b/frontend/src/routes/dashboard/+page.svelte
@@ -0,0 +1,118 @@
+
+
+
+ Tableau de bord - Classeo
+
+
+
+
+
+ Démo - Changer de rôle :
+ switchRole('parent')}>Parent
+ switchRole('teacher')}>Enseignant
+ switchRole('student')}>Élève
+ switchRole('admin')}>Admin
+
+
+{#if userRole === 'parent'}
+
+{:else if userRole === 'teacher'}
+
+{:else if userRole === 'student'}
+
+{:else if userRole === 'admin' || userRole === 'direction'}
+
+{/if}
+
+
diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte
index d483bac..5772fc1 100644
--- a/frontend/src/routes/login/+page.svelte
+++ b/frontend/src/routes/login/+page.svelte
@@ -111,7 +111,7 @@
if (result.success) {
// Rediriger vers le dashboard
- goto('/');
+ goto('/dashboard');
} else if (result.error) {
// Gérer les différents types d'erreur
switch (result.error.type) {
diff --git a/frontend/src/routes/settings/+layout.svelte b/frontend/src/routes/settings/+layout.svelte
index e5c750f..b0a3bd6 100644
--- a/frontend/src/routes/settings/+layout.svelte
+++ b/frontend/src/routes/settings/+layout.svelte
@@ -15,7 +15,7 @@
}
function goHome() {
- goto('/');
+ goto('/dashboard');
}
@@ -26,6 +26,8 @@
Classeo
@@ -58,7 +60,10 @@
.settings-header {
background: var(--surface-elevated, #fff);
border-bottom: 1px solid var(--border-subtle, #e2e8f0);
- padding: 0 24px;
+ padding: 0 1.5rem;
+ position: sticky;
+ top: 0;
+ z-index: 100;
}
.header-content {
@@ -74,39 +79,59 @@
background: none;
border: none;
cursor: pointer;
- padding: 8px 0;
+ padding: 0.5rem 0;
}
.logo-text {
- font-size: 20px;
+ font-size: 1.25rem;
font-weight: 700;
- color: var(--accent-primary, hsl(199, 89%, 48%));
+ color: var(--accent-primary, #0ea5e9);
}
.header-nav {
display: flex;
align-items: center;
- gap: 16px;
+ gap: 1rem;
+ }
+
+ .nav-link {
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--text-secondary, #64748b);
+ text-decoration: none;
+ border-radius: 0.5rem;
+ transition: all 0.2s;
+ }
+
+ .nav-link:hover {
+ color: var(--text-primary, #1f2937);
+ background: var(--surface-primary, #f8fafc);
+ }
+
+ .nav-link.active {
+ color: var(--accent-primary, #0ea5e9);
+ background: var(--accent-primary-light, #e0f2fe);
}
.logout-button {
display: inline-flex;
align-items: center;
- gap: 8px;
- padding: 8px 16px;
- font-size: 14px;
+ gap: 0.5rem;
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary, #64748b);
background: transparent;
border: 1px solid var(--border-subtle, #e2e8f0);
- border-radius: 8px;
+ border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.logout-button:hover:not(:disabled) {
- color: var(--color-alert, hsl(0, 72%, 51%));
- border-color: var(--color-alert, hsl(0, 72%, 51%));
+ color: var(--color-alert, #ef4444);
+ border-color: var(--color-alert, #ef4444);
}
.logout-button:disabled {
@@ -132,4 +157,20 @@
transform: rotate(360deg);
}
}
+
+ @media (max-width: 768px) {
+ .header-content {
+ flex-wrap: wrap;
+ height: auto;
+ padding: 0.75rem 0;
+ gap: 0.75rem;
+ }
+
+ .header-nav {
+ width: 100%;
+ justify-content: flex-end;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ }
+ }
diff --git a/frontend/tests/unit/lib/data/demo-data.test.ts b/frontend/tests/unit/lib/data/demo-data.test.ts
new file mode 100644
index 0000000..e31b88f
--- /dev/null
+++ b/frontend/tests/unit/lib/data/demo-data.test.ts
@@ -0,0 +1,87 @@
+import { describe, it, expect } from 'vitest';
+import demoData from '$lib/data/demo-data.json';
+
+describe('demo-data.json', () => {
+ describe('serenityScore', () => {
+ it('should have a value between 0 and 100', () => {
+ expect(demoData.serenityScore.value).toBeGreaterThanOrEqual(0);
+ expect(demoData.serenityScore.value).toBeLessThanOrEqual(100);
+ });
+
+ it('should have a valid emoji', () => {
+ expect(['💚', '🟡', '🔴']).toContain(demoData.serenityScore.emoji);
+ });
+
+ it('should have a valid trend', () => {
+ expect(['stable', 'up', 'down']).toContain(demoData.serenityScore.trend);
+ });
+
+ it('should have components with scores and labels', () => {
+ const { components } = demoData.serenityScore;
+
+ expect(components).toHaveProperty('notes');
+ expect(components).toHaveProperty('absences');
+ expect(components).toHaveProperty('devoirs');
+
+ for (const [, component] of Object.entries(components)) {
+ expect(component).toHaveProperty('score');
+ expect(component).toHaveProperty('label');
+ expect(typeof component.score).toBe('number');
+ expect(typeof component.label).toBe('string');
+ expect(component.score).toBeGreaterThanOrEqual(0);
+ expect(component.score).toBeLessThanOrEqual(100);
+ }
+ });
+ });
+
+ describe('schedule', () => {
+ it('should have today array with schedule items', () => {
+ expect(Array.isArray(demoData.schedule.today)).toBe(true);
+ expect(demoData.schedule.today.length).toBeGreaterThan(0);
+ });
+
+ it('should have valid schedule items with time, subject, and room', () => {
+ for (const item of demoData.schedule.today) {
+ expect(item).toHaveProperty('time');
+ expect(item).toHaveProperty('subject');
+ expect(item).toHaveProperty('room');
+ expect(typeof item.time).toBe('string');
+ expect(typeof item.subject).toBe('string');
+ expect(typeof item.room).toBe('string');
+ }
+ });
+ });
+
+ describe('grades', () => {
+ it('should have recent array with grade items', () => {
+ expect(Array.isArray(demoData.grades.recent)).toBe(true);
+ });
+
+ it('should have valid grade items', () => {
+ for (const item of demoData.grades.recent) {
+ expect(item).toHaveProperty('subject');
+ expect(item).toHaveProperty('value');
+ expect(item).toHaveProperty('max');
+ expect(item).toHaveProperty('date');
+ expect(typeof item.value).toBe('number');
+ expect(typeof item.max).toBe('number');
+ }
+ });
+ });
+
+ describe('homework', () => {
+ it('should have upcoming array with homework items', () => {
+ expect(Array.isArray(demoData.homework.upcoming)).toBe(true);
+ });
+
+ it('should have valid homework items', () => {
+ for (const item of demoData.homework.upcoming) {
+ expect(item).toHaveProperty('subject');
+ expect(item).toHaveProperty('title');
+ expect(item).toHaveProperty('dueDate');
+ expect(item).toHaveProperty('status');
+ expect(['pending', 'done', 'late']).toContain(item.status);
+ }
+ });
+ });
+});
diff --git a/frontend/tests/unit/lib/features/dashboard/serenity-score.test.ts b/frontend/tests/unit/lib/features/dashboard/serenity-score.test.ts
new file mode 100644
index 0000000..47006d5
--- /dev/null
+++ b/frontend/tests/unit/lib/features/dashboard/serenity-score.test.ts
@@ -0,0 +1,106 @@
+import { describe, it, expect } from 'vitest';
+import {
+ calculateSerenityScore,
+ getSerenityEmoji,
+ getSerenityLabel,
+ isValidScore,
+ getTrendIcon
+} from '$lib/features/dashboard/serenity-score';
+
+describe('serenity-score utilities', () => {
+ describe('calculateSerenityScore', () => {
+ it('should calculate score correctly with all 100s', () => {
+ const score = calculateSerenityScore({ notes: 100, absences: 100, devoirs: 100 });
+ expect(score).toBe(100);
+ });
+
+ it('should calculate score correctly with all 0s', () => {
+ const score = calculateSerenityScore({ notes: 0, absences: 0, devoirs: 0 });
+ expect(score).toBe(0);
+ });
+
+ it('should calculate score with weighted components', () => {
+ // Notes 90 × 0.4 = 36
+ // Absences 95 × 0.3 = 28.5
+ // Devoirs 70 × 0.3 = 21
+ // Total = 85.5, rounded to 86
+ const score = calculateSerenityScore({ notes: 90, absences: 95, devoirs: 70 });
+ expect(score).toBe(86);
+ });
+
+ it('should round the score to nearest integer', () => {
+ const score = calculateSerenityScore({ notes: 50, absences: 50, devoirs: 50 });
+ expect(score).toBe(50);
+ expect(Number.isInteger(score)).toBe(true);
+ });
+ });
+
+ describe('getSerenityEmoji', () => {
+ it('should return green emoji for scores >= 70', () => {
+ expect(getSerenityEmoji(70)).toBe('💚');
+ expect(getSerenityEmoji(85)).toBe('💚');
+ expect(getSerenityEmoji(100)).toBe('💚');
+ });
+
+ it('should return yellow emoji for scores 40-69', () => {
+ expect(getSerenityEmoji(40)).toBe('🟡');
+ expect(getSerenityEmoji(55)).toBe('🟡');
+ expect(getSerenityEmoji(69)).toBe('🟡');
+ });
+
+ it('should return red emoji for scores < 40', () => {
+ expect(getSerenityEmoji(0)).toBe('🔴');
+ expect(getSerenityEmoji(25)).toBe('🔴');
+ expect(getSerenityEmoji(39)).toBe('🔴');
+ });
+ });
+
+ describe('getSerenityLabel', () => {
+ it('should return Excellent for scores >= 70', () => {
+ expect(getSerenityLabel(70)).toBe('Excellent');
+ expect(getSerenityLabel(100)).toBe('Excellent');
+ });
+
+ it('should return A surveiller for scores 40-69', () => {
+ expect(getSerenityLabel(40)).toBe('A surveiller');
+ expect(getSerenityLabel(69)).toBe('A surveiller');
+ });
+
+ it('should return Attention requise for scores < 40', () => {
+ expect(getSerenityLabel(0)).toBe('Attention requise');
+ expect(getSerenityLabel(39)).toBe('Attention requise');
+ });
+ });
+
+ describe('isValidScore', () => {
+ it('should return true for valid scores', () => {
+ expect(isValidScore(0)).toBe(true);
+ expect(isValidScore(50)).toBe(true);
+ expect(isValidScore(100)).toBe(true);
+ });
+
+ it('should return false for scores below 0', () => {
+ expect(isValidScore(-1)).toBe(false);
+ expect(isValidScore(-100)).toBe(false);
+ });
+
+ it('should return false for scores above 100', () => {
+ expect(isValidScore(101)).toBe(false);
+ expect(isValidScore(200)).toBe(false);
+ });
+ });
+
+ describe('getTrendIcon', () => {
+ it('should return up arrow for up trend', () => {
+ expect(getTrendIcon('up')).toBe('↑');
+ });
+
+ it('should return down arrow for down trend', () => {
+ expect(getTrendIcon('down')).toBe('↓');
+ });
+
+ it('should return right arrow for stable trend', () => {
+ expect(getTrendIcon('stable')).toBe('→');
+ });
+ });
+});
diff --git a/frontend/tests/unit/page.test.ts b/frontend/tests/unit/page.test.ts
deleted file mode 100644
index 7c3e5fb..0000000
--- a/frontend/tests/unit/page.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { render, screen } from '@testing-library/svelte';
-import { describe, expect, it } from 'vitest';
-import Page from '../../src/routes/+page.svelte';
-
-describe('Home Page', () => {
- it('renders the welcome message', () => {
- render(Page);
-
- expect(screen.getByRole('heading', { name: 'Bienvenue sur Classeo' })).toBeTruthy();
- });
-
- it('renders the description', () => {
- render(Page);
-
- expect(screen.getByText('Application de gestion scolaire')).toBeTruthy();
- });
-
- it('starts counter at 0', () => {
- render(Page);
-
- expect(screen.getByText('Compteur: 0')).toBeTruthy();
- });
-});