Créa-blog

#100JoursPourCoder
Projet Créa-code

Ressources pour développeur web

Théme de la semaine : Le Machine learning en PHP

Coder un agenda en grille en HTML, PHP et JavaScript

⏱️ Temps de lecture estimé : 12 minutes
Accueil HTML5 Coder un agenda en grille en HTML, PHP et JavaScript

Dans ce tutoriel vous allez apprendre, pas à pas, comment concevoir et coder un agenda avec une vue mensuelle en utilisant HTML, CSS, JavaScript côté client et PHP + MySQL côté serveur.

L’objectif est de vous fournir un guide clair, compréhensible même si vous débutez, et suffisamment détaillé pour que vous puissiez installer, tester et étendre votre propre agenda.

Ce tutoriel est structuré en plusieurs parties. Vous trouverez d’abord la conception et la liste des fonctionnalités attendues, puis les étapes de création, la base de données, le code (fichiers complets commentés) et enfin des pistes d’amélioration et de déploiement. Chaque fichier est expliqué.

Quelles seront les fonctionnalités de l’agenda ?

L’agenda proposera une vue mensuelle où chaque jour apparaît dans une grille. Vous pourrez naviguer d’un mois à l’autre. Vous pourrez ajouter un événement, modifier un événement existant, supprimer un événement et voir, dans chaque case du calendrier, la liste (ou un aperçu) des événements prévus ce jour-là.

Les événements peuvent être ponctuels (heure précise) ou « toute la journée ». L’interface utilisera des requêtes AJAX pour communiquer avec le serveur : l’ajout, la modification et la suppression ne rechargeront pas toute la page. L’agenda affichera aussi visuellement la couleur associée à chaque événement pour mieux repérer types ou priorités. Bref, un agenda complet ! Et on va le coder nous-même !

Pourquoi ces choix ?

Ces fonctionnalités couvrent l’essentiel d’un agenda utilisable au quotidien tout en restant simple à implémenter pour un projet d’apprentissage. L’usage d’une API PHP permet de séparer la logique serveur et la logique affichage. JavaScript s’occupe de rendre la vue mensuelle interactive. MySQL (via PDO) sécurise les accès à la base de données.

Agenda en HTML, PHP et JS
L’agenda que nous allons coder en HTML, PHP et JS

Les étapes de la création de l’agenda

  1. Premièrement, vous préparerez l’environnement de développement : serveur local (XAMPP, MAMP, Laragon), base de données MySQL et éditeur de code (par exemple VS Code).
  2. Deuxièmement, on va créer la table SQL pour stocker les événements.
  3. Troisièmement, vous écrirez le fichier de connexion à la base (db.php) en PDO.
  4. Quatrièmement, vous construirez l’API PHP (api.php) qui répondra aux actions : lister, créer, mettre à jour, supprimer.
  5. Cinquièmement, vous coderez la page frontale (index.php) qui contient le HTML, le CSS et le JavaScript permettant d’afficher la vue mensuelle et d’interagir.
  6. Enfin, vous testerez, corrigerez et améliorerez. Chacune de ces étapes est détaillée dans la suite.

Création de la base et de la table

Créez une base de données nommée par exemple agenda_db. Ensuite exécutez la requête SQL suivante pour créer la table events. Le nommage et les types sont choisis pour stocker des événements simples mais complets.

CREATE DATABASE IF NOT EXISTS agenda_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE agenda_db;

