Architecture Data
Relations et Contraintes
- Isolation de l'espace de travail : Un étudiant ne doit pas être pollué par les sujets des autres promotions. Le système repose sur la correspondance entre la
classe de l'étudiant et la classe_cible de la SAE.
- Le cas "Toutes" : Les enseignants ont la possibilité de publier des SAEs transversales (ex: un projet d'école) ciblant la chaîne de caractères spéciale "Toutes".
- Vérification asynchrone : Pour chaque SAE affichée, le système doit aussi vérifier instantanément si l'étudiant a déjà soumis un fichier, afin de modifier le bouton d'action ("Déposer" vs "Mettre à jour").
/* Le schéma de correspondance (Concept) */
Table COMPTES (Étudiant)
- id: 42
- classe: 'MMI-A1'
Table SAE (Sujets)
- id: 101 | classe_cible: 'MMI-A1' (Match !)
- id: 102 | classe_cible: 'MMI-B2' (Rejeté)
- id: 103 | classe_cible: 'Toutes' (Match !)
Node.js / backend/server.js
Exécution de la Route Sécurisée
- Identification fiable : Le serveur ne fait pas confiance au client. Il utilise l'ID extrait du Token JWT (
req.user.id) pour interroger la base et récupérer la vraie classe de l'étudiant.
- Jointure Externe (LEFT JOIN) : La requête fusionne la table SAE et la table Rendus. Le
LEFT JOIN est crucial : il renvoie la SAE même si aucun rendu n'existe (les colonnes de rendu seront simplement nulles).
- Double condition (WHERE) : Sélectionne la classe spécifique OU la valeur globale 'Toutes', en s'assurant que le statut est validé.
app.get('/api/sae', verifierToken, async (req, res) => {
try {
// ... (Logique admin / enseignant gérée plus haut)
// 1. Récupération de la classe depuis la BDD (Sécurité)
const userDb = await db.get('SELECT classe FROM Comptes WHERE id = ?', [req.user.id]);
// 2. Requête combinée (Sujets + État du rendu de l'étudiant)
const rows = await db.all(`
SELECT SAE.*, Rendus.id AS rendu_id, Rendus.date_soumission
FROM SAE
/* On attache le rendu UNIQUEMENT s'il appartient à cet étudiant */
LEFT JOIN Rendus ON SAE.id = Rendus.sae_id AND Rendus.etudiant_id = ?
/* Filtre de classe et statut */
WHERE (SAE.classe_cible = ? OR SAE.classe_cible = 'Toutes')
AND SAE.statut = 'validee'
`, [req.user.id, userDb.classe]);
return res.json(rows);
} catch (error) { /* ... */ }
});
React / frontend/src/App.jsx
Moteur de Tri Client
- Traitement local : Pour éviter de surcharger le serveur, le tri et le filtrage se font directement dans le navigateur de l'utilisateur via JavaScript (Fonction
getSaesTriees).
- Filtres cumulatifs : Utilisation de
Array.prototype.filter() pour vérifier si le statut calculé de la SAE correspond aux cases cochées (En cours, Terminée, En retard).
- Tri chronologique : Utilisation de
Array.prototype.sort() pour convertir les dates en Millisecondes (getTime()) et les ordonner de manière ascendante ou descendante.
/* Fonction pure exécutée avant l'affichage */
const getSaesTriees = (listeASorter) => {
return [...listeASorter]
// 1. FILTRAGE PAR STATUT
.filter(sae => {
if (role !== 'etudiant') return true;
const statut = determinerStatut(sae).texte; // 'En cours', 'Terminée'...
return filtresStatut.includes(statut);
})
// 2. TRI PAR DATE
.sort((a, b) => {
// Gestion des SAEs sans date limite (placées à la fin)
if (!a.date_rendu) return 1;
if (!b.date_rendu) return -1;
const dateA = new Date(a.date_rendu).getTime();
const dateB = new Date(b.date_rendu).getTime();
// Tri dynamique selon le select (asc/desc)
return triDate === 'asc' ? dateA - dateB : dateB - dateA;
});
};
/* Dans le JSX */
{getSaesTriees(saes).map(sae => (
...
))}