Réalisations par membre
Mohamed Amine Hidaoui
<?php
/* ============================================================
* planning_public.php – Planning des séances accessible sans connexion
* Affiche un calendrier FullCalendar en lecture seule.
* Les visiteurs peuvent cliquer sur une séance pour voir les détails
* et sont invités à se connecter pour réserver.
* ============================================================ */
require_once "config/db.php"; // Connexion PDO (pas d'auth : page publique)
// --- Récupération des séances pour FullCalendar ---
// LEFT JOIN sur coach : une séance peut ne pas avoir de coach assigné
$sql = "
SELECT s.id_seance, a.nom_activite, c.prenom AS coach,
s.date_heure_debut, s.duree_minutes, s.capacite_max
FROM seance s
JOIN activite a ON a.id_activite = s.id_activite
LEFT JOIN coach c ON c.id_coach = s.id_coach
";
// Construction du tableau d'événements au format attendu par FullCalendar
$events = [];
foreach ($pdo->query($sql)->fetchAll() as $r) {
$events[] = [
"id" => $r['id_seance'],
"title" => $r['nom_activite'], // Texte affiché sur le bloc calendrier
"start" => $r['date_heure_debut'],
"extendedProps" => [ // Données accessibles via JS lors du clic
"coach" => $r['coach'] ?? '—',
"duree" => $r['duree_minutes'],
"capacite" => $r['capacite_max']
]
];
}
?>
<?php
/* ============================================================
* planning.php – Vue calendrier du planning des séances (intranet)
* Affiche les séances dans FullCalendar avec possibilité d'ajouter,
* modifier et supprimer directement depuis le calendrier.
* ============================================================ */
require_once "includes/auth.php"; // Vérifie que l'utilisateur est connecté
require_once "config/db.php";
// --- Traitement du formulaire (ajout ou modification via la modale) ---
if ($_POST) {
if (empty($_POST['id_seance'])) {
// Insertion d'une nouvelle séance (id_coach peut être null)
$pdo->prepare("INSERT INTO seance
(id_activite, id_coach, date_heure_debut, duree_minutes, capacite_max)
VALUES (?,?,?,?,?)")
->execute([
$_POST['id_activite'],
!empty($_POST['id_coach']) ? $_POST['id_coach'] : null,
$_POST['date_heure_debut'],
$_POST['duree_minutes'],
$_POST['capacite_max']
]);
} else {
// Mise à jour d'une séance existante
$pdo->prepare("UPDATE seance
SET id_activite=?, id_coach=?, date_heure_debut=?,
duree_minutes=?, capacite_max=?
WHERE id_seance=?")
->execute([
$_POST['id_activite'],
!empty($_POST['id_coach']) ? $_POST['id_coach'] : null,
$_POST['date_heure_debut'],
$_POST['duree_minutes'],
$_POST['capacite_max'],
$_POST['id_seance']
]);
}
header("Location: planning.php");
exit;
}
// --- Suppression via ?delete=ID ---
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM seance WHERE id_seance=?")->execute([$_GET['delete']]);
header("Location: planning.php");
exit;
}
// --- Récupération de toutes les séances avec jointures ---
$data = $pdo->query("
SELECT s.id_seance, a.nom_activite, c.prenom AS coach_prenom, c.nom AS coach_nom,
s.date_heure_debut, s.duree_minutes, s.capacite_max
FROM seance s
JOIN activite a ON a.id_activite = s.id_activite
LEFT JOIN coach c ON c.id_coach = s.id_coach
ORDER BY s.date_heure_debut DESC
")->fetchAll();
<?php
/* ============================================================
* hero_banner.php – Bannière animée partagée entre toutes les pages
* Reçoit $hero_title et $hero_subtitle depuis la page appelante.
* Utilisée aussi bien sur les pages publiques que l'intranet.
* ============================================================ */
?>
<div class="hero-banner mb-4">
<div class="hero-content">
<!-- Icône Bootstrap Icons + titre dynamique -->
<h1 class="hero-title">
<i class="bi bi-trophy"></i>
<?= htmlspecialchars($hero_title) ?>
</h1>
<p class="hero-subtitle"><?= htmlspecialchars($hero_subtitle) ?></p>
</div>
</div>
Observations
Développeur — Planning public (accessible sans connexion) et planning intranet avec FullCalendar. Responsable de la bannière hero partagée, du menu, du CSS global et des corrections de bugs d'affichage.
Utilisation de FullCalendar 6 pour la vue calendrier : les séances PHP sont sérialisées en JSON directement dans la page et injectées dans le calendrier JS, permettant une vue mensuelle/hebdomadaire sans rechargement.
Correction de l'affichage de la liste des coachs (en binôme avec Sofiane), et correction de la redirection sur la page de login (en binôme avec Axel).
Commentaires de l'ensemble du code du projet (maintenabilité) et review des fonctionnalités. Modification des images du site.
Sofiane Fodil
<?php
/* ============================================================
* dashboard.php – Tableau de bord principal de l'intranet
* Affiche les statistiques globales de la salle Colvert.
* ============================================================ */
require_once "includes/auth.php"; // Vérifie que l'utilisateur est connecté
require_once "config/db.php";
// --- Compteurs globaux pour les cartes de statistiques ---
$adherents = $pdo->query("SELECT COUNT(*) FROM Adherent")->fetchColumn();
$seances = $pdo->query("SELECT COUNT(*) FROM Seance")->fetchColumn();
$resas = $pdo->query("SELECT COUNT(*) FROM Reservation")->fetchColumn();
$abos = $pdo->query("SELECT COUNT(*) FROM Abonnement WHERE etat='Valide'")->fetchColumn();
include "includes/header.php";
include "includes/menu.php";
$hero_title = "Tableau de bord";
$hero_subtitle = "Salle de Sport Marc Colvert – Vue d'ensemble";
include "includes/hero_banner.php";
?>
<!-- Cartes de statistiques rapides -->
<div class="row">
<div class="col-md-3 mb-3">
<div class="card shadow p-3 text-center">
<h6>Adhérents</h6>
<h2><?= $adherents ?></h2>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow p-3 text-center">
<h6>Séances</h6>
<h2><?= $seances ?></h2>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow p-3 text-center">
<h6>Réservations</h6>
<h2><?= $resas ?></h2>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow p-3 text-center">
<h6>Abonnements actifs</h6>
<h2><?= $abos ?></h2>
</div>
</div>
</div>
<?php include "includes/footer.php"; ?>
<?php
/* ============================================================
* coachs.php – Gestion de l'équipe encadrante
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
if ($_POST) {
if (empty($_POST['id_coach'])) {
$pdo->prepare("INSERT INTO coach (nom, prenom, specialite, telephone) VALUES (?,?,?,?)")
->execute([$_POST['nom'], $_POST['prenom'], $_POST['specialite'], $_POST['telephone']]);
} else {
$pdo->prepare("UPDATE coach SET nom=?, prenom=?, specialite=?, telephone=? WHERE id_coach=?")
->execute([$_POST['nom'], $_POST['prenom'], $_POST['specialite'], $_POST['telephone'], $_POST['id_coach']]);
}
header("Location: coachs.php");
exit;
}
if (isset($_GET['edit'])) {
$e = $pdo->prepare("SELECT * FROM coach WHERE id_coach=?");
$e->execute([$_GET['edit']]);
$edit = $e->fetch();
}
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM coach WHERE id_coach=?")->execute([$_GET['delete']]);
header("Location: coachs.php");
exit;
}
// --- Statistiques ---
$total_coachs = $pdo->query("SELECT COUNT(*) FROM coach")->fetchColumn();
$total_seances = $pdo->query("SELECT COUNT(*) FROM seance WHERE id_coach IS NOT NULL")->fetchColumn();
// Coach ayant le plus grand nombre de séances assignées
$coach_plus_actif = $pdo->query("
SELECT c.prenom, c.nom, COUNT(s.id_seance) AS nb
FROM coach c
LEFT JOIN seance s ON s.id_coach = c.id_coach
GROUP BY c.id_coach
ORDER BY nb DESC
LIMIT 1
")->fetch();
$data = $pdo->query("SELECT * FROM coach ORDER BY nom")->fetchAll();
<?php
/* ============================================================
* seance.php – Gestion des séances de sport
* Chaque séance est liée à une activité et optionnellement à un coach.
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
if ($_POST) {
if (empty($_POST['id_seance'])) {
$pdo->prepare("INSERT INTO seance
(id_activite, id_coach, date_heure_debut, duree_minutes, capacite_max)
VALUES(?,?,?,?,?)")
->execute([
$_POST['id_activite'],
$_POST['id_coach'] ?: null, // null si aucun coach sélectionné
$_POST['date_heure_debut'],
$_POST['duree_minutes'],
$_POST['capacite_max']
]);
} else {
$pdo->prepare("UPDATE seance
SET id_activite=?, id_coach=?, date_heure_debut=?,
duree_minutes=?, capacite_max=?
WHERE id_seance=?")
->execute([
$_POST['id_activite'],
$_POST['id_coach'] ?: null,
$_POST['date_heure_debut'],
$_POST['duree_minutes'],
$_POST['capacite_max'],
$_POST['id_seance']
]);
}
header("Location: seance.php");
exit;
}
// --- Statistiques ---
$total_seances = $pdo->query("SELECT COUNT(*) FROM seance")->fetchColumn();
$capacite_moy = $pdo->query("SELECT AVG(capacite_max) FROM seance")->fetchColumn();
// Prochaine séance à venir
$prochaine = $pdo->query("
SELECT date_heure_debut FROM seance
WHERE date_heure_debut >= NOW()
ORDER BY date_heure_debut ASC LIMIT 1
")->fetchColumn();
// --- Liste avec jointures activité + coach ---
$data = $pdo->query("
SELECT s.*, a.nom_activite, c.nom AS coach_nom, c.prenom AS coach_prenom
FROM seance s
JOIN activite a ON s.id_activite = a.id_activite
LEFT JOIN coach c ON s.id_coach = c.id_coach
ORDER BY s.date_heure_debut DESC
")->fetchAll();
Observations
Conception et mise en place de la base de données MySQL (en binôme avec Axel et Rafael). Développement du dashboard, des pages Coachs, Séances, Types Abonnement, de la page de logs et du CSS global.
La page coachs.php calcule dynamiquement le coach le plus actif via une agrégation SQL (COUNT + GROUP BY + ORDER BY + LIMIT 1), ce qui évite tout calcul côté PHP.
Inversion du signe de la monnaie dans la page abonnements (montant affiché en négatif) — corrigé directement dans la requête SQL de calcul du revenu total.
8 tables : adherent, coach, activite, seance, reservation, abonnement, typeabonnement, utilisateur. Clés étrangères avec contraintes d'intégrité référentielle.
Axel Santin
<?php
/* ============================================================
* adherent.php – Gestion des adhérents
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
// --- Traitement du formulaire (ajout ou modification) ---
if ($_POST) {
if (empty($_POST['id_adherent'])) {
$pdo->prepare("INSERT INTO adherent
(nom, prenom, email, mot_de_passe, telephone, date_inscription, statut)
VALUES (?,?,?,?,?,?,?)")
->execute([
$_POST['nom'], $_POST['prenom'], $_POST['email'],
$_POST['mot_de_passe'], $_POST['telephone'],
$_POST['date_inscription'], $_POST['statut']
]);
} else {
$pdo->prepare("UPDATE adherent
SET nom=?, prenom=?, email=?, mot_de_passe=?,
telephone=?, date_inscription=?, statut=?
WHERE id_adherent=?")
->execute([
$_POST['nom'], $_POST['prenom'], $_POST['email'],
$_POST['mot_de_passe'], $_POST['telephone'],
$_POST['date_inscription'], $_POST['statut'],
$_POST['id_adherent']
]);
}
header("Location: adherent.php");
exit;
}
// --- Pré-chargement si modification via ?edit=ID ---
if (isset($_GET['edit'])) {
$e = $pdo->prepare("SELECT * FROM adherent WHERE id_adherent=?");
$e->execute([$_GET['edit']]);
$edit = $e->fetch();
}
// --- Suppression via ?delete=ID ---
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM adherent WHERE id_adherent=?")->execute([$_GET['delete']]);
header("Location: adherent.php");
exit;
}
// --- Statistiques ---
$total = $pdo->query("SELECT COUNT(*) FROM adherent")->fetchColumn();
$actifs = $pdo->query("SELECT COUNT(*) FROM adherent WHERE statut='Actif'")->fetchColumn();
$inactif = $pdo->query("SELECT COUNT(*) FROM adherent WHERE statut='Inactif'")->fetchColumn();
$data = $pdo->query("SELECT * FROM adherent ORDER BY nom")->fetchAll();
<?php
/* ============================================================
* activite.php – Gestion des activités sportives
* Affiche des statistiques par niveau de difficulté.
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
if ($_POST) {
if (empty($_POST['id_activite'])) {
$pdo->prepare("INSERT INTO activite(nom_activite, description, difficulte)
VALUES(?,?,?)")
->execute([$_POST['nom_activite'], $_POST['description'], $_POST['difficulte']]);
} else {
$pdo->prepare("UPDATE activite
SET nom_activite=?, description=?, difficulte=?
WHERE id_activite=?")
->execute([
$_POST['nom_activite'], $_POST['description'],
$_POST['difficulte'], $_POST['id_activite']
]);
}
header("Location: activite.php");
exit;
}
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM activite WHERE id_activite=?")->execute([$_GET['delete']]);
header("Location: activite.php");
exit;
}
// --- Statistiques par niveau de difficulté ---
$total = $pdo->query("SELECT COUNT(*) FROM activite")->fetchColumn();
$faciles = $pdo->query("SELECT COUNT(*) FROM activite WHERE difficulte='Facile'")->fetchColumn();
$difficiles = $pdo->query("SELECT COUNT(*) FROM activite WHERE difficulte='Difficile'")->fetchColumn();
$data = $pdo->query("SELECT * FROM activite ORDER BY nom_activite")->fetchAll();
<?php
/* ============================================================
* abonnements.php – Gestion des abonnements des adhérents
* Le prix est automatiquement rempli depuis le type sélectionné.
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
if ($_POST) {
if (empty($_POST['id_abonnement'])) {
$pdo->prepare("INSERT INTO abonnement
(id_adherent, id_type_abo, date_debut, date_fin, prix_paye, etat)
VALUES (?,?,?,?,?,?)")
->execute([
$_POST['id_adherent'], $_POST['id_type_abo'],
$_POST['date_debut'], $_POST['date_fin'],
$_POST['prix_paye'], $_POST['etat']
]);
} else {
$pdo->prepare("UPDATE abonnement
SET id_adherent=?, id_type_abo=?, date_debut=?,
date_fin=?, prix_paye=?, etat=?
WHERE id_abonnement=?")
->execute([
$_POST['id_adherent'], $_POST['id_type_abo'],
$_POST['date_debut'], $_POST['date_fin'],
$_POST['prix_paye'], $_POST['etat'],
$_POST['id_abonnement']
]);
}
header("Location: abonnements.php");
exit;
}
// --- Statistiques financières ---
$total_abos = $pdo->query("SELECT COUNT(*) FROM abonnement")->fetchColumn();
$abos_valides = $pdo->query("SELECT COUNT(*) FROM abonnement WHERE etat='Valide'")->fetchColumn();
$revenu_total = $pdo->query("SELECT SUM(prix_paye) FROM abonnement WHERE etat='Valide'")->fetchColumn();
// --- Liste avec jointures adhérent + type ---
$data = $pdo->query("
SELECT a.id_abonnement, ad.nom, ad.prenom, t.libelle,
a.date_debut, a.date_fin, a.prix_paye, a.etat
FROM abonnement a
JOIN adherent ad ON ad.id_adherent = a.id_adherent
JOIN typeabonnement t ON t.id_type_abo = a.id_type_abo
ORDER BY a.date_fin DESC
")->fetchAll();
Observations
Développement des pages Adhérents, Activités, Abonnements et de la page d'accueil (index). Correction des bugs d'ajout, suppression et modification sur les pages Adhérents et Activités.
La page abonnements.php remplit automatiquement le champ prix via JS à partir de l'attribut data-prix présent sur chaque option du sélecteur de type d'abonnement.
Corrections CRUD sur adherent.php et activite.php : problèmes de redirection après POST, et gestion de la valeur nulle sur le champ email non obligatoire.
Co-responsable avec Sofiane et Rafael de la conception du schéma de base de données. Définition des tables et contraintes relationnelles.
Rafael Marta
<?php
/* ============================================================
* reservation.php – Gestion des réservations de séances
* Permet aux adhérents de réserver une séance.
* Vérifie la capacité disponible avant insertion.
* ============================================================ */
require_once "includes/auth.php";
require_once "config/db.php";
if ($_POST) {
if (empty($_POST['id_reservation'])) {
$pdo->prepare("INSERT INTO reservation
(id_adherent, id_seance, date_reservation, statut_reservation)
VALUES (?,?,?,?)")
->execute([
$_POST['id_adherent'],
$_POST['id_seance'],
$_POST['date_reservation'],
$_POST['statut_reservation']
]);
} else {
$pdo->prepare("UPDATE reservation
SET id_adherent=?, id_seance=?,
date_reservation=?, statut_reservation=?
WHERE id_reservation=?")
->execute([
$_POST['id_adherent'],
$_POST['id_seance'],
$_POST['date_reservation'],
$_POST['statut_reservation'],
$_POST['id_reservation']
]);
}
header("Location: reservation.php");
exit;
}
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM reservation WHERE id_reservation=?")->execute([$_GET['delete']]);
header("Location: reservation.php");
exit;
}
// --- Statistiques ---
$total_resas = $pdo->query("SELECT COUNT(*) FROM reservation")->fetchColumn();
$resas_confirmees = $pdo->query(
"SELECT COUNT(*) FROM reservation WHERE statut_reservation='Confirmée'"
)->fetchColumn();
// --- Listes déroulantes pour le formulaire ---
$adherents = $pdo->query("SELECT id_adherent, nom, prenom FROM adherent ORDER BY nom")->fetchAll();
$seances = $pdo->query("
SELECT s.id_seance, a.nom_activite, s.date_heure_debut
FROM seance s
JOIN activite a ON a.id_activite = s.id_activite
ORDER BY s.date_heure_debut DESC
")->fetchAll();
// --- Liste complète des réservations avec jointures ---
$data = $pdo->query("
SELECT r.id_reservation, ad.nom, ad.prenom,
a.nom_activite, s.date_heure_debut,
r.date_reservation, r.statut_reservation
FROM reservation r
JOIN adherent ad ON ad.id_adherent = r.id_adherent
JOIN seance s ON s.id_seance = r.id_seance
JOIN activite a ON a.id_activite = s.id_activite
ORDER BY r.date_reservation DESC
")->fetchAll();
Observations
Profil SISR + Développeur. Développement de la page Réservations et co-développement de la page Index avec Axel. Co-concepteur de la base de données avec Sofiane et Axel.
La liste des réservations utilise une triple jointure (reservation → adherent, seance, activite) pour afficher un tableau complet sans aucun traitement PHP supplémentaire.
Correction de l'affichage du tableau des réservations — les colonnes Activité et Date n'apparaissaient pas correctement à cause d'alias SQL incorrects sur les colonnes jointes.
Les réservations distinguent deux états : Confirmée et Annulée, cohérents avec le type ENUM défini dans la base de données.
Nesrine
Nesrine intervient en qualité de profil SISR avec participation au développement. Son rôle s'est concentré sur la phase de finalisation et de déploiement du projet :
- Visualisation et validation du rendu final de l'application — vérification du comportement de chaque page (navigation, formulaires, affichages).
- Participation active au déploiement du dossier projet dans
C:\wamp64\wwwsur le serveur local partagé en LAN. - Tests fonctionnels sur l'ensemble des pages CRUD : adhérents, coachs, séances, abonnements, réservations, activités.
- Vérification de la cohérence des redirections après les opérations POST et de l'affichage des messages de confirmation.
Observations
Profil SISR orienté tests et déploiement. Garantit la conformité du rendu livré en s'assurant que l'application est opérationnelle dans l'environnement cible WAMP 64.
La phase de test réalisée par un membre non développeur de la fonctionnalité permet de détecter des bugs d'affichage ou de comportement que les développeurs responsables n'auraient pas repérés.
Bilan du projet
✅ Points forts
- Base de données relationnelle complète couvrant tous les besoins métier (8 tables)
- Pages CRUD fonctionnelles sur toutes les entités du projet
- Séparation claire entre partie publique (planning_public) et intranet sécurisé par authentification
- Intégration de FullCalendar 6 pour la visualisation du planning en vue mensuelle et hebdomadaire
- Organisation Kanban efficace via Trello (4 colonnes : À faire / En cours / Terminé / Ressources)
- Collaboration en temps réel via LAN partagé — modifications visibles instantanément par tous
- Déploiement local opérationnel sous WAMP 64 — application fonctionnelle et testée
- Code commenté sur l'ensemble du projet (maintenabilité assurée par Mohamed Amine)
⚡ Axes d'amélioration
- Hachage des mots de passe côté base de données (actuellement stockés en clair)
- Contrôle de la capacité maximale lors de la création d'une réservation (pas encore implémenté côté serveur)
- Sécurisation des suppressions avec tokens CSRF pour prévenir les suppressions accidentelles via URL directe
- Gestion des rôles différenciés (Admin vs Coach) dans la table utilisateur
- Pagination des tableaux sur les grandes listes (adhérents, réservations)
- Tests unitaires et fonctionnels à ajouter pour fiabiliser les futures évolutions