CREATE TABLE IF NOT EXISTS events (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  start DATETIME NOT NULL,
  end DATETIME NOT NULL,
  all_day TINYINT(1) DEFAULT 0,
  color VARCHAR(7) DEFAULT '#3788d8',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Le champ id identifie chaque événement. title contient le titre affiché. description sert pour des détails optionnels. start et end définissent les bornes temporelles de l’événement au format DATETIME. all_day est un booléen (0 ou 1) qui indique si l’événement occupe toute la journée. color permet d’afficher visuellement une couleur pour l’événement. created_at et updated_at enregistrent les dates d’insertion et de modification.

Fichiers du projet et arborescence

Imaginons que votre projet soit dans un dossier agenda. L’arborescence conseillée est :

/agenda
  -- | index.php
  -- | api.php
  -- | db.php
  -- | /assets
  ----- | style.css
  -- | /assets
  ----- | script.js

Vous pouvez créer ces fichiers et dossiers maintenant. Le code complet pour chaque fichier suit.

Code complet et explications détaillées

Vous trouverez ci-dessous les fichiers complets. Après chaque fichier, l’explication détaillée pour les débutants.

db.php — connexion PDO

<?php
// db.php
// Ce fichier établit la connexion à la base de données avec PDO.
// Placez-le dans le même dossier que api.php et index.php.

$host = '127.0.0.1';      // adresse du serveur MySQL, changez si nécessaire
$db   = 'agenda_db';      // nom de la base de données créée précédemment
$user = 'root';           // utilisateur MySQL (sous XAMPP souvent 'root')
$pass = '';               // mot de passe (vide sous XAMPP par défaut)
$charset = 'utf8mb4';     // encodage recommandé

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // obtenir les erreurs PDO sous forme d'exceptions
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,       // récupérer les résultats sous forme de tableau associatif
    PDO::ATTR_EMULATE_PREPARES   => false,                  // utiliser les vraies requêtes préparées
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    // Si la connexion échoue, on renvoie une erreur compréhensible.
    http_response_code(500);
    echo json_encode(['error' => 'Erreur de connexion à la base de données: ' . $e->getMessage()]);
    exit;
}

Ce fichier centralise la connexion à la base. $dsn indique à PDO comment se connecter. Les options définissent des comportements sûrs : levée d’exceptions, récupération associative et désactivation de l’émulation des requêtes préparées. Le bloc try/catch permet d’attraper une erreur de connexion et d’expliquer la cause.

api.php — API REST minimale (JSON)

<?php
// api.php
header('Content-Type: application/json; charset=utf-8');

require 'db.php'; // inclut la connexion PDO

$action = $_REQUEST['action'] ?? '';

if ($action === 'list') {
    // Récupérer les événements entre deux dates (inclus)
    // Paramètres attendus : start (YYYY-MM-DD), end (YYYY-MM-DD)
    $start = $_GET['start'] ?? null;
    $end = $_GET['end'] ?? null;

    if (!$start || !$end) {
        echo json_encode([]);
        exit;
    }

    // On recherche les événements qui intersectent la période demandée
    $stmt = $pdo->prepare('SELECT * FROM events WHERE (start BETWEEN ? AND ?) OR (end BETWEEN ? AND ?) OR (start <= ? AND end >= ?)');
    $stmt->execute([$start . ' 00:00:00', $end . ' 23:59:59', $start . ' 00:00:00', $end . ' 23:59:59', $start . ' 00:00:00', $end . ' 23:59:59']);
    $events = $stmt->fetchAll();
    echo json_encode($events);
    exit;
}

if ($action === 'create') {
    // Créer un événement. Les données sont envoyées en JSON dans le corps.
    $data = json_decode(file_get_contents('php://input'), true);
    if (!$data || !isset($data['title']) || !isset($data['start']) || !isset($data['end'])) {
        http_response_code(400);
        echo json_encode(['error' => 'Données manquantes']);
        exit;
    }

    $title = trim($data['title']);
    $description = trim($data['description'] ?? '');
    $start = $data['start'];
    $end = $data['end'];
    $all_day = !empty($data['all_day']) ? 1 : 0;
    $color = $data['color'] ?? '#3788d8';

    $stmt = $pdo->prepare('INSERT INTO events (title, description, start, end, all_day, color) VALUES (?, ?, ?, ?, ?, ?)');
    $stmt->execute([$title, $description, $start, $end, $all_day, $color]);

    echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
    exit;
}

