Agenda Dynamique - Bootstrap 5

🏷️ Extraits de code HTML 📅 25/03/2026 12:00:00 👤 Mezgani said
Bootstrap Bootstrap5 Agenda Reactive Html Js

Template Bootstrap 5 d'agenda dynamique avec interactions, structure reactive et mise en page moderne.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8" />
  <meta name="copyright" content="MEZGANI Said" />
  <meta name="author" content="AngularForAll" />
  <meta name="robots" content="noindex, nofollow" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Snippet Agenda Reactive Bootstrap 5 02 | AngularForAll</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
  
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Segoe UI', Arial, sans-serif;
      background: linear-gradient(145deg, #f5f7fa 0%, #e9ecf2 100%);
      min-height: 100vh;
      padding: 20px;
      color: #1e293b;
    }

    .container {
      max-width: 1300px;
      margin: 0 auto;
    }

    h1 {
      font-size: 2rem;
      font-weight: 700;
      margin-bottom: 20px;
      color: #0f172a;
    }

    .header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
      flex-wrap: wrap;
      gap: 15px;
    }

    .nav-buttons {
      display: flex;
      gap: 10px;
    }

    .btn {
      padding: 10px 20px;
      border: none;
      border-radius: 10px;
      font-weight: 500;
      cursor: pointer;
      transition: all 0.2s;
      font-size: 14px;
    }

    .btn-primary {
      background: #2563eb;
      color: white;
    }

    .btn-primary:hover {
      background: #1d4ed8;
    }

    .btn-outline {
      background: white;
      color: #1e293b;
      border: 1px solid #cbd5e1;
    }

    .btn-outline:hover {
      background: #f8fafc;
    }

    .calendar-grid {
      display: grid;
      grid-template-columns: 1fr 350px;
      gap: 20px;
    }

    @media (max-width: 900px) {
      .calendar-grid {
        grid-template-columns: 1fr;
      }
    }

    .calendar-card {
      background: white;
      border-radius: 20px;
      padding: 20px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.08);
    }

    .month-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
    }

    .month-header h2 {
      font-size: 1.5rem;
      font-weight: 600;
    }

    .weekdays {
      display: grid;
      grid-template-columns: repeat(7, 1fr);
      text-align: center;
      margin-bottom: 10px;
    }

    .weekday {
      font-weight: 600;
      color: #64748b;
      font-size: 0.9rem;
      padding: 8px 0;
    }

    .days-grid {
      display: grid;
      grid-template-columns: repeat(7, 1fr);
      gap: 5px;
    }

    .day-cell {
      aspect-ratio: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      border-radius: 12px;
      font-weight: 500;
      cursor: pointer;
      background: #fafbfc;
      transition: all 0.2s;
      padding: 5px;
      border: 2px solid transparent;
    }

    .day-cell:hover {
      background: #eef2ff;
    }

    .day-cell.other-month {
      color: #94a3b8;
    }

    .day-cell.today {
      background: #2563eb;
      color: white;
    }

    .day-cell.selected {
      background: #7c3aed;
      color: white;
    }

    .event-dots {
      display: flex;
      gap: 2px;
      margin-top: 3px;
    }

    .dot {
      width: 5px;
      height: 5px;
      border-radius: 50%;
    }

    .sidebar {
      background: white;
      border-radius: 20px;
      padding: 20px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.08);
    }

    .sidebar h3 {
      margin-bottom: 15px;
      font-size: 1.2rem;
    }

    .selected-date {
      background: #eef2ff;
      padding: 8px 15px;
      border-radius: 20px;
      display: inline-block;
      margin-bottom: 15px;
      font-size: 0.9rem;
      color: #2563eb;
    }

    .event-list {
      display: flex;
      flex-direction: column;
      gap: 10px;
      margin-bottom: 20px;
    }

    .event-item {
      padding: 12px;
      background: #fafbfc;
      border-radius: 12px;
      border-left: 4px solid #2563eb;
      cursor: pointer;
    }

    .event-item:hover {
      background: #eef2ff;
    }

    .event-title {
      font-weight: 600;
      margin-bottom: 5px;
    }

    .event-time {
      font-size: 0.85rem;
      color: #64748b;
    }

    .event-category {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 15px;
      font-size: 0.7rem;
      color: white;
      margin-left: 8px;
    }

    .no-events {
      text-align: center;
      color: #94a3b8;
      padding: 30px;
    }

    /* Modal */
    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0,0,0,0.5);
      z-index: 1000;
      align-items: center;
      justify-content: center;
    }

    .modal-content {
      background: white;
      border-radius: 20px;
      padding: 25px;
      width: 90%;
      max-width: 450px;
      max-height: 90vh;
      overflow-y: auto;
    }

    .modal-content h3 {
      margin-bottom: 20px;
    }

    .form-group {
      margin-bottom: 18px;
    }

    .form-group label {
      display: block;
      margin-bottom: 6px;
      font-weight: 500;
      font-size: 0.9rem;
    }

    .form-group input,
    .form-group textarea,
    .form-group select {
      width: 100%;
      padding: 10px 12px;
      border: 1.5px solid #e2e8f0;
      border-radius: 10px;
      font-size: 0.95rem;
    }

    .form-group input:focus,
    .form-group textarea:focus,
    .form-group select:focus {
      outline: none;
      border-color: #2563eb;
    }

    .modal-actions {
      display: flex;
      gap: 10px;
      margin-top: 20px;
    }

    .modal-actions button {
      flex: 1;
    }

    .category-filters {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      margin-bottom: 15px;
    }

    .filter-btn {
      padding: 6px 14px;
      border-radius: 20px;
      border: none;
      cursor: pointer;
      color: white;
      font-size: 0.8rem;
      opacity: 0.6;
      transition: all 0.2s;
    }

    .filter-btn:hover {
      opacity: 0.8;
    }

    .filter-btn.active {
      opacity: 1;
    }

    .delete-btn {
      background: #ef4444;
      color: white;
    }

    .delete-btn:hover {
      background: #dc2626;
    }
  </style>
