Composants HTML & CSS angularforall.com

- Calendrier Complet Bootstrap 5

Bootstrap 5 Calendar Fullcalendar Evenements Dynamique Javascript

Calendrier dynamique Bootstrap 5 avec gestion complète d'événements, vues mensuelle/semaine, création rapide événements et notifications.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8" />
    <meta name="copyright" content="AngularForAll" />
    <meta name="author" content="AngularForAll" />
    <meta name="robots" content="noindex, nofollow" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Cache-Control" content="public, max-age=604800" />
    <title>Snippets Full Calendar Bootstrap5 2026 05060029 | 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>
        :root {
            --primary-color: #4e73df;
            --secondary-color: #858796;
            --success-color: #1cc88a;
            --info-color: #36b9cc;
            --warning-color: #f6c23e;
            --danger-color: #e74a3b;
            --light-color: #f8f9fc;
            --dark-color: #5a5c69;
            --today-bg: #4e73df;
            --selected-bg: #e8f0fe;
            --event-color-1: #4e73df;
            --event-color-2: #1cc88a;
            --event-color-3: #f6c23e;
            --event-color-4: #e74a3b;
            --event-color-5: #36b9cc;
        }

        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 2rem 0;
        }

        .calendar-container {
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }

        .calendar-header {
            background: linear-gradient(135deg, #4e73df 0%, #224abe 100%);
            color: white;
            padding: 2rem;
        }

        .calendar-nav {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 1.5rem;
        }

        .month-title {
            font-size: 1.8rem;
            font-weight: 700;
            margin: 0;
            text-transform: capitalize;
            letter-spacing: 1px;
        }

        .btn-nav {
            background: rgba(255, 255, 255, 0.2);
            color: white;
            border: none;
            width: 45px;
            height: 45px;
            border-radius: 50%;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.2rem;
        }

        .btn-nav:hover {
            background: rgba(255, 255, 255, 0.4);
            transform: scale(1.1);
            color: white;
        }

        .btn-today {
            background: rgba(255, 255, 255, 0.25);
            color: white;
            border: 2px solid rgba(255, 255, 255, 0.5);
            padding: 0.5rem 1.5rem;
            border-radius: 25px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .btn-today:hover {
            background: white;
            color: #4e73df;
            border-color: white;
        }

        .view-selector {
            display: flex;
            gap: 0.5rem;
            background: rgba(255, 255, 255, 0.15);
            padding: 0.3rem;
            border-radius: 25px;
        }

        .btn-view {
            background: transparent;
            color: white;
            border: none;
            padding: 0.5rem 1.2rem;
            border-radius: 20px;
            font-size: 0.875rem;
            font-weight: 600;
            transition: all 0.3s ease;
            white-space: nowrap;
        }

        .btn-view.active {
            background: white;
            color: #4e73df;
        }

        .btn-view:hover:not(.active) {
            background: rgba(255, 255, 255, 0.2);
            color: white;
        }

        .calendar-body {
            padding: 1.5rem;
            min-height: 500px;
        }

        /* Vue Mois */
        .weekdays {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 5px;
            margin-bottom: 10px;
        }

        .weekday {
            text-align: center;
            font-weight: 700;
            color: #4e73df;
            padding: 0.8rem 0;
            font-size: 0.9rem;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .weekday.weekend {
            color: #e74a3b;
        }

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

        .day-cell {
            aspect-ratio: 1;
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            font-weight: 500;
            color: #5a5c69;
            font-size: 0.9rem;
            min-height: 70px;
        }

        .day-cell:hover {
            background: #f0f2f5;
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
        }

        .day-cell.other-month {
            color: #d1d3e2;
            cursor: default;
        }

        .day-cell.other-month:hover {
            background: transparent;
            transform: none;
            box-shadow: none;
        }

        .day-cell.today {
            background: var(--today-bg);
            color: white;
            font-weight: 700;
            box-shadow: 0 5px 15px rgba(78, 115, 223, 0.4);
        }

        .day-cell.today:hover {
            background: #224abe;
            color: white;
        }

        .day-cell.selected {
            background: var(--selected-bg);
            color: #4e73df;
            font-weight: 700;
            box-shadow: 0 0 0 2px #4e73df;
        }

        .day-cell.weekend {
            color: #e74a3b;
        }

        .day-cell.today.weekend {
            color: white;
        }

        .day-number {
            font-size: 1rem;
            margin-bottom: 5px;
        }

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

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

        .event-dot.color-1 {
            background: var(--event-color-1);
        }

        .event-dot.color-2 {
            background: var(--event-color-2);
        }

        .event-dot.color-3 {
            background: var(--event-color-3);
        }

        .event-dot.color-4 {
            background: var(--event-color-4);
        }

        .event-dot.color-5 {
            background: var(--event-color-5);
        }

        /* Vue Semaine */
        .week-view {
            display: none;
        }

        .week-view.active {
            display: block;
        }

        .week-header {
            display: grid;
            grid-template-columns: 60px repeat(7, 1fr);
            gap: 2px;
            margin-bottom: 2px;
        }

        .week-header-cell {
            text-align: center;
            padding: 0.5rem;
            font-weight: 600;
            font-size: 0.875rem;
            color: #4e73df;
        }

        .week-header-cell.today {
            background: rgba(78, 115, 223, 0.1);
            border-radius: 8px;
            color: #4e73df;
        }

        .week-body {
            display: grid;
            grid-template-columns: 60px repeat(7, 1fr);
            gap: 2px;
        }

        .time-label {
            text-align: right;
            padding: 0.5rem;
            font-size: 0.75rem;
            color: #858796;
            font-weight: 500;
        }

        .week-cell {
            min-height: 40px;
            padding: 0.25rem;
            font-size: 0.75rem;
            border: 1px solid #e3e6f0;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .week-cell:hover {
            background: #f0f2f5;
        }

        .week-cell.has-event {
            background: rgba(78, 115, 223, 0.1);
            border-left: 3px solid #4e73df;
            padding: 0.5rem;
        }

        .week-event-title {
            font-weight: 600;
            color: #4e73df;
            font-size: 0.75rem;
            margin-bottom: 2px;
        }

        .week-event-time {
            color: #858796;
            font-size: 0.7rem;
        }

        /* Vue Jour */
        .day-view {
            display: none;
        }

        .day-view.active {
            display: block;
        }

        .day-view-header {
            background: linear-gradient(135deg, #4e73df 0%, #224abe 100%);
            color: white;
            padding: 1.5rem;
            border-radius: 10px;
            margin-bottom: 1.5rem;
        }

        .day-view-date {
            font-size: 2rem;
            font-weight: 700;
        }

        .day-view-day {
            font-size: 1rem;
            opacity: 0.9;
        }

        .day-timeline {
            position: relative;
        }

        .day-event {
            background: white;
            border-left: 4px solid #4e73df;
            padding: 1rem;
            margin-bottom: 0.5rem;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            transition: all 0.3s ease;
            cursor: pointer;
        }

        .day-event:hover {
            transform: translateX(5px);
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
        }

        .day-event.color-1 {
            border-left-color: var(--event-color-1);
        }

        .day-event.color-2 {
            border-left-color: var(--event-color-2);
        }

        .day-event.color-3 {
            border-left-color: var(--event-color-3);
        }

        .day-event.color-4 {
            border-left-color: var(--event-color-4);
        }

        .day-event.color-5 {
            border-left-color: var(--event-color-5);
        }

        .event-time-badge {
            background: #f0f2f5;
            padding: 0.25rem 0.75rem;
            border-radius: 15px;
            font-size: 0.8rem;
            font-weight: 600;
            color: #4e73df;
            display: inline-block;
            margin-bottom: 0.5rem;
        }

        .event-title {
            font-weight: 700;
            color: #5a5c69;
            margin-bottom: 0.25rem;
        }

        .event-description {
            font-size: 0.875rem;
            color: #858796;
        }

        /* Modal */
        .modal-event-color {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            display: inline-block;
            cursor: pointer;
            transition: all 0.2s ease;
            border: 3px solid transparent;
        }

        .modal-event-color:hover {
            transform: scale(1.2);
        }

        .modal-event-color.selected {
            border-color: #5a5c69;
        }

        /* Responsive */
        @media (max-width: 768px) {
            .calendar-header {
                padding: 1rem;
            }

            .month-title {
                font-size: 1.3rem;
            }

            .btn-view {
                padding: 0.4rem 0.8rem;
                font-size: 0.75rem;
            }

            .day-cell {
                min-height: 50px;
                font-size: 0.8rem;
            }

            .week-header,
            .week-body {
                grid-template-columns: 40px repeat(7, 1fr);
            }

            .view-selector {
                gap: 0.2rem;
            }
        }

        @media (max-width: 480px) {
            .calendar-nav {
                flex-direction: column;
                gap: 1rem;
            }

            .view-selector {
                width: 100%;
                justify-content: center;
            }

            .day-cell {
                min-height: 40px;
                font-size: 0.75rem;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-12">
                <div class="calendar-container">
                    <!-- En-tête du calendrier -->
                    <div class="calendar-header">
                        <div class="calendar-nav">
                            <div class="d-flex align-items-center gap-3">
                                <button class="btn-nav" id="prevBtn" title="Mois précédent">
                                    <i class="bi bi-chevron-left"></i>
                                </button>
                                <h2 class="month-title" id="monthYear">Mai 2026</h2>
                                <button class="btn-nav" id="nextBtn" title="Mois suivant">
                                    <i class="bi bi-chevron-right"></i>
                                </button>
                            </div>
                            
                            <div class="d-flex align-items-center gap-2">
                                <button class="btn-today" id="todayBtn">
                                    <i class="bi bi-calendar-check me-2"></i>Aujourd'hui
                                </button>
                                
                                <div class="view-selector">
                                    <button class="btn-view active" data-view="month">
                                        <i class="bi bi-calendar-month me-1"></i>Mois
                                    </button>
                                    <button class="btn-view" data-view="week">
                                        <i class="bi bi-calendar-week me-1"></i>Semaine
                                    </button>
                                    <button class="btn-view" data-view="day">
                                        <i class="bi bi-calendar-day me-1"></i>Jour
                                    </button>
                                </div>
                            </div>
                        </div>

                        <div class="row align-items-center">
                            <div class="col-md-8">
                                <div id="selectedDateInfo" class="text-white-50">
                                    <i class="bi bi-hand-index-thumb me-2"></i>
                                    Cliquez sur une date pour voir les détails ou ajouter un événement
                                </div>
                            </div>
                            <div class="col-md-4 text-md-end mt-2 mt-md-0">
                                <button class="btn btn-light" data-bs-toggle="modal" data-bs-target="#addEventModal">
                                    <i class="bi bi-plus-circle me-2"></i>Nouvel événement
                                </button>
                            </div>
                        </div>
                    </div>

                    <!-- Corps du calendrier -->
                    <div class="calendar-body">
                        <!-- Vue Mois -->
                        <div id="monthView" class="view-container">
                            <div class="weekdays" id="weekdaysContainer"></div>
                            <div class="days-grid" id="daysGrid"></div>
                        </div>

                        <!-- Vue Semaine -->
                        <div id="weekView" class="week-view">
                            <div class="week-header" id="weekHeader"></div>
                            <div class="week-body" id="weekBody"></div>
                        </div>

                        <!-- Vue Jour -->
                        <div id="dayView" class="day-view">
                            <div class="day-view-header">
                                <div class="day-view-day" id="dayViewDay"></div>
                                <div class="day-view-date" id="dayViewDate"></div>
                            </div>
                            <div class="day-timeline" id="dayTimeline"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal Ajout d'événement -->
    <div class="modal fade" id="addEventModal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header bg-primary text-white">
                    <h5 class="modal-title">
                        <i class="bi bi-calendar-plus me-2"></i>Ajouter un événement
                    </h5>
                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <form id="eventForm">
                        <div class="mb-3">
                            <label class="form-label">Date</label>
                            <input type="date" class="form-control" id="eventDate" required>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Titre</label>
                            <input type="text" class="form-control" id="eventTitle" placeholder="Titre de l'événement" required>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Heure</label>
                            <input type="time" class="form-control" id="eventTime">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Description</label>
                            <textarea class="form-control" id="eventDescription" rows="3" placeholder="Description (optionnelle)"></textarea>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Couleur</label>
                            <div class="d-flex gap-2">
                                <span class="modal-event-color" style="background: var(--event-color-1);" data-color="1"></span>
                                <span class="modal-event-color" style="background: var(--event-color-2);" data-color="2"></span>
                                <span class="modal-event-color" style="background: var(--event-color-3);" data-color="3"></span>
                                <span class="modal-event-color" style="background: var(--event-color-4);" data-color="4"></span>
                                <span class="modal-event-color selected" style="background: var(--event-color-5);" data-color="5"></span>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
                    <button type="button" class="btn btn-primary" id="saveEventBtn">
                        <i class="bi bi-check-lg me-2"></i>Enregistrer
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal Détails événement -->
    <div class="modal fade" id="eventDetailsModal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">
                        <i class="bi bi-calendar-event me-2"></i>Détails de l'événement
                    </h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body" id="eventDetailsBody">
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-danger" id="deleteEventBtn">
                        <i class="bi bi-trash me-2"></i>Supprimer
                    </button>
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        class DynamicCalendar {
            constructor() {
                this.currentDate = new Date();
                this.selectedDate = new Date();
                this.currentView = 'month';
                this.events = this.loadEvents();
                this.selectedColor = 5;
                
                this.init();
            }

            init() {
                this.render();
                this.attachEventListeners();
            }

            loadEvents() {
                const saved = localStorage.getItem('calendarEvents');
                return saved ? JSON.parse(saved) : this.getDefaultEvents();
            }

            saveEvents() {
                localStorage.setItem('calendarEvents', JSON.stringify(this.events));
            }

            getDefaultEvents() {
                const today = new Date();
                const formatDate = (d) => d.toISOString().split('T')[0];
                
                return [
                    {
                        id: 1,
                        date: formatDate(new Date(today.getFullYear(), today.getMonth(), today.getDate())),
                        title: 'Réunion d\'équipe',
                        time: '10:00',
                        description: 'Réunion hebdomadaire de suivi des projets',
                        color: 1
                    },
                    {
                        id: 2,
                        date: formatDate(new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)),
                        title: 'Déjeuner client',
                        time: '12:30',
                        description: 'Déjeuner avec le client ABC Corp',
                        color: 2
                    },
                    {
                        id: 3,
                        date: formatDate(new Date(today.getFullYear(), today.getMonth(), today.getDate() + 5)),
                        title: 'Formation',
                        time: '14:00',
                        description: 'Formation sur les nouvelles technologies',
                        color: 3
                    },
                    {
                        id: 4,
                        date: formatDate(new Date(today.getFullYear(), today.getMonth() + 1, 1)),
                        title: 'Deadline projet',
                        time: '18:00',
                        description: 'Date limite de soumission du projet X',
                        color: 4
                    },
                    {
                        id: 5,
                        date: formatDate(new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)),
                        title: 'Rendez-vous médecin',
                        time: '09:00',
                        description: 'Consultation annuelle',
                        color: 5
                    }
                ];
            }

            getEventsForDate(dateStr) {
                return this.events.filter(event => event.date === dateStr);
            }

            render() {
                this.updateHeader();
                switch(this.currentView) {
                    case 'month':
                        this.renderMonthView();
                        break;
                    case 'week':
                        this.renderWeekView();
                        break;
                    case 'day':
                        this.renderDayView();
                        break;
                }
            }

            updateHeader() {
                const options = { month: 'long', year: 'numeric' };
                document.getElementById('monthYear').textContent = 
                    this.currentDate.toLocaleDateString('fr-FR', options);
                
                // Mise à jour info date sélectionnée
                const dateStr = this.formatDate(this.selectedDate);
                const events = this.getEventsForDate(dateStr);
                const infoDiv = document.getElementById('selectedDateInfo');
                
                if (events.length > 0) {
                    infoDiv.innerHTML = `
                        <i class="bi bi-calendar-check me-2"></i>
                        <strong>${this.selectedDate.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' })}</strong> 
                        - ${events.length} événement(s)
                    `;
                } else {
                    infoDiv.innerHTML = `
                        <i class="bi bi-calendar me-2"></i>
                        ${this.selectedDate.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' })}
                    `;
                }
            }

            renderMonthView() {
                const year = this.currentDate.getFullYear();
                const month = this.currentDate.getMonth();
                
                // Jours de la semaine
                const weekdays = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
                const weekdaysContainer = document.getElementById('weekdaysContainer');
                weekdaysContainer.innerHTML = weekdays.map((day, index) => {
                    const isWeekend = index >= 5;
                    return `<div class="weekday ${isWeekend ? 'weekend' : ''}">${day}</div>`;
                }).join('');
                
                // Grille des jours
                const firstDay = new Date(year, month, 1);
                const lastDay = new Date(year, month + 1, 0);
                const startPadding = (firstDay.getDay() + 6) % 7;
                
                const daysGrid = document.getElementById('daysGrid');
                let html = '';
                
                // Jours du mois précédent
                const prevLastDay = new Date(year, month, 0);
                for (let i = startPadding - 1; i >= 0; i--) {
                    const day = prevLastDay.getDate() - i;
                    const date = new Date(year, month - 1, day);
                    html += this.createDayCell(date, true);
                }
                
                // Jours du mois courant
                for (let day = 1; day <= lastDay.getDate(); day++) {
                    const date = new Date(year, month, day);
                    html += this.createDayCell(date, false);
                }
                
                // Jours du mois suivant
                const remainingCells = 42 - (startPadding + lastDay.getDate());
                for (let day = 1; day <= remainingCells; day++) {
                    const date = new Date(year, month + 1, day);
                    html += this.createDayCell(date, true);
                }
                
                daysGrid.innerHTML = html;
            }

            createDayCell(date, isOtherMonth) {
                const today = new Date();
                const dateStr = this.formatDate(date);
                const events = this.getEventsForDate(dateStr);
                
                const isToday = this.formatDate(date) === this.formatDate(today);
                const isSelected = this.formatDate(date) === this.formatDate(this.selectedDate);
                const isWeekend = date.getDay() === 0 || date.getDay() === 6;
                
                let classes = ['day-cell'];
                if (isOtherMonth) classes.push('other-month');
                if (isToday) classes.push('today');
                if (isSelected) classes.push('selected');
                if (isWeekend && !isToday) classes.push('weekend');
                
                let eventDots = '';
                if (events.length > 0) {
                    const uniqueColors = [...new Set(events.map(e => e.color))];
                    eventDots = `
                        <div class="event-dots">
                            ${uniqueColors.slice(0, 3).map(color => 
                                `<span class="event-dot color-${color}"></span>`
                            ).join('')}
                            ${uniqueColors.length > 3 ? '<span class="event-dot" style="background: #858796;">+</span>' : ''}
                        </div>
                    `;
                }
                
                return `
                    <div class="${classes.join(' ')}" data-date="${dateStr}" onclick="calendar.selectDate('${dateStr}')">
                        <span class="day-number">${date.getDate()}</span>
                        ${eventDots}
                    </div>
                `;
            }

            renderWeekView() {
                const weekStart = this.getWeekStart(this.selectedDate);
                const weekHeader = document.getElementById('weekHeader');
                const weekBody = document.getElementById('weekBody');
                
                // En-tête de la semaine
                const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
                let headerHtml = '<div></div>'; // Colonne vide pour les heures
                
                for (let i = 0; i < 7; i++) {
                    const date = new Date(weekStart);
                    date.setDate(date.getDate() + i);
                    const isToday = this.formatDate(date) === this.formatDate(new Date());
                    headerHtml += `
                        <div class="week-header-cell ${isToday ? 'today' : ''}">
                            <div>${days[i]}</div>
                            <strong>${date.getDate()}</strong>
                        </div>
                    `;
                }
                weekHeader.innerHTML = headerHtml;
                
                // Corps de la semaine (8h-20h)
                let bodyHtml = '';
                for (let hour = 8; hour <= 20; hour++) {
                    bodyHtml += `<div class="time-label">${hour}:00</div>`;
                    
                    for (let day = 0; day < 7; day++) {
                        const date = new Date(weekStart);
                        date.setDate(date.getDate() + day);
                        const dateStr = this.formatDate(date);
                        const events = this.getEventsForDate(dateStr);
                        
                        // Trouver les événements pour cette heure
                        const hourEvents = events.filter(event => {
                            if (!event.time) return false;
                            const eventHour = parseInt(event.time.split(':')[0]);
                            return eventHour === hour;
                        });
                        
                        if (hourEvents.length > 0) {
                            const event = hourEvents[0];
                            bodyHtml += `
                                <div class="week-cell has-event" onclick="calendar.showEventDetails(${event.id})">
                                    <div class="week-event-title">${event.title}</div>
                                    <div class="week-event-time">${event.time}</div>
                                </div>
                            `;
                        } else {
                            bodyHtml += `<div class="week-cell"></div>`;
                        }
                    }
                }
                weekBody.innerHTML = bodyHtml;
            }

            renderDayView() {
                const dateStr = this.formatDate(this.selectedDate);
                const events = this.getEventsForDate(dateStr);
                
                document.getElementById('dayViewDay').textContent = 
                    this.selectedDate.toLocaleDateString('fr-FR', { weekday: 'long' });
                document.getElementById('dayViewDate').textContent = 
                    this.selectedDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
                
                const timeline = document.getElementById('dayTimeline');
                
                if (events.length === 0) {
                    timeline.innerHTML = `
                        <div class="text-center text-muted py-5">
                            <i class="bi bi-calendar-x display-4"></i>
                            <p class="mt-3">Aucun événement pour cette journée</p>
                            <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addEventModal">
                                <i class="bi bi-plus-circle me-2"></i>Ajouter un événement
                            </button>
                        </div>
                    `;
                } else {
                    const sortedEvents = events.sort((a, b) => {
                        if (!a.time) return 1;
                        if (!b.time) return -1;
                        return a.time.localeCompare(b.time);
                    });
                    
                    timeline.innerHTML = sortedEvents.map(event => `
                        <div class="day-event color-${event.color}" onclick="calendar.showEventDetails(${event.id})">
                            <span class="event-time-badge">
                                <i class="bi bi-clock me-2"></i>${event.time || 'Journée entière'}
                            </span>
                            <div class="event-title">${event.title}</div>
                            ${event.description ? `<div class="event-description">${event.description}</div>` : ''}
                        </div>
                    `).join('');
                }
            }

            getWeekStart(date) {
                const d = new Date(date);
                const day = d.getDay();
                const diff = d.getDate() - day + (day === 0 ? -6 : 1);
                d.setDate(diff);
                return d;
            }

            formatDate(date) {
                const year = date.getFullYear();
                const month = String(date.getMonth() + 1).padStart(2, '0');
                const day = String(date.getDate()).padStart(2, '0');
                return `${year}-${month}-${day}`;
            }

            selectDate(dateStr) {
                this.selectedDate = new Date(dateStr + 'T00:00:00');
                
                if (this.currentView === 'day') {
                    this.renderDayView();
                }
                
                this.render();
                
                // Mettre à jour le champ date du modal
                document.getElementById('eventDate').value = dateStr;
            }

            navigateMonth(direction) {
                this.currentDate.setMonth(this.currentDate.getMonth() + direction);
                this.render();
            }

            goToToday() {
                this.currentDate = new Date();
                this.selectedDate = new Date();
                this.render();
                document.getElementById('eventDate').value = this.formatDate(new Date());
            }

            switchView(view) {
                this.currentView = view;
                
                // Masquer toutes les vues
                document.querySelectorAll('.view-container, .week-view, .day-view').forEach(el => {
                    el.style.display = 'none';
                    el.classList.remove('active');
                });
                
                // Afficher la vue sélectionnée
                switch(view) {
                    case 'month':
                        document.getElementById('monthView').style.display = 'block';
                        break;
                    case 'week':
                        document.getElementById('weekView').style.display = 'block';
                        document.getElementById('weekView').classList.add('active');
                        break;
                    case 'day':
                        document.getElementById('dayView').style.display = 'block';
                        document.getElementById('dayView').classList.add('active');
                        break;
                }
                
                this.render();
            }

            addEvent(eventData) {
                const newEvent = {
                    id: Date.now(),
                    ...eventData
                };
                
                this.events.push(newEvent);
                this.saveEvents();
                this.render();
                
                // Notification
                this.showToast('Événement ajouté avec succès !', 'success');
            }

            deleteEvent(eventId) {
                this.events = this.events.filter(event => event.id !== eventId);
                this.saveEvents();
                this.render();
                
                const modal = bootstrap.Modal.getInstance(document.getElementById('eventDetailsModal'));
                modal.hide();
                
                this.showToast('Événement supprimé', 'warning');
            }

            showEventDetails(eventId) {
                const event = this.events.find(e => e.id === eventId);
                if (!event) return;
                
                const colors = {
                    1: '#4e73df',
                    2: '#1cc88a',
                    3: '#f6c23e',
                    4: '#e74a3b',
                    5: '#36b9cc'
                };
                
                document.getElementById('eventDetailsBody').innerHTML = `
                    <div class="text-center mb-3">
                        <span style="display: inline-block; width: 20px; height: 20px; background: ${colors[event.color]}; border-radius: 50%;"></span>
                    </div>
                    <h5 class="text-center">${event.title}</h5>
                    <div class="text-center text-muted mb-3">
                        <i class="bi bi-calendar me-2"></i>
                        ${new Date(event.date + 'T00:00:00').toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
                        ${event.time ? `<br><i class="bi bi-clock me-2"></i>${event.time}` : ''}
                    </div>
                    ${event.description ? `
                        <div class="alert alert-light">
                            <i class="bi bi-chat-left-text me-2"></i>${event.description}
                        </div>
                    ` : ''}
                `;
                
                document.getElementById('deleteEventBtn').onclick = () => this.deleteEvent(eventId);
                
                const modal = new bootstrap.Modal(document.getElementById('eventDetailsModal'));
                modal.show();
            }

            showToast(message, type = 'info') {
                const toastContainer = document.createElement('div');
                toastContainer.style.cssText = `
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    z-index: 9999;
                `;
                
                const bgClass = type === 'success' ? 'bg-success' : 
                                type === 'warning' ? 'bg-warning' : 'bg-info';
                
                toastContainer.innerHTML = `
                    <div class="toast show ${bgClass} text-white" role="alert">
                        <div class="toast-body d-flex align-items-center">
                            <i class="bi bi-${type === 'success' ? 'check-circle' : type === 'warning' ? 'exclamation-triangle' : 'info-circle'} me-2"></i>
                            ${message}
                            <button type="button" class="btn-close btn-close-white ms-3" data-bs-dismiss="toast"></button>
                        </div>
                    </div>
                `;
                
                document.body.appendChild(toastContainer);
                
                setTimeout(() => {
                    toastContainer.remove();
                }, 3000);
            }

            attachEventListeners() {
                // Navigation mois
                document.getElementById('prevBtn').addEventListener('click', () => {
                    this.navigateMonth(-1);
                });
                
                document.getElementById('nextBtn').addEventListener('click', () => {
                    this.navigateMonth(1);
                });
                
                document.getElementById('todayBtn').addEventListener('click', () => {
                    this.goToToday();
                });
                
                // Sélecteur de vue
                document.querySelectorAll('.btn-view').forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        document.querySelectorAll('.btn-view').forEach(b => b.classList.remove('active'));
                        e.target.classList.add('active');
                        this.switchView(e.target.dataset.view);
                    });
                });
                
                // Sélection de couleur dans le modal
                document.querySelectorAll('.modal-event-color').forEach(colorEl => {
                    colorEl.addEventListener('click', (e) => {
                        document.querySelectorAll('.modal-event-color').forEach(el => el.classList.remove('selected'));
                        e.target.classList.add('selected');
                        this.selectedColor = parseInt(e.target.dataset.color);
                    });
                });
                
                // Sauvegarde d'événement
                document.getElementById('saveEventBtn').addEventListener('click', () => {
                    const date = document.getElementById('eventDate').value;
                    const title = document.getElementById('eventTitle').value.trim();
                    const time = document.getElementById('eventTime').value;
                    const description = document.getElementById('eventDescription').value.trim();
                    
                    if (!date || !title) {
                        alert('Veuillez remplir la date et le titre de l\'événement');
                        return;
                    }
                    
                    this.addEvent({
                        date,
                        title,
                        time,
                        description,
                        color: this.selectedColor
                    });
                    
                    // Réinitialiser le formulaire
                    document.getElementById('eventForm').reset();
                    document.getElementById('eventDate').value = this.formatDate(this.selectedDate);
                    
                    // Fermer le modal
                    const modal = bootstrap.Modal.getInstance(document.getElementById('addEventModal'));
                    modal.hide();
                });
                
                // Mise à jour de la date dans le modal quand on clique sur une date
                document.getElementById('addEventModal').addEventListener('show.bs.modal', () => {
                    document.getElementById('eventDate').value = this.formatDate(this.selectedDate);
                });
            }
        }

        // Initialisation du calendrier
        const calendar = new DynamicCalendar();
        
        // Raccourcis clavier
        document.addEventListener('keydown', (e) => {
            if (e.key === 'ArrowLeft') {
                calendar.navigateMonth(-1);
            } else if (e.key === 'ArrowRight') {
                calendar.navigateMonth(1);
            } else if (e.key === 't') {
                calendar.goToToday();
            }
        });
    </script>
</body>
</html>

Télécharger le fichier source

Partager