if ($action === 'update') {
    $data = json_decode(file_get_contents('php://input'), true);
    if (!$data || !isset($data['id'])) {
        http_response_code(400);
        echo json_encode(['error' => 'Données manquantes']);
        exit;
    }

    $id = (int)$data['id'];
    $title = trim($data['title'] ?? '');
    $description = trim($data['description'] ?? '');
    $start = $data['start'] ?? null;
    $end = $data['end'] ?? null;
    $all_day = !empty($data['all_day']) ? 1 : 0;
    $color = $data['color'] ?? '#3788d8';

    $stmt = $pdo->prepare('UPDATE events SET title = ?, description = ?, start = ?, end = ?, all_day = ?, color = ? WHERE id = ?');
    $stmt->execute([$title, $description, $start, $end, $all_day, $color, $id]);

    echo json_encode(['success' => true]);
    exit;
}

if ($action === 'delete') {
    $id = (int)($_GET['id'] ?? 0);
    if (!$id) {
        http_response_code(400);
        echo json_encode(['error' => 'Identifiant manquant']);
        exit;
    }
    $stmt = $pdo->prepare('DELETE FROM events WHERE id = ?');
    $stmt->execute([$id]);
    echo json_encode(['success' => true]);
    exit;
}

// Si l'action n'est pas reconnue, renvoyer une erreur
http_response_code(400);
echo json_encode(['error' => 'Action inconnue']);
exit;

Ce fichier reçoit les requêtes du client et effectue les opérations appropriées. L’action est fournie par le paramètre action. Pour la lecture (list), l’API attend deux dates start et end et renvoie les événements qui intersectent la période demandée.

Pour create et update, elle lit le corps JSON et utilise des requêtes préparées pour éviter les injections SQL. Pour delete on supprime par identifiant.

En production il faudra ajouter des vérifications d’authentification et de validation plus strictes. Ici, pour l’apprentissage, l’important est la logique de base.

Formation web et informatique - Alban Guillier - Formateur

Des formations informatique pour tous !

Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…

Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.

Découvrez mes formations Qui suis-je ?

index.php — interface HTML + inclusion des assets

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Créer un agenda mensuel</title>
  <link rel="stylesheet" href="assets/style.css">
</head>
<body>
  <main class="container">
    <header class="header">
      <h1>Agenda — Vue mensuelle</h1>
      <div class="controls">
        <button id="prevBtn">‹</button>
        <div id="monthLabel"></div>
        <button id="nextBtn">›</button>
      </div>
    </header>

    <section id="calendar"></section>

    <!-- Modal simple pour créer/modifier un événement -->
    <div id="modal" class="modal hidden">
      <div class="modal-content">
        <h2 id="modalTitle">Nouvel événement</h2>
        <form id="eventForm">
          <label for="title">Titre</label>
          <input type="text" id="title" name="title" required>

          <label for="start">Début</label>
          <input type="datetime-local" id="start" name="start" required>

          <label for="end">Fin</label>
          <input type="datetime-local" id="end" name="end" required>

          <label for="all_day">
            <input type="checkbox" id="all_day" name="all_day">
            Toute la journée
          </label>

          <label for="color">Couleur</label>
          <input type="color" id="color" name="color" value="#3788d8">

          <label for="description">Description</label>
          <textarea id="description" name="description" rows="4"></textarea>

          <input type="hidden" id="eventId" name="id" value="">

          <div class="modal-actions">
            <button type="submit" id="saveBtn">Enregistrer</button>
            <button type="button" id="deleteBtn" class="danger hidden">Supprimer</button>
            <button type="button" id="cancelBtn">Annuler</button>
          </div>
        </form>
      </div>
    </div>

  </main>

  <script src="assets/script.js"></script>
</body>
</html>

