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