</head>
<body>

<div class="container">
  
  <div class="header">
    <h1>📅 Mon Agenda</h1>
    <div class="nav-buttons">
      <button class="btn btn-outline" id="prevMonthBtn">← Mois précédent</button>
      <button class="btn btn-outline" id="todayBtn">Aujourd'hui</button>
      <button class="btn btn-outline" id="nextMonthBtn">Mois suivant →</button>
      <button class="btn btn-primary" id="addEventBtn">+ Nouvel événement</button>
    </div>
  </div>

  <div class="category-filters" id="categoryFilters"></div>

  <div class="calendar-grid">
    
    <div class="calendar-card">
      <div class="month-header">
        <h2 id="monthYearDisplay">Avril 2026</h2>
      </div>
      <div class="weekdays" id="weekdays"></div>
      <div class="days-grid" id="daysGrid"></div>
    </div>

    <div class="sidebar">
      <h3>Événements</h3>
      <div class="selected-date" id="selectedDateDisplay">Aujourd'hui</div>
      <div class="event-list" id="eventList"></div>
      <button class="btn btn-primary" style="width: 100%;" id="sidebarAddBtn">+ Ajouter un événement</button>
    </div>
  </div>
</div>

<!-- Modal -->
<div class="modal" id="eventModal">
  <div class="modal-content">
    <h3 id="modalTitle">Nouvel événement</h3>
    
    <form id="eventForm">
      <input type="hidden" id="eventId">
      <input type="hidden" id="eventDate" value="">
      
      <div class="form-group">
        <label>Titre *</label>
        <input type="text" id="eventTitle" required placeholder="Ex: Réunion d'équipe">
      </div>
      
      <div class="form-group">
        <label>Catégorie</label>
        <select id="eventCategory">
          <option value="meeting">👥 Réunion</option>
          <option value="personal">🏠 Personnel</option>
          <option value="work">💼 Travail</option>
          <option value="important">⭐ Important</option>
          <option value="reminder">🔔 Rappel</option>
          <option value="other">📌 Autre</option>
        </select>
      </div>
      
      <div class="form-group">
        <label>Heure *</label>
        <input type="time" id="eventTime" required>
      </div>
      
      <div class="form-group">
        <label>Description</label>
        <textarea id="eventDescription" rows="2" placeholder="Détails..."></textarea>
      </div>
      
      <div class="modal-actions">
        <button type="button" class="btn btn-outline" id="cancelModalBtn">Annuler</button>
        <button type="submit" class="btn btn-primary">Enregistrer</button>
      </div>
    </form>
    
    <div id="deleteSection" style="display: none; margin-top: 15px;">
      <button class="btn delete-btn" id="deleteEventBtn" style="width: 100%;">🗑️ Supprimer</button>
    </div>
  </div>
</div>