Le fichier contient la structure HTML de la page. Un header avec le titre et des boutons de navigation (précédent / suivant). La section#calendar accueillera la grille du mois. Le modal est un simple formulaire qui s’ouvre pour créer ou modifier un événement. Les champs sont standards : titre, début, fin, case « toute la journée », couleur, description. Un champ caché eventId permet de savoir s’il s’agit d’une création ou d’une modification.

assets/style.css — style simple et responsive

/* assets/style.css - styles basiques pour l'agenda */

:root {
  --bg: #f5f7fb;
  --card: #ffffff;
  --text: #222;
  --accent: #3788d8;
  --danger: #c0392b;
}

* { box-sizing: border-box; }
body { margin: 0; font-family: Arial, Helvetica, sans-serif; background: var(--bg); color: var(--text); }
.container { max-width: 1100px; margin: 24px auto; padding: 16px; }

.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.controls { display: flex; gap: 8px; align-items: center; }
.controls button { padding: 6px 10px; border: none; background: var(--accent); color: white; border-radius: 4px; cursor: pointer; }
.controls #monthLabel { font-weight: bold; min-width: 220px; text-align: center; }

#calendar { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; }

.day-header { background: transparent; text-align: center; padding: 6px 0; font-weight: bold; color: #666; }

.day {
  min-height: 100px;
  background: var(--card);
  padding: 8px;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
  position: relative;
  cursor: pointer;
}

.day .date-number { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.day .events { display: flex; flex-direction: column; gap: 4px; }

.event {
  padding: 4px 6px;
  border-radius: 4px;
  color: #fff;
  font-size: 13px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.other-month { opacity: 0.45; }

.modal { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.35); z-index: 50; }
.hidden { display: none; }
.modal-content { background: var(--card); padding: 16px; border-radius: 8px; width: 360px; box-shadow: 0 6px 20px rgba(0,0,0,0.15); }
.modal-content label { display: block; margin-top: 8px; font-size: 13px; }
.modal-content input[type="text"], .modal-content input[type="datetime-local"], .modal-content textarea, .modal-content input[type="color"] {
  width: 100%; padding: 6px; margin-top: 4px; border: 1px solid #ddd; border-radius: 4px;
}
.modal-actions { display: flex; gap: 8px; margin-top: 12px; justify-content: flex-end; }
button.danger { background: var(--danger); color: #fff; border: none; padding: 8px 10px; border-radius: 4px; cursor: pointer; }
button#cancelBtn { background: #eee; border: none; padding: 8px 10px; border-radius: 4px; cursor: pointer; }
@media (max-width: 700px) {
  .modal-content { width: 92%; }
  #calendar { grid-template-columns: repeat(7, 1fr); }
}

Le CSS utilise une grille CSS à sept colonnes pour afficher les jours de la semaine. Les classes .day correspondent aux cases jours. .event représente un petit bloc coloré dans la case. Le modal est centré à l’écran et masqué par la classe .hidden.

assets/script.js — logique JavaScript de la vue mensuelle

// assets/script.js
// Ce script crée la vue mensuelle, gère la navigation et la communication avec l'API.

const apiUrl = 'api.php';
let currentDate = new Date(); // date courante pour construire le mois affiché

// Sélecteurs DOM
const calendarEl = document.getElementById('calendar');
const monthLabel = document.getElementById('monthLabel');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const modal = document.getElementById('modal');
const eventForm = document.getElementById('eventForm');
const modalTitle = document.getElementById('modalTitle');
const deleteBtn = document.getElementById('deleteBtn');
const cancelBtn = document.getElementById('cancelBtn');

// Initialisation
document.addEventListener('DOMContentLoaded', () => {
  renderCalendar();
  prevBtn.addEventListener('click', () => changeMonth(-1));
  nextBtn.addEventListener('click', () => changeMonth(1));
  cancelBtn.addEventListener('click', hideModal);
});

// Changer de mois
function changeMonth(delta) {
  currentDate.setMonth(currentDate.getMonth() + delta);
  renderCalendar();
}

// Rendu de la grille mensuelle
async function renderCalendar() {
  calendarEl.innerHTML = '';
  const year = currentDate.getFullYear();
  const month = currentDate.getMonth(); // 0-11
  monthLabel.textContent = currentDate.toLocaleString('fr-FR', { month: 'long', year: 'numeric' });

  // En-têtes des jours
  const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
  for (let d = 0; d < 7; d++) {
    const h = document.createElement('div');
    h.className = 'day-header';
    h.textContent = days[d];
    calendarEl.appendChild(h);
  }

  // Calcul du premier jour utile à afficher (début de semaine: lundi)
  const firstOfMonth = new Date(year, month, 1);
  const firstDayIndex = (firstOfMonth.getDay() + 6) % 7; // convertit dimanche=0 en 6, lundi=0
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const prevMonthDays = new Date(year, month, 0).getDate();

  // On veut afficher 6 semaines (42 cases) pour une grille régulière
  const totalCells = 42;
  // Calcul des bornes pour la récupération des événements
  const startDate = new Date(year, month, 1 - firstDayIndex);
  const endDate = new Date(startDate);
  endDate.setDate(startDate.getDate() + totalCells - 1);

  // Format YYYY-MM-DD pour l'API
  const startStr = formatDateISODate(startDate);
  const endStr = formatDateISODate(endDate);

  // Récupérer les événements du mois (période)
  const events = await fetchEvents(startStr, endStr);

  // Construire chaque case
  for (let i = 0; i < totalCells; i++) {
    const cellDate = new Date(startDate);
    cellDate.setDate(startDate.getDate() + i);
    const cell = document.createElement('div');
    cell.className = 'day';
    // Si le jour n'appartient pas au mois courant, on ajoute une classe
    if (cellDate.getMonth() !== month) {
      cell.classList.add('other-month');
    }

    // Numéro du jour
    const dateNumber = document.createElement('div');
    dateNumber.className = 'date-number';
    dateNumber.textContent = cellDate.getDate();
    cell.appendChild(dateNumber);

    // Conteneur d'événements
    const eventsContainer = document.createElement('div');
    eventsContainer.className = 'events';

    // Filtrer les événements qui s'affichent ce jour
    const dayStr = formatDateISODate(cellDate);
    const dayEvents = events.filter(ev => {
      // ev.start et ev.end sont au format 'YYYY-MM-DD HH:MM:SS'
      const evStart = ev.start.split(' ')[0];
      const evEnd = ev.end.split(' ')[0];
      // Si l'événement couvre la date cellDate (inclus)
      return (evStart <= dayStr && evEnd >= dayStr);
    });

    // Ajouter un aperçu pour chaque événement
    dayEvents.forEach(ev => {
      const evEl = document.createElement('div');
      evEl.className = 'event';
      evEl.textContent = ev.title;
      evEl.style.background = ev.color || '#3788d8';
      evEl.title = ev.title + '\n' + (ev.description || '');
      // ouvrir modal pour modifier au clic
      evEl.addEventListener('click', (e) => {
        e.stopPropagation();
        openModalWithEvent(ev);
      });
      eventsContainer.appendChild(evEl);
    });

    // Clic sur la case pour créer un nouvel événement
    cell.addEventListener('click', () => {
      openModalForDate(cellDate);
    });

    cell.appendChild(eventsContainer);
    calendarEl.appendChild(cell);
  }
}

// Formater date au format YYYY-MM-DD utilisé par l'API pour les périodes
function formatDateISODate(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${day}`;
}

// Récupérer les événements via l'API
async function fetchEvents(start, end) {
  try {
    const res = await fetch(`${apiUrl}?action=list&start=${start}&end=${end}`);
    const data = await res.json();
    return Array.isArray(data) ? data : [];
  } catch (err) {
    console.error('Erreur récupération événements', err);
    return [];
  }
}

// Ouvrir modal pour création
function openModalForDate(date) {
  resetForm();
  modalTitle.textContent = 'Nouvel événement';
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  // On positionne le début à 09:00 et la fin à 10:00 par défaut
  document.getElementById('start').value = `${y}-${m}-${d}T09:00`;
  document.getElementById('end').value = `${y}-${m}-${d}T10:00`;
  document.getElementById('eventId').value = '';
  deleteBtn.classList.add('hidden');
  showModal();
}

// Ouvrir modal avec un événement existant
function openModalWithEvent(ev) {
  resetForm();
  modalTitle.textContent = 'Modifier l\'événement';
  document.getElementById('title').value = ev.title;
  document.getElementById('description').value = ev.description || '';
  document.getElementById('color').value = ev.color || '#3788d8';
  document.getElementById('all_day').checked = ev.all_day == 1;
  // Convertir 'YYYY-MM-DD HH:MM:SS' en 'YYYY-MM-DDTHH:MM'
  document.getElementById('start').value = ev.start.replace(' ', 'T').slice(0,16);
  document.getElementById('end').value = ev.end.replace(' ', 'T').slice(0,16);
  document.getElementById('eventId').value = ev.id;
  deleteBtn.classList.remove('hidden');
  showModal();

  // Attacher comportement suppression
  deleteBtn.onclick = async () => {
    if (!confirm('Voulez-vous vraiment supprimer cet événement ?')) return;
    await fetch(`${apiUrl}?action=delete&id=${ev.id}`);
    hideModal();
    renderCalendar();
  };
}

// Montrer / cacher modal
function showModal() { modal.classList.remove('hidden'); }
function hideModal() { modal.classList.add('hidden'); }

// Réinitialiser le formulaire
function resetForm() {
  eventForm.reset();
  document.getElementById('eventId').value = '';
}

// Gérer l'envoi du formulaire (création ou mise à jour)
eventForm.addEventListener('submit', async (e) => {
  e.preventDefault();
  const id = document.getElementById('eventId').value;
  const title = document.getElementById('title').value.trim();
  const description = document.getElementById('description').value.trim();
  const start = document.getElementById('start').value;
  const end = document.getElementById('end').value;
  const all_day = document.getElementById('all_day').checked ? 1 : 0;
  const color = document.getElementById('color').value;

  // Vérifications simples
  if (!title || !start || !end) {
    alert('Veuillez remplir les champs obligatoires.');
    return;
  }
  if (start > end) {
    alert('La date de début doit être antérieure à la date de fin.');
    return;
  }

  // Convertir 'YYYY-MM-DDTHH:MM' vers 'YYYY-MM-DD HH:MM:SS'
  const startSql = start.replace('T', ' ') + ':00';
  const endSql = end.replace('T', ' ') + ':00';

  const payload = { title, description, start: startSql, end: endSql, all_day, color };

  if (id) {
    // mise à jour
    payload.id = id;
    await fetch(apiUrl + '?action=update', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
  } else {
    // création
    await fetch(apiUrl + '?action=create', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
  }

  hideModal();
  renderCalendar();
});

Le script initialise currentDate et rend la grille mensuelle en créant 42 cases (6 semaines) pour garder une mise en page constante. Il calcule la date de départ et la date de fin affichées, interroge l’API pour récupérer les événements dans cette période, puis affiche dans chaque case les événements qui couvrent ce jour.

Les événements sont cliquables pour ouvrir le modal en modification. Le formulaire envoie ensuite les données à l’API en JSON.

Le format datetime-local du navigateur montre une saisie confortable pour l’heure et la date. Le script convertit ces valeurs vers un format accepté par MySQL (YYYY-MM-DD HH:MM:SS). La fonction formatDateISODate produit YYYY-MM-DD pour l’API de période. Les requêtes réseau utilisent fetch; la gestion des erreurs est basique ici.

Installation et test pas à pas

  1. Installez un serveur local : XAMPP, MAMP ou Laragon. Démarrez Apache et MySQL.
  2. Créez la base agenda_db et la table events en exécutant le SQL donné plus haut via phpMyAdmin ou la console MySQL.
  3. Copiez les fichiers index.phpapi.phpdb.php et le dossier assets dans le dossier root de votre projet (par exemple htdocs/agenda pour XAMPP).
  4. Ajustez les identifiants dans db.php si nécessaire (utilisateur, mot de passe).
  5. Ouvrez le navigateur sur http://localhost/agenda/index.php. Vous devriez voir la grille du mois.
  6. Cliquez sur une case pour créer un événement. Vérifiez qu’il s’affiche immédiatement. Cliquez sur un événement pour le modifier ou le supprimer.

Pourquoi utiliser PDO et requêtes préparées ?

PDO permet d’indiquer la base de données et d’exécuter des requêtes. Les requêtes préparées empêchent les injections SQL. Même si votre projet est local, il est essentiel d’apprendre les bonnes pratiques dès le départ.

Comment le front communique avec le back ?

Le front réalise des appels fetch vers api.php en ajoutant le paramètre action. Pour la lecture (list) on utilise GET avec start et end. Pour créer ou modifier, on envoie du JSON en POST. L’API renvoie du JSON, que le front interprète.

Gestion des événements multi-jours

La condition SQL dans api.php?action=list recherche les événements qui intersectent la période demandée. Ainsi un événement qui commence avant le mois et finit pendant ou après sera quand même renvoyé.

Limites de cette version de l’agenda

Cette version ne gère pas les utilisateurs (authentification), les fuseaux horaires complexes, ni les événements récurrents. Elle n’inclut pas non plus de protection CSRF. Pour un usage en production, il faudra ajouter l’authentification, la séparation par utilisateur et des vérifications supplémentaires.

Améliorations possibles de notre projet

Voici quelques idées pour enrichir votre agenda une fois la version de base maîtrisée.

  • Vous pouvez ajouter l’authentification pour que chaque utilisateur ait son agenda.
  • Vous pouvez implémenter des événements récurrents (quotidiens, hebdomadaires, mensuels).
  • Vous pouvez ajouter une vue hebdomadaire ou quotidienne. Vous pouvez permettre le glisser-déposer (drag & drop) pour réaffecter la date d’un événement, intégrer une synchronisation avec Google Calendar ou exporter en format ICS.
  • Enfin, l’affichage des heures, la gestion des fuseaux horaires ou la pagination des événements dans un jour chargé sont de bonnes améliorations UX.

Vous venez de parcourir un tutoriel complet pour construire un agenda avec une vue mensuelle en HTML, CSS, JavaScript et PHP/MySQL. Vous avez appris à concevoir la base de données, écrire une API simple et sécurisée, rendre une interface interactive et gérer des opérations CRUD via AJAX.

Ce tutoriel vous donne une base solide pour créer un agenda fonctionnel et l’adapter à vos besoins. La suite logique est d’ajouter l’authentification utilisateur et d’améliorer la robustesse (validation, sécurité, gestion des fuseaux horaires). En maîtrisant ces briques, vous transformerez un simple affichage mensuel en une application de planning complète, capable de répondre à des usages personnels ou professionnels.

Si vous souhaitez que je vous fournisse la même chose mais avec une gestion multi-utilisateurs (inscription / connexion) ou l’ajout d’une vue hebdomadaire et d’un export ICS, je peux vous fournir le code détaillé et sécurisé pour ces fonctionnalités. Voulez-vous que l’on ajoute maintenant la gestion des utilisateurs et une table users ainsi que la liaison user_id dans la table events afin de rendre l’agenda privé par utilisateur ?

Merci d’avoir lu ce tutoriel.