diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c9fb8..fe5a8b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,142 +4,49 @@ Toutes les évolutions notables de **Dalibi** sont consignées ici. Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le versionnage respecte [SemVer](https://semver.org/lang/fr/). -## [1.2.0] — 2026-09-03 +## [1.0.0] — 2026-09-06 -Refonte du module **Statistiques** : chaque onglet gagne des graphiques plus lisibles, -accessibles (palette validée en vision déficiente, mode sombre), interactifs et, là où -c'est pertinent, ventilés par sexe et filtrables. - -### Ajouté -- **Comparaisons** : cartes de variation vs année précédente, séparation en deux lectures - claires — *Performance* (réussite, recouvrement) et *Déperdition* (redoublement, - abandon) — l'année en cours n'étant pas tracée pour les taux de fin d'année encore - indécis. Le jeu de démonstration sème deux années passées pour donner de vraies tendances. -- **Géographie** : treemap hiérarchique région → préfecture, une teinte par région - (nuancée selon l'effectif) et une légende ; KPI de concentration (couverture, part du - Grand Lomé, régions, préfectures). -- **Réussite & examens** : examens officiels (CEPD, BEPC, BAC) avec résultats - admis / échoué / absent et taux d'admission ; **répartition des mentions** enfin - alimentée (quatre paliers). -- **Effectifs & parité** : **pyramide des âges** divergente garçons / filles, **origine - géographique** (top villes) ventilée par sexe, et **filtre par cycle** (Maternelle / - Primaire / Collège / Lycée) avec tri sur le graphe des classes. -- **Statistiques élèves** (module Élèves) refondue : vrais graphiques thémés, mode sombre, - donut de parité, histogramme d'âge, effectifs par classe filtrables et empilés par sexe, - KPI d'âge moyen et de sur-âge. -- Âge attendu par classe renseigné, ce qui active le calcul du **sur-âge** (retard scolaire). - -### Corrigé -- **Répartition des mentions vide** : les libellés (« Très bien », « Assez bien »…) sont - désormais normalisés avant d'être agrégés sur les quatre paliers standard. -- **Lisibilité de la géographie** : couleurs par région (les faibles effectifs, en teinte - unique quasi blanche, étaient invisibles) et libellés du treemap en graisse normale. - -## [1.1.0] — 2026-09-03 - -### Ajouté -- **Jeu de démonstration complet** (`DemoSeeder`) : une année scolaire vivante sur toutes - les classes actives (au moins 20 élèves par classe), avec dossiers d'élèves complets, - programme et affectations des enseignants, évaluations notées et moyennes par matière, - bulletins figés, facturation et paiements, présences, emploi du temps, calendrier, - cycles de paie et dossiers courants (justificatifs, réclamations, primes, documents). - Noms, prénoms et villes réels du Togo (divisions administratives officielles), - recrutement pondéré autour de Lomé. Seeder idempotent, déterministe, et interdit en - production. Lancement : `php artisan db:seed --class=DemoSeeder`. - -### Corrigé -- **Bulletin — colonne Composition toujours vide** : la colonne de source « composition » - était résolue sur une clé absente du bulletin figé (la valeur y est stockée sous - « compo »). La colonne restait vide **même quand la note existait**, quelle que soit la - période ou le barème. Correctif d'affichage : aucun bulletin à régénérer. -- **Bulletin — logo de l'en-tête surdimensionné** : les styles de l'en-tête ministériel - (dont le plafond de taille du logo) n'étaient pas embarqués dans le CSS du bulletin ; le - logo s'affichait à sa taille naturelle et écrasait l'en-tête. -- **Types d'évaluation — catégorie de la « Composition »** : lorsqu'elle avait dérivé vers - « contrôle continu », la composition était diluée dans la note de classe et la - pondération Classe/Compo du barème était ignorée. Rejouer `ReferenceDataSeeder` corrige - la donnée, puis régénérer les bulletins concernés. - -### Modifié -- **Bulletin — la colonne « Classe »** (contrôle continu) s'intitule désormais « Devoir » - par défaut. Seul l'affichage change : la colonne agrège toujours interro + devoir et - alimente la moyenne. Un libellé déjà personnalisé par une école n'est pas écrasé. - -## [1.0.1] — 2026-09-02 - -### Corrigé -- **Couleur des catégories dans les graphiques** : la couleur suivait la *position* - dans le tableau et non la catégorie. Lorsqu'une catégorie vide était filtrée, les - suivantes changeaient de couleur — les filles pouvaient hériter du bleu des garçons, - « Très bien » perdre son vert. - -### Accessibilité -- **Palette revue et validée** (bande de luminosité, chroma, séparation en vision - déficiente, contraste) dans les modes clair *et* sombre. L'ancien couple bleu/violet - était indistinguable en deutéranopie. -- **Légendes ajoutées** aux camemberts qui n'en avaient pas : l'identité ne repose plus - sur la seule couleur. -- Les répartitions à plus de trois catégories (modes de paiement, régions) passent en - **barres étiquetées** ; les mentions, données ordonnées, en **rampe séquentielle**. -- **Mode sombre** : couleurs d'axes et de grille désormais adaptées au thème, au lieu de - valeurs claires codées en dur (la grille était quasi invisible sur fond sombre). -- L'animation d'entrée des camemberts est retirée : elle laissait le graphique **vide** - lorsque les images d'animation sont ralenties (onglet en arrière-plan, économie - d'énergie, `prefers-reduced-motion`). -## [1.0.0] — 2026-09-02 - -Première version stable. Dalibi couvre le cycle complet de gestion d'un établissement -scolaire, de l'inscription de l'élève au bulletin, en passant par l'écolage et la paie. +Première version stable de **Dalibi**, système de gestion scolaire pensé pour les +établissements togolais 🇹🇬 — de l'inscription de l'élève au bulletin, en passant par +l'écolage, la paie et le pilotage. ### Scolarité -- **Élèves & inscriptions** : dossiers complets (état civil, parents, fiche médicale, pièces - justificatives sur disque privé), import CSV en masse, inscriptions rattachées à l'année - académique, effectifs et listes de classe, passage de classe en fin d'année, bourses. -- **Notes & bulletins** : saisie par matière et évaluation, calcul des moyennes paramétrable - (trimestre ou semestre **par type de classe**), bulletins PDF fidèles avec modèle et - en-tête configurables, réclamations de notes. -- **Examens** : modèles d'évaluation réutilisables, déploiement par classe, planning - imprimable, examens officiels (CEPD, BEPC, BAC). -- **Présences** : appel par classe et par séance, statistiques d'assiduité, demandes de - permission d'absence avec cycle de révision. -- **Emploi du temps** : grille hebdomadaire par classe, export PDF. - -### Gestion -- **Comptabilité & écolage** : structures de frais, encaissements, reçus vérifiables par - code-barres, situation par classe, dépenses, caisses multiples (dont Mobile Money). -- **Personnel & Paie** : fiches employés, grilles salariales et rubriques, cycles de paie - mensuels avec bulletins PDF, CNSS et ITS calculés automatiquement, décaissement écrit - en comptabilité. -- **Archives & documents** : modèles de documents avec en-tête glisser-déposer et filigrane, - archivage avec tags et corbeille, fichiers sur disque privé. -- **Statistiques** : indicateurs alignés sur la carte scolaire (MEPSTA), exports PDF et Excel. - -### Portail parents & élèves -- API REST (`/api/v1`) authentifiée par token, spécifiée en OpenAPI 3.1. -- Isolation stricte : un tuteur n'accède qu'à ses enfants, un élève qu'à ses propres données. -- Activable par établissement, accès délivré compte par compte. - -### Sécurité -- Contrôle d'accès **piloté par les permissions** (~145), avec **une permission par verbe** : - un droit de lecture ne permet jamais de créer, modifier ou supprimer. -- **Cloisonnement enseignant** : notes et appels limités aux classes affectées. -- **Intégrité financière** : gardes anti trop-perçu et transitions du cycle de paie évaluées - sous verrou ; une inscription portant des paiements ne peut pas être supprimée. -- Protection des comptes à privilèges, 2FA, journal d'audit, fichiers sensibles hors du - dossier public. -- Données de démonstration **interdites en production**. - -### Exploitation -- **Sauvegardes** conçues pour la durée : écriture en flux, compression gzip, `pg_dump` natif, - archives d'année scolaire verrouillées, empreinte SHA-256, sauvegarde optionnelle des médias, - restauration sélective par table. -- Déploiement **Docker** (app / worker / scheduler), observabilité optionnelle (logs JSON, - Sentry/GlitchTip). -- Intégration continue : tests, build et vérification des types à chaque contribution. - -### Socle technique -Laravel 12 (PHP 8.3+), React 19 + TypeScript via Inertia.js, Tailwind CSS 4, PostgreSQL, -identifiants UUID. **502 tests** automatisés. - -[1.0.1]: https://github.com/wearedalibi/dalibi/releases/tag/v1.0.1 -[1.0.0]: https://github.com/wearedalibi/dalibi/releases/tag/v1.0.0 +- **Élèves & inscriptions** : dossiers complets (état civil, parents, fiche médicale, + pièces justificatives sur disque privé), import CSV, inscriptions rattachées à l'année + académique, effectifs et listes de classe, passage de classe, bourses. +- **Notes & bulletins** : saisie par matière et évaluation, calcul des moyennes + paramétrable (trimestre ou semestre par type de classe), mentions, bulletins PDF + fidèles avec modèle et en-tête configurables, réclamations de notes. +- **Examens** : modèles d'évaluation réutilisables, planning imprimable, examens officiels + (CEPD, BEPC, BAC) avec inscriptions et résultats. +- **Présences** : appel par séance, statistiques d'assiduité, permissions d'absence avec + cycle de révision. +- **Emploi du temps & calendrier** : grille hebdomadaire par classe, calendrier scolaire. + +### Finances & paie +- **Écolage** : structures de frais, factures, paiements (espèces, Mobile Money, virement, + chèque), reçus vérifiables, recouvrement par classe. +- **Comptabilité** : caisses, journal des transactions, dépenses, situation par classe. +- **Personnel & paie** : fiches employés, grilles et rubriques, cycles de paie et bulletins + (retenues légales CNSS et ITS). + +### Statistiques +- Tableaux de bord par thème : **effectifs & parité** (pyramide des âges par sexe, origine + géographique par sexe, filtre par cycle), **finances & recouvrement** (par classe, + filtrable et détaillé), **réussite & examens**, **encadrement**, **assiduité**, + **comparaisons** pluriannuelles et **géographie** (treemap région → préfecture). +- Graphiques accessibles : palette validée en vision déficiente, mode sombre, légendes, + survols ; exports PDF et Excel. + +### Documents & système +- **En-tête unique** : bulletins, bulletins de paie, certificats, attestations, listes, + emplois du temps, planning et exports statistiques partagent la même en-tête officielle. +- **Sauvegardes** : à la demande ou planifiées, base + médias, restauration sûre, + archives annuelles verrouillées. +- **Sécurité** : rôles et permissions (RBAC), authentification à deux facteurs, + changement de mot de passe imposé aux comptes de démonstration, journal d'audit. +- **Portail** : accès parents/élèves, API du portail. +- **Paramètres** : configuration complète de l'établissement, stockage local ou S3, et + une page **À propos** (version, licence, informations techniques). + +Dalibi est un logiciel libre publié sous licence **GNU GPL v3**. diff --git a/app/Http/Controllers/Eleves/RosterController.php b/app/Http/Controllers/Eleves/RosterController.php index c3c4069..59171e4 100644 --- a/app/Http/Controllers/Eleves/RosterController.php +++ b/app/Http/Controllers/Eleves/RosterController.php @@ -8,6 +8,7 @@ use App\Models\Classroom; use App\Models\Enrollment; use App\Models\School; +use App\Services\DocumentRenderer; use Barryvdh\DomPDF\Facade\Pdf; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -112,14 +113,17 @@ public function export(Request $request) ->sortBy(fn ($e) => $e->student?->lastname) ->values(); - $school = School::where('active', true)->first() ?? School::query()->first(); + $school = School::where('active', true)->first() ?? School::query()->first(); + $renderer = app(DocumentRenderer::class); $pdf = Pdf::loadView('exports.roster', [ - 'school' => $school, - 'classroom' => $classroom, - 'year' => $year, - 'students' => $students, - 'statuses' => Enrollment::ACADEMIC_STATUSES, + 'school' => $school, + 'headerHtml' => $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : '', + 'headerCss' => $renderer->headerCss(), + 'classroom' => $classroom, + 'year' => $year, + 'students' => $students, + 'statuses' => Enrollment::ACADEMIC_STATUSES, ])->setPaper('a4', 'portrait'); return $pdf->stream('effectifs-' . Str::slug($classroom->name . '-' . $year->year) . '.pdf'); diff --git a/app/Http/Controllers/Eleves/TimetableController.php b/app/Http/Controllers/Eleves/TimetableController.php index a19f763..a23f849 100644 --- a/app/Http/Controllers/Eleves/TimetableController.php +++ b/app/Http/Controllers/Eleves/TimetableController.php @@ -6,6 +6,7 @@ use App\Models\AcademicYear; use App\Models\Classroom; use App\Models\School; +use App\Services\DocumentRenderer; use App\Models\Subject; use App\Models\TimetableSlot; use App\Models\User; @@ -84,8 +85,12 @@ public function export(Request $request, string $classId) $grid[$range][$slot->day_of_week] = $slot; } + $renderer = app(DocumentRenderer::class); + $pdf = Pdf::loadView('exports.timetable', [ 'school' => $school, + 'headerHtml' => $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : '', + 'headerCss' => $renderer->headerCss(), 'classroom' => $classroom, 'days' => TimetableSlot::DAYS, 'timeRanges' => $timeRanges, diff --git a/app/Http/Controllers/Examens/EvaluationController.php b/app/Http/Controllers/Examens/EvaluationController.php index 5e6fb48..b7faa1c 100644 --- a/app/Http/Controllers/Examens/EvaluationController.php +++ b/app/Http/Controllers/Examens/EvaluationController.php @@ -220,7 +220,11 @@ public function exportPlanning(Request $request, string $classroomId): \Illumina $school = School::where('active', true)->first(); - $html = view('exports.planning', compact('classroom', 'evaluations', 'school', 'activeYear'))->render(); + $renderer = app(\App\Services\DocumentRenderer::class); + $headerHtml = $school ? $renderer->headerHtml($school, $renderer->resolveVariables($school)) : ''; + $headerCss = $renderer->headerCss(); + + $html = view('exports.planning', compact('classroom', 'evaluations', 'school', 'activeYear', 'headerHtml', 'headerCss'))->render(); return response($html)->header('Content-Type', 'text/html'); } diff --git a/app/Http/Controllers/Parametres/AboutController.php b/app/Http/Controllers/Parametres/AboutController.php new file mode 100644 index 0000000..2716578 --- /dev/null +++ b/app/Http/Controllers/Parametres/AboutController.php @@ -0,0 +1,31 @@ +version ?? '—'; + } + + return Inertia::render('Parametres/About', [ + 'app' => [ + 'name' => config('app.name', 'Dalibi'), + 'version' => $version, + 'laravel' => app()->version(), + 'php' => PHP_VERSION, + 'environment' => app()->environment(), + ], + ]); + } +} diff --git a/app/Http/Controllers/Statistics/StatisticsController.php b/app/Http/Controllers/Statistics/StatisticsController.php index 77ae02f..5c5908e 100644 --- a/app/Http/Controllers/Statistics/StatisticsController.php +++ b/app/Http/Controllers/Statistics/StatisticsController.php @@ -62,6 +62,7 @@ public function export(Request $request, string $section, string $format) $html = view('statistics.report', [ 'header' => $renderer->headerHtml($school, $vars), + 'headerCss' => $renderer->headerCss(), 'section' => $section, 'data' => $data, 'year' => optional(AcademicYear::find($filters['academic_year_id']))->year, diff --git a/app/Services/DocumentRenderer.php b/app/Services/DocumentRenderer.php index 784ad72..4d5d708 100644 --- a/app/Services/DocumentRenderer.php +++ b/app/Services/DocumentRenderer.php @@ -370,8 +370,10 @@ protected function renderSignature(DocumentTemplate $template, array $variables) } /** - * CSS de l'en-tête officiel (ministériel + logo), réutilisable par des - * documents qui n'embarquent pas tout le baseCss (ex. bulletin de paie). + * CSS de l'en-tête officiel (ministériel + logo). **Source unique** : tous les + * documents (bulletin, bulletin de paie, export statistiques, certificats et + * attestations) partagent exactement cette en-tête. Ne pas redéfinir les + * règles `.mh-*` ou `.doc-mheader` ailleurs — {@see baseCss()} la compose. */ public function headerCss(): string { @@ -393,22 +395,12 @@ public function headerCss(): string protected function baseCss(): string { - return <<headerCss() . <<billed ?? 0); $collected = (float) ($stats->collected ?? 0); - // Recouvrement par classe + // Recouvrement par classe (avec cycle + reste, pour filtre et charts détaillés) $byClass = (clone $inv) ->join('classes', 'enrollments.class_id', '=', 'classes.id') - ->selectRaw('classes.name AS name, + ->leftJoin('classroom_types', 'classroom_types.id', '=', 'classes.classroom_type_id') + ->selectRaw('classes.name AS name, classroom_types.name AS cycle, COALESCE(SUM(invoices.total), 0) AS billed, - COALESCE(SUM(invoices.amount_paid), 0) AS collected') - ->groupBy('classes.name') - ->orderByDesc('billed') + COALESCE(SUM(invoices.amount_paid), 0) AS collected, + COALESCE(SUM(invoices.amount_remaining), 0) AS remaining') + ->groupBy('classes.name', 'classroom_types.name', 'classes.expected_age') + ->orderByRaw('classes.expected_age NULLS LAST') + ->orderBy('classes.name') ->get() ->map(fn ($r) => [ 'name' => $r->name, + 'cycle' => $this->cycleLabel($r->cycle), 'billed' => (float) $r->billed, 'collected' => (float) $r->collected, + 'remaining' => (float) $r->remaining, 'rate' => $r->billed > 0 ? round($r->collected / $r->billed * 100, 1) : 0.0, ]); diff --git a/package.json b/package.json index 5e01440..27a7f8a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://www.schemastore.org/package.json", "private": true, - "version": "1.2.0", + "version": "1.0.0", "type": "module", "scripts": { "build": "vite build", diff --git a/public/svgs/dalibi-blanc.svg b/public/svgs/dalibi-blanc.svg new file mode 100644 index 0000000..fca1aaa --- /dev/null +++ b/public/svgs/dalibi-blanc.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/svgs/dalibi.svg b/public/svgs/dalibi.svg new file mode 100644 index 0000000..483bdef --- /dev/null +++ b/public/svgs/dalibi.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/js/helpers/route.ts b/resources/js/helpers/route.ts index 203abf5..f8feae6 100644 --- a/resources/js/helpers/route.ts +++ b/resources/js/helpers/route.ts @@ -157,6 +157,7 @@ export function route(name: string, params?: any): string { 'backups.restore': '/settings/backups/restore', 'backups.download': `/settings/backups/${params}/download`, 'backups.destroy': `/settings/backups/${params}`, + 'about.index': '/settings/about', 'fee-structures.index': '/fee-structures', 'fee-structures.replicate': '/fee-structures/replicate', diff --git a/resources/js/pages/Parametres/About.tsx b/resources/js/pages/Parametres/About.tsx new file mode 100644 index 0000000..a91101f --- /dev/null +++ b/resources/js/pages/Parametres/About.tsx @@ -0,0 +1,146 @@ +import { Head } from '@inertiajs/react'; +import { + BadgeCheck, BookOpen, CalendarRange, ClipboardList, DatabaseBackup, ExternalLink, + FileBadge, GraduationCap, Info, Scale, Server, Wallet, Globe, FileCode2, ScrollText, Users, Heart, +} from 'lucide-react'; +import AppLayout from '@/layouts/app-layout'; + +interface Props { + app: { name: string; version: string; laravel: string; php: string; environment: string }; +} + +const GITHUB = 'https://github.com/wearedalibi/dalibi'; +const SITE = 'https://dalibi.wearekarfi.dev'; + +/** Modules couverts par l'application (résumé). */ +const MODULES: { icon: React.ElementType; title: string; desc: string }[] = [ + { icon: Users, title: 'Élèves & inscriptions', desc: 'Dossiers complets, inscriptions, passage de classe, bourses.' }, + { icon: FileBadge, title: 'Notes & bulletins', desc: 'Saisie, moyennes paramétrables, bulletins PDF, réclamations.' }, + { icon: ClipboardList, title: 'Présences', desc: 'Appel par séance, statistiques d’assiduité, permissions d’absence.' }, + { icon: GraduationCap, title: 'Examens officiels', desc: 'CEPD, BEPC, BAC : inscriptions, planning, résultats.' }, + { icon: Wallet, title: 'Finances & recouvrement', desc: 'Frais, factures, paiements (dont Mobile Money), reçus.' }, + { icon: BadgeCheck, title: 'Personnel & paie', desc: 'Fiches employés, cycles de paie, bulletins (CNSS, ITS).' }, + { icon: CalendarRange, title: 'Emploi du temps & calendrier', desc: 'Grilles par classe, calendrier de l’établissement.' }, + { icon: DatabaseBackup, title: 'Sauvegardes & statistiques', desc: 'Sauvegardes planifiées, tableaux de bord et exports.' }, +]; + +const LINKS: { icon: React.ElementType; title: string; href: string; sub: string }[] = [ + { icon: BookOpen, title: 'Documentation', href: `${SITE}/documentation`, sub: 'Guide de A à Z' }, + { icon: Globe, title: 'Site web', href: SITE, sub: 'Présentation & fonctionnalités' }, + { icon: FileCode2, title: 'Code source', href: GITHUB, sub: 'Dépôt GitHub' }, + { icon: ScrollText, title: 'Journal des versions', href: `${GITHUB}/blob/main/CHANGELOG.md`, sub: 'Historique des évolutions' }, +]; + +function Card({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +export default function About({ app }: Readonly) { + const brand = app.name ? app.name.charAt(0).toUpperCase() + app.name.slice(1) : 'Dalibi'; + + return ( + + +
+ + {/* En-tête : logo officiel Dalibi (version blanche en mode sombre) + version */} +
+
+

{brand}

+ {brand} + {brand} + + v{app.version} + +
+

+ Système de gestion scolaire — de l’inscription de l’élève au bulletin, en passant par l’écolage et la paie. + Pensé pour les établissements togolais 🇹🇬. +

+
+ + {/* Liens utiles */} +
+ {LINKS.map((l) => { + const Icon = l.icon; + return ( + +
+ + +
+

{l.title}

+

{l.sub}

+
+ ); + })} +
+ + {/* Ce que fait l'application */} + +

Ce que couvre l’application

+
+ {MODULES.map((m) => { + const Icon = m.icon; + return ( +
+
+
+

{m.title}

+

{m.desc}

+
+
+ ); + })} +
+
+ +
+ {/* Éditeur & licence */} + +

Éditeur & licence

+
+
+
Édité par
+
Kudayah Sassou Horacio Herve
+
+
+
Licence
+
GNU GPL v3 — logiciel libre
+
+
+
Droits
+
© {new Date().getFullYear()} {brand}
+
+
+

+ Vous pouvez utiliser, étudier, modifier et redistribuer ce logiciel selon les termes de la GPL v3. +

+
+ + {/* Informations techniques */} + +

Informations techniques

+
+
Version de l’application
v{app.version}
+
Laravel
{app.laravel}
+
PHP
{app.php}
+
Environnement
{app.environment}
+
+
+
+ + {/* Support */} + +

Besoin d’aide ?

+

+ Consultez d’abord la documentation. + Pour signaler un problème ou proposer une amélioration, ouvrez un ticket sur le dépôt GitHub. +

+
+
+
+ ); +} diff --git a/resources/js/pages/Statistiques/Index.tsx b/resources/js/pages/Statistiques/Index.tsx index 3d34d6d..271affe 100644 --- a/resources/js/pages/Statistiques/Index.tsx +++ b/resources/js/pages/Statistiques/Index.tsx @@ -3,7 +3,7 @@ import { BarChart3, Download, FileSpreadsheet, GraduationCap, Layers, MapPin, Pi import { useState } from 'react'; import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, LabelList, Legend, Line, LineChart, Pie, PieChart, - ResponsiveContainer, Tooltip as RTooltip, Treemap, XAxis, YAxis, + ReferenceLine, ResponsiveContainer, Tooltip as RTooltip, Treemap, XAxis, YAxis, } from 'recharts'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -34,7 +34,7 @@ interface Enrollment { interface Finance { billed: number; collected: number; remaining: number; recovery_rate: number; total_invoices: number; paid_count: number; partial_count: number; unpaid_count: number; - by_class: { name: string; billed: number; collected: number; rate: number }[]; + by_class: { name: string; cycle: string; billed: number; collected: number; remaining: number; rate: number }[]; by_method: { method: string; total: number; count: number }[]; monthly: { month: string; total: number }[]; } @@ -223,6 +223,19 @@ export default function StatisticsIndex({ filters, academicYears, classes, enrol const agePyramid = enrollment.age_distribution.map((d) => ({ age: d.age, male: -d.male, female: d.female })); const ageAxisMax = Math.max(1, ...enrollment.age_distribution.map((d) => Math.max(d.male, d.female))); + /* ---- Finances : recouvrement par classe, filtrable par cycle. ---- */ + const finCycles = [...new Set(finance.by_class.map((c) => c.cycle))]; + const [finCycle, setFinCycle] = useState('Tous'); + const [finSort, setFinSort] = useState<'facture' | 'taux'>('facture'); + const finClasses = (() => { + const f = finance.by_class.filter((c) => finCycle === 'Tous' || c.cycle === finCycle); + // Tri par taux = les classes en retard d'abord (croissant) ; par montant = les plus grosses d'abord. + return finSort === 'taux' ? [...f].sort((a, b) => a.rate - b.rate) : [...f].sort((a, b) => b.billed - a.billed); + })(); + // Couleur du taux par palier (statut, toujours accompagné du chiffre). + const rateColor = (r: number) => (r >= 80 ? '#16a34a' : r >= 60 ? '#d97706' : '#dc2626'); + const finChartHeight = Math.max(220, finClasses.length * 24 + 28); + /* ---- Comparaisons : l'année active est en cours, ses taux de fin d'année (redoublement, abandon, admission) ne sont pas encore décidés. On les met à null pour que les courbes s'arrêtent au lieu de plonger à zéro. ---- */ @@ -472,20 +485,80 @@ export default function StatisticsIndex({ filters, academicYears, classes, enrol - }> -
- - - - {finance.by_class.map((c) => ( - - - - - ))} -
ClasseFacturéEncaisséTaux
{c.name}{money(c.billed)}{money(c.collected)}{c.rate}%
+ {/* Recouvrement par classe — filtrable, plusieurs situations */} +
+
+ Recouvrement par classe + {['Tous', ...finCycles].map((c) => ( + + ))} +
+ Tri + {(['facture', 'taux'] as const).map((s) => ( + + ))} +
- + +
+ }> + + + + (Number(v) >= 1000 ? `${Math.round(Number(v) / 1000)}k` : String(v))} /> + + [money(Number(v)), n]} /> + + + + + + + }> + + + + `${v}%`} /> + + [`${v}%`, 'Taux']} /> + + + {finClasses.map((c) => )} + `${v}%`} style={{ fontSize: 10, fill: theme.tick }} /> + + + +
+ ≥ 80 % + 60–79 % + < 60 % +
+
+
+ + }> +
+ + + + {finClasses.map((c) => ( + + + + + + + ))} +
ClasseFacturéEncaisséResteTaux
{c.name}{money(c.billed)}{money(c.collected)}{money(c.remaining)}{c.rate}%
+
+
+
)} diff --git a/resources/js/types/menu.ts b/resources/js/types/menu.ts index 2fb1af5..c3fa992 100644 --- a/resources/js/types/menu.ts +++ b/resources/js/types/menu.ts @@ -1,4 +1,4 @@ -import { BookOpen, Calendar, Clock, ClipboardList, GraduationCap, LayoutGrid, Layers, Settings, Tag, Users, Shield, Lock, Wallet, Percent, DollarSign, UserCircle, TrendingUp, ArrowLeftRight, PieChart, FileText, ListChecks, NotebookPen, SlidersHorizontal, AlertCircle, CalendarRange, UserCheck, BarChart3, ShieldCheck, HardDrive, FileBadge, ScanLine, DatabaseBackup, Archive, Globe, TrendingDown, Briefcase, Receipt, Coins } from 'lucide-react'; +import { BookOpen, Calendar, Clock, ClipboardList, GraduationCap, LayoutGrid, Layers, Settings, Tag, Users, Shield, Lock, Wallet, Percent, DollarSign, UserCircle, TrendingUp, ArrowLeftRight, PieChart, FileText, ListChecks, NotebookPen, SlidersHorizontal, AlertCircle, CalendarRange, UserCheck, BarChart3, ShieldCheck, HardDrive, FileBadge, ScanLine, DatabaseBackup, Archive, Globe, TrendingDown, Briefcase, Receipt, Coins, Info } from 'lucide-react'; import { route } from '@/helpers/route'; import type { NavItem } from '@/types'; @@ -134,6 +134,7 @@ export const mainNavItems: NavItem[] = [ { title: 'Fichiers & Stockage', href: route('file-storage.index'), icon: HardDrive, permission: 'manage_file_storage' }, { title: 'Modèles de documents', href: route('document-templates.index'), icon: FileBadge, permission: 'view_documents' }, { title: 'Sauvegardes', href: route('backups.index'), icon: DatabaseBackup, permission: 'view_backups' }, + { title: 'À propos', href: route('about.index'), icon: Info }, ], }, { diff --git a/resources/views/exports/planning.blade.php b/resources/views/exports/planning.blade.php index 36a6aac..a2c3409 100644 --- a/resources/views/exports/planning.blade.php +++ b/resources/views/exports/planning.blade.php @@ -42,14 +42,16 @@ body { padding: 16px; } @page { margin: 1cm; size: A4 landscape; } } + /* En-tête ministérielle unifiée (identique au bulletin). */ + {!! $headerCss !!} -
-
{{ $school?->name ?? 'Établissement scolaire' }}
+{!! $headerHtml !!} +
Planning des examens
Classe : {{ $classroom->name }} ({{ $classroom->code }}) diff --git a/resources/views/exports/roster.blade.php b/resources/views/exports/roster.blade.php index 9f68c0f..7caf187 100644 --- a/resources/views/exports/roster.blade.php +++ b/resources/views/exports/roster.blade.php @@ -30,18 +30,15 @@ .footer { margin-top: 16px; font-size: 9px; color: #888; display: flex; justify-content: space-between; } .sign { margin-top: 36px; text-align: right; font-size: 11px; } + {{-- En-tête ministérielle unifiée (identique au bulletin). --}} + {!! $headerCss !!} + {!! $headerHtml !!}
- @if($school) -
{{ $school->terme ?? 'République Togolaise' }}
- @if($school->devise)
{{ $school->devise }}
@endif -
{{ $school->name }}
- @endif
LISTE DES ÉLÈVES
Classe : {{ $classroom->name }} ({{ $classroom->code }}) — Année : {{ $year->year }}
-
diff --git a/resources/views/exports/timetable.blade.php b/resources/views/exports/timetable.blade.php index 01431aa..9540ff7 100644 --- a/resources/views/exports/timetable.blade.php +++ b/resources/views/exports/timetable.blade.php @@ -23,15 +23,13 @@ .slot-meta { font-size: 9px; color: #555; } .empty { color: #ccc; text-align: center; } .footer { margin-top: 12px; font-size: 9px; color: #888; text-align: right; } + {{-- En-tête ministérielle unifiée (identique au bulletin). --}} + {!! $headerCss !!} + {!! $headerHtml !!}
- @if($school) -
{{ $school->terme ?? 'République Togolaise' }}
- @if($school->devise)
{{ $school->devise }}
@endif -
{{ $school->name }}
- @endif
EMPLOI DU TEMPS
Classe : {{ $classroom->name }}
diff --git a/resources/views/statistics/report.blade.php b/resources/views/statistics/report.blade.php index 322dba8..511181c 100644 --- a/resources/views/statistics/report.blade.php +++ b/resources/views/statistics/report.blade.php @@ -19,6 +19,8 @@ th { background: #f1f5f9; font-size: 10px; text-transform: uppercase; } td.n, th.n { text-align: right; } .kpis td { font-weight: bold; } + {{-- En-tête ministérielle unifiée (dont le plafond de taille du logo). --}} + {!! $headerCss !!} diff --git a/routes/web.php b/routes/web.php index 6e44bdc..e9cb8d6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -395,6 +395,9 @@ Route::post('settings/backups/{backup}/verify', [\App\Http\Controllers\Parametres\BackupController::class, 'verify'])->middleware('can:view_backups')->name('backups.verify'); Route::delete('settings/backups/{backup}', [\App\Http\Controllers\Parametres\BackupController::class, 'destroy'])->middleware('can:delete_backups')->name('backups.destroy'); + // À propos (informations sur l'application) — accessible à tout utilisateur connecté + Route::get('settings/about', [\App\Http\Controllers\Parametres\AboutController::class, 'index'])->name('about.index'); + // Passage de classe / réinscription en masse Route::get('promotion', [PromotionController::class, 'index'])->middleware('can:execute_promotion')->name('promotion.index'); Route::post('promotion', [PromotionController::class, 'store'])->middleware('can:execute_promotion')->name('promotion.store');