Composants HTML & CSS angularforall.com

- Calendrier Widget Bootstrap 5

Bootstrap 5 Calendar Widget Sidebar Evenements Responsive

Widget calendrier interactif Bootstrap 5 avec sidebar navigation, gestion mois et événements colorés pour interface planification et réservation.

<!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 Calendar Widget 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 {
            --sidebar-width: 380px;
            --primary-color: #4e73df;
            --event-work: #4e73df;
            --event-personal: #1cc88a;
            --event-meeting: #f6c23e;
            --event-important: #e74a3b;
            --event-other: #36b9cc;
        }

        body {
            background: #f8f9fc;
            min-height: 100vh;
        }

        .app-container {
            display: flex;
            min-height: 100vh;
        }

        /* Sidebar */
        .sidebar {
            width: var(--sidebar-width);
            background: white;
            box-shadow: 2px 0 15px rgba(0, 0, 0, 0.1);
            display: flex;
            flex-direction: column;
            border-right: 1px solid #e3e6f0;
            position: fixed;
            top: 0;
            left: 0;
            height: 100vh;
            z-index: 1000;
            overflow-y: auto;
        }

        .sidebar-header {
            background: linear-gradient(135deg, #4e73df 0%, #224abe 100%);
            color: white;
            padding: 1.5rem;
            text-align: center;
        }

        .sidebar-title {
            font-size: 1.2rem;
            font-weight: 700;
            margin-bottom: 0.5rem;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 0.5rem;
        }

        .sidebar-subtitle {
            font-size: 0.85rem;
            opacity: 0.9;
        }

        /* Calendrier Widget */
        .calendar-widget {
            padding: 1rem;
            flex: 1;
        }

        .calendar-nav-mini {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 1rem;
            padding: 0.5rem;
            background: #f8f9fc;
            border-radius: 10px;
        }

        .month-title-mini {
            font-weight: 700;
            font-size: 1rem;
            color: #5a5c69;
            text-transform: capitalize;
            margin: 0;
        }

        .btn-nav-mini {
            background: white;
            border: 1px solid #e3e6f0;
            color: #5a5c69;
            width: 32px;
            height: 32px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s ease;
            padding: 0;
        }

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

        .weekdays-mini {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 2px;
            margin-bottom: 5px;
        }

        .weekday-mini {
            text-align: center;
            font-size: 0.7rem;
            font-weight: 700;
            color: #4e73df;
            padding: 0.3rem 0;
            text-transform: uppercase;
        }

        .weekday-mini.weekend {
            color: #e74a3b;
        }

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

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

        .day-cell-mini:hover {
            background: #f0f2f5;
        }

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

        .day-cell-mini.today {
            background: #4e73df;
            color: white;
            font-weight: 700;
            box-shadow: 0 3px 10px rgba(78, 115, 223, 0.3);
        }

        .day-cell-mini.selected {
            background: rgba(78, 115, 223, 0.1);
            color: #4e73df;
            font-weight: 700;
            box-shadow: 0 0 0 2px #4e73df;
        }

        .day-cell-mini.weekend:not(.today) {
            color: #e74a3b;
        }

        .day-number-mini {
            font-size: 0.75rem;
            line-height: 1;
        }

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

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

        .event-dot-mini.work {
            background: var(--event-work);
        }

        .event-dot-mini.personal {
            background: var(--event-personal);
        }

        .event-dot-mini.meeting {
            background: var(--event-meeting);
        }

        .event-dot-mini.important {
            background: var(--event-important);
        }

        .event-dot-mini.other {
            background: var(--event-other);
        }

        /* Section Événements */
        .events-section {
            border-top: 1px solid #e3e6f0;
            padding: 1rem;
            background: #f8f9fc;
        }

        .events-section-title {
            font-size: 0.9rem;
            font-weight: 700;
            color: #5a5c69;
            margin-bottom: 1rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .events-list {
            max-height: 300px;
            overflow-y: auto;
        }

        .event-item {
            background: white;
            padding: 0.75rem;
            border-radius: 10px;
            margin-bottom: 0.5rem;
            border-left: 4px solid;
            transition: all 0.2s ease;
            cursor: pointer;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
        }

        .event-item:hover {
            transform: translateX(3px);
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
        }

        .event-item.work {
            border-left-color: var(--event-work);
        }

        .event-item.personal {
            border-left-color: var(--event-personal);
        }

        .event-item.meeting {
            border-left-color: var(--event-meeting);
        }

        .event-item.important {
            border-left-color: var(--event-important);
        }

        .event-item.other {
            border-left-color: var(--event-other);
        }

        .event-item-header {
            display: flex;
            justify-content: space-between;
            align-items: start;
            margin-bottom: 0.25rem;
        }

        .event-item-title {
            font-weight: 600;
            font-size: 0.85rem;
            color: #5a5c69;
            margin: 0;
        }

        .event-item-badge {
            font-size: 0.7rem;
            padding: 0.15rem 0.5rem;
            border-radius: 12px;
            font-weight: 600;
            white-space: nowrap;
        }

        .badge-work {
            background: rgba(78, 115, 223, 0.1);
            color: #4e73df;
        }

        .badge-personal {
            background: rgba(28, 200, 138, 0.1);
            color: #1cc88a;
        }

        .badge-meeting {
            background: rgba(246, 194, 62, 0.1);
            color: #f6c23e;
        }

        .badge-important {
            background: rgba(231, 74, 59, 0.1);
            color: #e74a3b;
        }

        .badge-other {
            background: rgba(54, 185, 204, 0.1);
            color: #36b9cc;
        }

        .event-item-time {
            font-size: 0.75rem;
            color: #858796;
            margin-bottom: 0.25rem;
        }

        .event-item-description {
            font-size: 0.75rem;
            color: #b7b9c9;
            margin: 0;
        }

        .no-events {
            text-align: center;
            padding: 2rem 1rem;
            color: #b7b9c9;
        }

        .no-events i {
            font-size: 2rem;
            margin-bottom: 0.5rem;
        }

        .no-events p {
            font-size: 0.85rem;
            margin: 0;
        }

        /* Filtres */
        .filter-section {
            padding: 0.5rem 1rem;
            border-top: 1px solid #e3e6f0;
        }

        .filter-buttons {
            display: flex;
            flex-wrap: wrap;
            gap: 0.25rem;
        }

        .btn-filter {
            font-size: 0.7rem;
            padding: 0.2rem 0.6rem;
            border-radius: 15px;
            border: 1px solid #e3e6f0;
            background: white;
            color: #858796;
            cursor: pointer;
            transition: all 0.2s ease;
            white-space: nowrap;
        }

        .btn-filter:hover {
            background: #f0f2f5;
        }

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

        /* Tooltip personnalisé */
        .event-tooltip {
            display: none;
            position: absolute;
            background: #5a5c69;
            color: white;
            padding: 0.5rem;
            border-radius: 8px;
            font-size: 0.75rem;
            z-index: 1001;
            pointer-events: none;
            white-space: nowrap;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }

        .day-cell-mini:hover .event-tooltip {
            display: block;
        }

        /* Main Content */
        .main-content {
            margin-left: var(--sidebar-width);
            flex: 1;
            padding: 2rem;
        }

        .main-content h2 {
            color: #5a5c69;
            margin-bottom: 1.5rem;
        }

        /* Scrollbar */
        .events-list::-webkit-scrollbar {
            width: 5px;
        }

        .events-list::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }

        .events-list::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 10px;
        }

        .events-list::-webkit-scrollbar-thumb:hover {
            background: #a1a1a1;
        }

        /* Responsive */
        @media (max-width: 992px) {
            .sidebar {
                width: 320px;
            }
            
            :root {
                --sidebar-width: 320px;
            }
        }

        @media (max-width: 768px) {
            .sidebar {
                position: relative;
                width: 100%;
                height: auto;
                border-right: none;
                border-bottom: 1px solid #e3e6f0;
            }

            .app-container {
                flex-direction: column;
            }

            .main-content {
                margin-left: 0;
            }

            :root {
                --sidebar-width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="app-container">
        <!-- Sidebar avec Calendrier Widget -->
        <aside class="sidebar">
            <!-- En-tête -->
            <div class="sidebar-header">
                <div class="sidebar-title">
                    <i class="bi bi-calendar3"></i>
                    Mon Calendrier
                </div>
                <div class="sidebar-subtitle">
                    Événements & Planning
                </div>
            </div>

            <!-- Calendrier Mini -->
            <div class="calendar-widget">
                <div class="calendar-nav-mini">
                    <button class="btn-nav-mini" id="prevMonthBtn" title="Mois précédent">
                        <i class="bi bi-chevron-left"></i>
                    </button>
                    <h6 class="month-title-mini" id="miniMonthYear">Mai 2026</h6>
                    <button class="btn-nav-mini" id="nextMonthBtn" title="Mois suivant">
                        <i class="bi bi-chevron-right"></i>
                    </button>
                </div>

                <div class="weekdays-mini" id="miniWeekdays"></div>
                <div class="days-grid-mini" id="miniDaysGrid"></div>
            </div>

            <!-- Filtres -->
            <div class="filter-section">
                <div class="filter-buttons">
                    <button class="btn-filter active" data-filter="all">Tous</button>
                    <button class="btn-filter" data-filter="work">Travail</button>
                    <button class="btn-filter" data-filter="personal">Personnel</button>
                    <button class="btn-filter" data-filter="meeting">Réunions</button>
                    <button class="btn-filter" data-filter="important">Important</button>
                </div>
            </div>

            <!-- Liste des Événements -->
            <div class="events-section">
                <div class="events-section-title">
                    <i class="bi bi-list-ul"></i>
                    Événements du jour
                    <span class="badge bg-primary ms-auto" id="eventCount">0</span>
                </div>
                <div class="events-list" id="eventsList">
                    <div class="no-events">
                        <i class="bi bi-calendar-x"></i>
                        <p>Sélectionnez une date pour voir les événements</p>
                    </div>
                </div>
            </div>
        </aside>

        <!-- Contenu Principal -->
        <main class="main-content">
            <div class="container-fluid">
                <h2>
                    <i class="bi bi-speedometer2 me-2"></i>
                    Tableau de Bord
                </h2>
                
                <div class="row">
                    <div class="col-md-6 mb-4">
                        <div class="card shadow-sm">
                            <div class="card-body">
                                <h5 class="card-title">
                                    <i class="bi bi-graph-up me-2"></i>
                                    Aperçu des Événements
                                </h5>
                                <div class="row mt-3" id="statsCards">
                                    <!-- Généré dynamiquement -->
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <div class="col-md-6 mb-4">
                        <div class="card shadow-sm">
                            <div class="card-body">
                                <h5 class="card-title">
                                    <i class="bi bi-calendar-check me-2"></i>
                                    Prochains Événements
                                </h5>
                                <div class="list-group list-group-flush mt-3" id="upcomingEvents">
                                    <!-- Généré dynamiquement -->
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        // ============================================
        // BASE DE DONNÉES DES ÉVÉNEMENTS (Array d'objets)
        // ============================================
        const eventsDatabase = [
            {
                id: 1,
                date: '2026-05-06',
                title: 'Réunion projet Alpha',
                time: '09:00',
                description: 'Revue hebdomadaire des avancées du projet',
                category: 'meeting',
                priority: 'high'
            },
            {
                id: 2,
                date: '2026-05-06',
                title: 'Déjeuner équipe',
                time: '12:30',
                description: 'Restaurant Le Petit Bistrot',
                category: 'personal',
                priority: 'medium'
            },
            {
                id: 3,
                date: '2026-05-07',
                title: 'Formation React',
                time: '14:00',
                description: 'Session de formation avancée',
                category: 'work',
                priority: 'high'
            },
            {
                id: 4,
                date: '2026-05-08',
                title: 'Deadline rapport',
                time: '18:00',
                description: 'Soumission finale du rapport Q2',
                category: 'important',
                priority: 'critical'
            },
            {
                id: 5,
                date: '2026-05-10',
                title: 'Anniversaire Marie',
                time: '20:00',
                description: 'Soirée d\'anniversaire',
                category: 'personal',
                priority: 'medium'
            },
            {
                id: 6,
                date: '2026-05-12',
                title: 'Conférence Tech',
                time: '10:00',
                description: 'Présentation des nouvelles technologies',
                category: 'work',
                priority: 'high'
            },
            {
                id: 7,
                date: '2026-05-15',
                title: 'Rendez-vous médical',
                time: '11:30',
                description: 'Consultation annuelle',
                category: 'personal',
                priority: 'low'
            },
            {
                id: 8,
                date: '2026-05-15',
                title: 'Sprint planning',
                time: '14:00',
                description: 'Planification du sprint #23',
                category: 'meeting',
                priority: 'high'
            },
            {
                id: 9,
                date: '2026-05-18',
                title: 'Lancement produit',
                time: '09:00',
                description: 'Mise en production v2.0',
                category: 'important',
                priority: 'critical'
            },
            {
                id: 10,
                date: '2026-05-20',
                title: 'Team Building',
                time: '10:00',
                description: 'Activité de cohésion d\'équipe',
                category: 'personal',
                priority: 'medium'
            },
            {
                id: 11,
                date: '2026-05-22',
                title: 'Audit sécurité',
                time: '15:00',
                description: 'Audit annuel des systèmes',
                category: 'work',
                priority: 'high'
            },
            {
                id: 12,
                date: '2026-05-25',
                title: 'Présentation clients',
                time: '11:00',
                description: 'Démonstration nouveaux modules',
                category: 'meeting',
                priority: 'high'
            },
            {
                id: 13,
                date: '2026-05-26',
                title: 'Maintenance serveur',
                time: '22:00',
                description: 'Mise à jour infrastructure',
                category: 'important',
                priority: 'critical'
            },
            {
                id: 14,
                date: '2026-05-28',
                title: 'Afterwork',
                time: '18:30',
                description: 'Bar Le Central',
                category: 'personal',
                priority: 'low'
            },
            {
                id: 15,
                date: '2026-05-30',
                title: 'Bilan mensuel',
                time: '16:00',
                description: 'Revue des KPIs du mois',
                category: 'work',
                priority: 'medium'
            }
        ];

        // ============================================
        // CLASS CALENDRIER WIDGET
        // ============================================
        class CalendarWidget {
            constructor() {
                this.currentDate = new Date(2026, 4, 6); // Mai 2026
                this.selectedDate = new Date(2026, 4, 6);
                this.currentFilter = 'all';
                this.events = eventsDatabase;
                this.init();
            }

            init() {
                this.renderCalendar();
                this.displayEventsForDate(this.selectedDate);
                this.updateMainContent();
                this.attachListeners();
            }

            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}`;
            }

            getEventsForDate(date) {
                const dateStr = this.formatDate(date);
                let dateEvents = this.events.filter(event => event.date === dateStr);
                
                if (this.currentFilter !== 'all') {
                    dateEvents = dateEvents.filter(event => event.category === this.currentFilter);
                }
                
                return dateEvents;
            }

            getEventCountForDate(date) {
                return this.getEventsForDate(date).length;
            }

            getAllEventsForDate(date) {
                const dateStr = this.formatDate(date);
                return this.events.filter(event => event.date === dateStr);
            }

            renderCalendar() {
                const year = this.currentDate.getFullYear();
                const month = this.currentDate.getMonth();

                // Titre
                const options = { month: 'long', year: 'numeric' };
                document.getElementById('miniMonthYear').textContent = 
                    this.currentDate.toLocaleDateString('fr-FR', options);

                // Jours de la semaine
                const weekdays = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
                document.getElementById('miniWeekdays').innerHTML = weekdays.map((day, index) => {
                    const isWeekend = index >= 5;
                    return `<div class="weekday-mini ${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;

                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);
                }

                document.getElementById('miniDaysGrid').innerHTML = html;
            }

            createDayCell(date, isOtherMonth) {
                const today = new Date(2026, 4, 6); // Date de référence
                const dateStr = this.formatDate(date);
                const allEvents = this.getAllEventsForDate(date);
                const eventCount = allEvents.length;
                
                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-mini'];
                if (isOtherMonth) classes.push('other-month');
                if (isToday) classes.push('today');
                if (isSelected) classes.push('selected');
                if (isWeekend && !isToday) classes.push('weekend');

                // Indicateurs d'événements
                let eventIndicators = '';
                if (eventCount > 0) {
                    const categories = [...new Set(allEvents.map(e => e.category))];
                    eventIndicators = `
                        <div class="event-indicator">
                            ${categories.slice(0, 3).map(cat => 
                                `<span class="event-dot-mini ${cat}"></span>`
                            ).join('')}
                            ${categories.length > 3 ? '<span class="event-dot-mini other">+</span>' : ''}
                        </div>
                    `;
                }

                // Tooltip
                let tooltip = '';
                if (eventCount > 0) {
                    const tooltipEvents = allEvents.slice(0, 3).map(e => e.title).join('<br>');
                    const moreText = allEvents.length > 3 ? `<br>+${allEvents.length - 3} autres...` : '';
                    tooltip = `
                        <div class="event-tooltip">
                            ${tooltipEvents}${moreText}
                        </div>
                    `;
                }

                return `
                    <div class="${classes.join(' ')}" 
                         data-date="${dateStr}" 
                         onclick="calendarWidget.selectDate('${dateStr}')"
                         title="${eventCount} événement(s)">
                        <span class="day-number-mini">${date.getDate()}</span>
                        ${eventIndicators}
                        ${tooltip}
                    </div>
                `;
            }

            selectDate(dateStr) {
                this.selectedDate = new Date(dateStr + 'T00:00:00');
                this.renderCalendar();
                this.displayEventsForDate(this.selectedDate);
            }

            displayEventsForDate(date) {
                const events = this.getEventsForDate(date);
                const eventsList = document.getElementById('eventsList');
                const eventCount = document.getElementById('eventCount');
                const dateFormatted = date.toLocaleDateString('fr-FR', { 
                    weekday: 'long', 
                    day: 'numeric', 
                    month: 'long' 
                });

                document.querySelector('.events-section-title').innerHTML = `
                    <i class="bi bi-list-ul"></i>
                    ${dateFormatted}
                    <span class="badge bg-primary ms-auto" id="eventCount">${events.length}</span>
                `;

                if (events.length === 0) {
                    eventsList.innerHTML = `
                        <div class="no-events">
                            <i class="bi bi-calendar-check"></i>
                            <p>Aucun événement pour cette date</p>
                        </div>
                    `;
                    return;
                }

                // Trier par heure
                const sortedEvents = events.sort((a, b) => {
                    if (!a.time) return 1;
                    if (!b.time) return -1;
                    return a.time.localeCompare(b.time);
                });

                const categoryLabels = {
                    work: 'Travail',
                    personal: 'Personnel',
                    meeting: 'Réunion',
                    important: 'Important',
                    other: 'Autre'
                };

                const priorityIcons = {
                    low: '',
                    medium: '<i class="bi bi-flag text-warning ms-1"></i>',
                    high: '<i class="bi bi-flag text-danger ms-1"></i>',
                    critical: '<i class="bi bi-exclamation-triangle text-danger ms-1"></i>'
                };

                eventsList.innerHTML = sortedEvents.map(event => `
                    <div class="event-item ${event.category}" onclick="calendarWidget.highlightEvent(${event.id})">
                        <div class="event-item-header">
                            <h6 class="event-item-title">
                                ${event.title}
                                ${priorityIcons[event.priority] || ''}
                            </h6>
                            <span class="event-item-badge badge-${event.category}">
                                ${categoryLabels[event.category] || event.category}
                            </span>
                        </div>
                        <div class="event-item-time">
                            <i class="bi bi-clock me-2"></i>
                            ${event.time || 'Journée entière'}
                        </div>
                        ${event.description ? `
                            <p class="event-item-description">
                                <i class="bi bi-chat-left-text me-2"></i>
                                ${event.description}
                            </p>
                        ` : ''}
                    </div>
                `).join('');
            }

            highlightEvent(eventId) {
                const event = this.events.find(e => e.id === eventId);
                if (!event) return;

                // Animation de surbrillance
                const eventItems = document.querySelectorAll('.event-item');
                eventItems.forEach(item => {
                    item.style.transform = 'scale(1)';
                    item.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
                });

                const selectedItem = eventItems[this.getEventsForDate(this.selectedDate).findIndex(e => e.id === eventId)];
                if (selectedItem) {
                    selectedItem.style.transform = 'scale(1.02)';
                    selectedItem.style.boxShadow = '0 5px 20px rgba(78,115,223,0.3)';
                }
            }

            filterEvents(category) {
                this.currentFilter = category;
                
                // Mise à jour des boutons
                document.querySelectorAll('.btn-filter').forEach(btn => {
                    btn.classList.remove('active');
                    if (btn.dataset.filter === category) {
                        btn.classList.add('active');
                    }
                });

                this.renderCalendar();
                this.displayEventsForDate(this.selectedDate);
            }

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

            updateMainContent() {
                // Statistiques
                const totalEvents = this.events.length;
                const workEvents = this.events.filter(e => e.category === 'work').length;
                const personalEvents = this.events.filter(e => e.category === 'personal').length;
                const meetingEvents = this.events.filter(e => e.category === 'meeting').length;
                const importantEvents = this.events.filter(e => e.category === 'important').length;

                document.getElementById('statsCards').innerHTML = `
                    <div class="col-6 mb-3">
                        <div class="card bg-primary text-white">
                            <div class="card-body text-center py-3">
                                <h3 class="mb-0">${totalEvents}</h3>
                                <small>TOTAL</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6 mb-3">
                        <div class="card bg-success text-white">
                            <div class="card-body text-center py-3">
                                <h3 class="mb-0">${workEvents}</h3>
                                <small>TRAVAIL</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6">
                        <div class="card bg-warning">
                            <div class="card-body text-center py-3">
                                <h3 class="mb-0">${meetingEvents}</h3>
                                <small>RÉUNIONS</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6">
                        <div class="card bg-danger text-white">
                            <div class="card-body text-center py-3">
                                <h3 class="mb-0">${importantEvents}</h3>
                                <small>IMPORTANT</small>
                            </div>
                        </div>
                    </div>
                `;

                // Prochains événements
                const today = new Date(2026, 4, 6);
                const upcomingEvents = this.events
                    .filter(e => new Date(e.date + 'T00:00:00') >= today)
                    .sort((a, b) => new Date(a.date + 'T00:00:00') - new Date(b.date + 'T00:00:00'))
                    .slice(0, 5);

                document.getElementById('upcomingEvents').innerHTML = upcomingEvents.map(event => {
                    const eventDate = new Date(event.date + 'T00:00:00');
                    const daysUntil = Math.ceil((eventDate - today) / (1000 * 60 * 60 * 24));
                    const dayLabel = daysUntil === 0 ? "Aujourd'hui" : 
                                    daysUntil === 1 ? "Demain" : 
                                    `Dans ${daysUntil} jours`;

                    return `
                        <div class="list-group-item d-flex justify-content-between align-items-center">
                            <div>
                                <h6 class="mb-1">${event.title}</h6>
                                <small class="text-muted">
                                    ${eventDate.toLocaleDateString('fr-FR', { weekday: 'short', day: 'numeric', month: 'short' })}
                                    ${event.time ? ` à ${event.time}` : ''}
                                </small>
                            </div>
                            <span class="badge bg-secondary rounded-pill">${dayLabel}</span>
                        </div>
                    `;
                }).join('');
            }

            attachListeners() {
                document.getElementById('prevMonthBtn').addEventListener('click', () => {
                    this.navigateMonth(-1);
                });

                document.getElementById('nextMonthBtn').addEventListener('click', () => {
                    this.navigateMonth(1);
                });

                // Filtres
                document.querySelectorAll('.btn-filter').forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        this.filterEvents(e.target.dataset.filter);
                    });
                });

                // Mise à jour périodique (optionnel)
                setInterval(() => {
                    this.updateMainContent();
                }, 60000); // Toutes les minutes
            }
        }

        // Initialisation du widget
        const calendarWidget = new CalendarWidget();
    </script>
</body>
</html>

Télécharger le fichier source

Partager