<script>
  (function() {
    "use strict";
    
    console.log('🚀 Agenda - Démarrage...');
    
    // Catégories avec couleurs
    const categories = {
      meeting: { name: 'Réunion', color: '#2563eb', icon: '👥' },
      personal: { name: 'Personnel', color: '#10b981', icon: '🏠' },
      work: { name: 'Travail', color: '#7c3aed', icon: '💼' },
      important: { name: 'Important', color: '#ef4444', icon: '⭐' },
      reminder: { name: 'Rappel', color: '#f59e0b', icon: '🔔' },
      other: { name: 'Autre', color: '#64748b', icon: '📌' }
    };
    
    // État de l'application
    let events = [];
    let currentYear = new Date().getFullYear();
    let currentMonth = new Date().getMonth();
    let selectedDate = new Date();
    let activeFilter = 'all';
    let editingId = null;
    
    const weekdaysFR = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
    const monthsFR = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
    
    // Fonctions utilitaires
    function formatDate(date) {
      return date.getFullYear() + '-' + 
             String(date.getMonth() + 1).padStart(2, '0') + '-' + 
             String(date.getDate()).padStart(2, '0');
    }
    
    function isSameDay(d1, d2) {
      return d1.getFullYear() === d2.getFullYear() &&
             d1.getMonth() === d2.getMonth() &&
             d1.getDate() === d2.getDate();
    }
    
    // Événements de démo
    function addDemoEvents() {
      const today = new Date();
      const tomorrow = new Date(today);
      tomorrow.setDate(tomorrow.getDate() + 1);
      const nextWeek = new Date(today);
      nextWeek.setDate(nextWeek.getDate() + 7);
      
      events = [
        { id: 1, title: 'Réunion design', date: formatDate(today), time: '10:00', description: 'Brainstorming', category: 'meeting' },
        { id: 2, title: 'Déjeuner client', date: formatDate(today), time: '12:30', description: 'Restaurant', category: 'work' },
        { id: 3, title: 'Présentation', date: formatDate(tomorrow), time: '14:00', description: 'Salle comité', category: 'important' },
        { id: 4, title: 'Workshop', date: formatDate(nextWeek), time: '09:00', description: '', category: 'work' },
        { id: 5, title: 'Anniversaire', date: formatDate(nextWeek), time: '19:00', description: 'Apporter cadeau', category: 'personal' }
      ];
    }
    
    // Rendu du calendrier
    function renderCalendar() {
      console.log('📅 Rendu calendrier:', currentMonth + 1, currentYear);
      
      // En-tête
      document.getElementById('monthYearDisplay').textContent = monthsFR[currentMonth] + ' ' + currentYear;
      document.getElementById('weekdays').innerHTML = weekdaysFR.map(d => `<div class="weekday">${d}</div>`).join('');
      
      // Jours
      const firstDay = new Date(currentYear, currentMonth, 1);
      const lastDay = new Date(currentYear, currentMonth + 1, 0);
      
      let startOffset = firstDay.getDay() - 1;
      if (startOffset < 0) startOffset = 6;
      
      const daysGrid = document.getElementById('daysGrid');
      daysGrid.innerHTML = '';
      
      let dayCount = 0;
      const totalCells = 42; // 6 semaines
      
      for (let i = 0; i < totalCells; i++) {
        let date, isOtherMonth = false;
        
        if (i < startOffset) {
          date = new Date(currentYear, currentMonth, 1 - (startOffset - i));
          isOtherMonth = true;
        } else if (dayCount < lastDay.getDate()) {
          dayCount++;
          date = new Date(currentYear, currentMonth, dayCount);
        } else {
          date = new Date(currentYear, currentMonth + 1, dayCount - lastDay.getDate() + 1);
          isOtherMonth = true;
        }
        
        const dateStr = formatDate(date);
        const isToday = isSameDay(date, new Date());
        const isSelected = isSameDay(date, selectedDate);
        
        const dayEvents = events.filter(e => e.date === dateStr && 
                                            (activeFilter === 'all' || e.category === activeFilter));
        
        const cell = document.createElement('div');
        cell.className = 'day-cell' + (isOtherMonth ? ' other-month' : '') + 
                                       (isToday ? ' today' : '') + 
                                       (isSelected ? ' selected' : '');
        cell.setAttribute('data-date', dateStr);
        
        const dotsHtml = dayEvents.slice(0, 3).map(e => {
          const cat = categories[e.category] || categories.other;
          return `<span class="dot" style="background: ${cat.color};"></span>`;
        }).join('');
        
        cell.innerHTML = date.getDate() + (dotsHtml ? `<div class="event-dots">${dotsHtml}</div>` : '');
        
        cell.addEventListener('click', function() {
          const clickedDate = this.getAttribute('data-date');
          if (clickedDate) {
            selectedDate = new Date(clickedDate);
            renderCalendar();
            updateSelectedDateDisplay();
            renderEvents();
          }
        });
        
        daysGrid.appendChild(cell);
      }
    }
    
    // Filtres de catégories
    function renderFilters() {
      const container = document.getElementById('categoryFilters');
      let html = `<button class="filter-btn ${activeFilter === 'all' ? 'active' : ''}" 
                   style="background: #64748b;" data-filter="all">Toutes</button>`;
      
      Object.entries(categories).forEach(([id, cat]) => {
        html += `<button class="filter-btn ${activeFilter === id ? 'active' : ''}" 
                  style="background: ${cat.color};" data-filter="${id}">${cat.icon} ${cat.name}</button>`;
      });
      
      container.innerHTML = html;
      
      container.querySelectorAll('.filter-btn').forEach(btn => {
        btn.addEventListener('click', function() {
          activeFilter = this.getAttribute('data-filter');
          renderFilters();
          renderCalendar();
          renderEvents();
        });
      });
    }
    
    // Affichage de la date sélectionnée
    function updateSelectedDateDisplay() {
      const today = new Date();
      const display = document.getElementById('selectedDateDisplay');
      
      if (isSameDay(selectedDate, today)) {
        display.textContent = "Aujourd'hui";
      } else {
        display.textContent = selectedDate.getDate() + ' ' + 
                             monthsFR[selectedDate.getMonth()].substring(0, 3) + ' ' + 
                             selectedDate.getFullYear();
      }
    }
    
    // Rendu des événements du jour
    function renderEvents() {
      const container = document.getElementById('eventList');
      const dateStr = formatDate(selectedDate);
      
      let dayEvents = events.filter(e => e.date === dateStr);
      if (activeFilter !== 'all') {
        dayEvents = dayEvents.filter(e => e.category === activeFilter);
      }
      
      dayEvents.sort((a, b) => a.time.localeCompare(b.time));
      
      if (dayEvents.length === 0) {
        container.innerHTML = '<div class="no-events">✨ Aucun événement ce jour</div>';
        return;
      }
      
      container.innerHTML = dayEvents.map(e => {
        const cat = categories[e.category] || categories.other;
        return `
          <div class="event-item" data-id="${e.id}">
            <div class="event-title">
              ${e.title}
              <span class="event-category" style="background: ${cat.color};">${cat.icon} ${cat.name}</span>
            </div>
            <div class="event-time">🕐 ${e.time}</div>
            ${e.description ? `<div style="font-size: 0.85rem; color: #64748b; margin-top: 5px;">${e.description}</div>` : ''}
            <div style="margin-top: 8px; display: flex; gap: 8px;">
              <button class="btn btn-outline edit-btn" data-id="${e.id}" style="padding: 4px 10px; font-size: 0.8rem;">✏️ Modifier</button>
              <button class="btn delete-btn delete-event-btn" data-id="${e.id}" style="padding: 4px 10px; font-size: 0.8rem;">🗑️</button>
            </div>
          </div>
        `;
      }).join('');
      
      // Attacher événements aux boutons
      container.querySelectorAll('.edit-btn').forEach(btn => {
        btn.addEventListener('click', function(e) {
          e.stopPropagation();
          const id = parseInt(this.getAttribute('data-id'));
          const event = events.find(ev => ev.id === id);
          if (event) editEvent(event);
        });
      });
      
      container.querySelectorAll('.delete-event-btn').forEach(btn => {
        btn.addEventListener('click', function(e) {
          e.stopPropagation();
          const id = parseInt(this.getAttribute('data-id'));
          if (confirm('Supprimer cet événement ?')) {
            events = events.filter(ev => ev.id !== id);
            renderCalendar();
            renderEvents();
          }
        });
      });
      
      container.querySelectorAll('.event-item').forEach(item => {
        item.addEventListener('click', function(e) {
          if (!e.target.closest('button')) {
            const id = parseInt(this.getAttribute('data-id'));
            const event = events.find(ev => ev.id === id);
            if (event) editEvent(event);
          }
        });
      });
    }
    
    // Modal
    function openModal(eventData = null) {
      const modal = document.getElementById('eventModal');
      const title = document.getElementById('modalTitle');
      const form = document.getElementById('eventForm');
      const deleteSection = document.getElementById('deleteSection');
      
      if (eventData) {
        editingId = eventData.id;
        title.textContent = 'Modifier l\'événement';
        document.getElementById('eventId').value = eventData.id;
        document.getElementById('eventTitle').value = eventData.title;
        document.getElementById('eventCategory').value = eventData.category;
        document.getElementById('eventTime').value = eventData.time;
        document.getElementById('eventDescription').value = eventData.description || '';
        document.getElementById('eventDate').value = eventData.date;
        deleteSection.style.display = 'block';
      } else {
        editingId = null;
        title.textContent = 'Nouvel événement';
        document.getElementById('eventId').value = '';
        document.getElementById('eventTitle').value = '';
        document.getElementById('eventCategory').value = 'meeting';
        document.getElementById('eventTime').value = '';
        document.getElementById('eventDescription').value = '';
        document.getElementById('eventDate').value = formatDate(selectedDate);
        deleteSection.style.display = 'none';
      }
      
      modal.style.display = 'flex';
    }
    
    function closeModal() {
      document.getElementById('eventModal').style.display = 'none';
      editingId = null;
    }
    
    function editEvent(event) {
      openModal(event);
    }
    
    function saveEvent(e) {
      e.preventDefault();
      
      const id = document.getElementById('eventId').value;
      const title = document.getElementById('eventTitle').value.trim();
      const category = document.getElementById('eventCategory').value;
      const time = document.getElementById('eventTime').value;
      const description = document.getElementById('eventDescription').value.trim();
      const date = document.getElementById('eventDate').value;
      
      if (!title || !time) {
        alert('Titre et heure sont obligatoires');
        return;
      }
      
      if (id) {
        const index = events.findIndex(ev => ev.id === parseInt(id));
        if (index !== -1) {
          events[index] = { ...events[index], title, category, time, description, date };
        }
      } else {
        events.push({
          id: Date.now(),
          title,
          category,
          time,
          description,
          date
        });
      }
      
      closeModal();
      
      if (date !== formatDate(selectedDate)) {
        selectedDate = new Date(date);
        updateSelectedDateDisplay();
      }
      
      renderCalendar();
      renderEvents();
    }
    
    function deleteEvent() {
      if (editingId) {
        events = events.filter(e => e.id !== editingId);
        closeModal();
        renderCalendar();
        renderEvents();
      }
    }
    
    // Navigation
    function prevMonth() {
      currentMonth--;
      if (currentMonth < 0) {
        currentMonth = 11;
        currentYear--;
      }
      renderCalendar();
      renderEvents();
    }
    
    function nextMonth() {
      currentMonth++;
      if (currentMonth > 11) {
        currentMonth = 0;
        currentYear++;
      }
      renderCalendar();
      renderEvents();
    }
    
    function goToToday() {
      currentYear = new Date().getFullYear();
      currentMonth = new Date().getMonth();
      selectedDate = new Date();
      renderCalendar();
      updateSelectedDateDisplay();
      renderEvents();
    }
    
    // Initialisation
    function init() {
      console.log('✅ Initialisation...');
      
      addDemoEvents();
      renderFilters();
      renderCalendar();
      updateSelectedDateDisplay();
      renderEvents();
      
      // Attacher événements
      document.getElementById('prevMonthBtn').addEventListener('click', prevMonth);
      document.getElementById('nextMonthBtn').addEventListener('click', nextMonth);
      document.getElementById('todayBtn').addEventListener('click', goToToday);
      document.getElementById('addEventBtn').addEventListener('click', () => openModal());
      document.getElementById('sidebarAddBtn').addEventListener('click', () => openModal());
      document.getElementById('cancelModalBtn').addEventListener('click', closeModal);
      document.getElementById('deleteEventBtn').addEventListener('click', deleteEvent);
      
      document.getElementById('eventForm').addEventListener('submit', saveEvent);
      
      document.getElementById('eventModal').addEventListener('click', function(e) {
        if (e.target === this) closeModal();
      });
      
      console.log('✅ Prêt !');
    }
    
    init();
  })();
</script>

</body>
</html>

Ouvrir cet aperçu dans un nouvel onglet du navigateur

🔗 Ouvrir dans le navigateur