Download calendrier.html from leroyfal/taskflow: direct link, hf CLI and curl.
- Browser
- Download file 53.8 kB
-
https://huggingface.co/spaces/leroyfal/taskflow/resolve/main/calendrier.html
- Command line
-
hf download hf://spaces/leroyfal/taskflow/calendrier.html
-
curl -L -o calendrier.html https://huggingface.co/spaces/leroyfal/taskflow/resolve/main/calendrier.html
53.8 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TaskFlow - Calendrier</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .sidebar { | |
| transition: all 0.3s ease; | |
| } | |
| .event-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .calendar-day:hover { | |
| background-color: #f5f3ff; | |
| } | |
| .calendar-day.today { | |
| background-color: #eef2ff; | |
| border: 2px solid #6366f1; | |
| } | |
| .calendar-day.selected { | |
| background-color: #6366f1; | |
| color: white; | |
| } | |
| .calendar-day.has-events::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 8px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background-color: #6366f1; | |
| } | |
| .calendar-day.selected.has-events::after { | |
| background-color: white; | |
| } | |
| .dropdown-menu { | |
| animation: fadeIn 0.2s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 font-sans"> | |
| <!-- App Container --> | |
| <div class="flex h-screen overflow-hidden"> | |
| <!-- Sidebar Navigation --> | |
| <div class="sidebar bg-indigo-700 text-white w-64 flex-shrink-0 hidden md:block"> | |
| <div class="p-4 flex items-center space-x-2 border-b border-indigo-600"> | |
| <i class="fas fa-tasks text-2xl"></i> | |
| <h1 class="text-xl font-bold">TaskFlow</h1> | |
| </div> | |
| <nav class="p-4"> | |
| <div class="mb-8"> | |
| <h2 class="text-xs uppercase tracking-wider text-indigo-300 mb-4">Menu Principal</h2> | |
| <ul class="space-y-2"> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <i class="fas fa-home w-5"></i> | |
| <span>Tableau de bord</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <i class="fas fa-project-diagram w-5"></i> | |
| <span>Mes projets</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <i class="fas fa-tasks w-5"></i> | |
| <span>Mes tâches</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded bg-indigo-800"> | |
| <i class="fas fa-calendar-alt w-5"></i> | |
| <span>Calendrier</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h2 class="text-xs uppercase tracking-wider text-indigo-300 mb-4">Équipes</h2> | |
| <ul class="space-y-2"> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <div class="w-5 h-5 rounded-full bg-green-500 flex items-center justify-center text-xs">D</div> | |
| <span>Développement</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <div class="w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center text-xs">M</div> | |
| <span>Marketing</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-2 p-2 rounded hover:bg-indigo-800"> | |
| <div class="w-5 h-5 rounded-full bg-purple-500 flex items-center justify-center text-xs">V</div> | |
| <span>Ventes</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| </nav> | |
| </div> | |
| <!-- Main Content Area --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Top Navigation Bar --> | |
| <header class="bg-white border-b border-gray-200 flex items-center justify-between p-4"> | |
| <div class="flex items-center space-x-4"> | |
| <button class="md:hidden text-gray-600"> | |
| <i class="fas fa-bars text-xl"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold text-gray-800">Calendrier</h2> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="relative"> | |
| <button class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-bell text-gray-600"></i> | |
| <span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span> | |
| </button> | |
| </div> | |
| <div class="relative"> | |
| <button class="flex items-center space-x-2"> | |
| <div class="w-8 h-8 rounded-full bg-indigo-500 flex items-center justify-center text-white">JD</div> | |
| <span class="hidden md:inline">John Doe</span> | |
| <i class="fas fa-chevron-down text-xs hidden md:inline"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-1 overflow-auto p-6 bg-gray-50"> | |
| <!-- Calendar Header --> | |
| <div class="flex justify-between items-center mb-6"> | |
| <div> | |
| <h1 class="text-2xl font-bold text-gray-800">Calendrier</h1> | |
| <p class="text-gray-600">Visualisez vos événements et échéances</p> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button | |
| class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 flex items-center space-x-2" | |
| onclick="openModal('newEventModal')" | |
| > | |
| <i class="fas fa-plus"></i> | |
| <span>Nouvel événement</span> | |
| </button> | |
| <div class="relative"> | |
| <button class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 flex items-center space-x-2"> | |
| <span id="current-view">Mois</span> | |
| <i class="fas fa-chevron-down text-xs"></i> | |
| </button> | |
| <div class="dropdown-menu hidden absolute right-0 mt-1 w-32 bg-white rounded-md shadow-lg z-10 border border-gray-200"> | |
| <div class="py-1"> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onclick="changeView('day')">Jour</a> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onclick="changeView('week')">Semaine</a> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onclick="changeView('month')">Mois</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Calendar Navigation --> | |
| <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-6 flex items-center justify-between"> | |
| <div class="flex items-center space-x-4"> | |
| <button class="p-2 rounded-full hover:bg-gray-100" onclick="navigateCalendar('prev')"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold" id="calendar-title">Juillet 2023</h2> | |
| <button class="p-2 rounded-full hover:bg-gray-100" onclick="navigateCalendar('next')"> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| <button class="px-3 py-1 border border-gray-300 rounded-md text-sm hover:bg-gray-50" onclick="goToToday()"> | |
| Aujourd'hui | |
| </button> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <div class="relative"> | |
| <button class="px-3 py-1 border border-gray-300 rounded-md text-sm hover:bg-gray-50 flex items-center space-x-1"> | |
| <i class="fas fa-filter"></i> | |
| <span>Filtres</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Calendar View --> | |
| <div id="calendar-view"> | |
| <!-- Month View (default) --> | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden" id="month-view"> | |
| <!-- Weekday Headers --> | |
| <div class="grid grid-cols-7 bg-gray-50 border-b border-gray-200"> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Dim</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Lun</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Mar</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Mer</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Jeu</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Ven</div> | |
| <div class="py-2 text-center text-xs font-medium text-gray-500 uppercase">Sam</div> | |
| </div> | |
| <!-- Calendar Grid --> | |
| <div class="grid grid-cols-7" id="month-grid"> | |
| <!-- Days will be dynamically inserted here --> | |
| </div> | |
| </div> | |
| <!-- Week View (hidden by default) --> | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden hidden" id="week-view"> | |
| <div class="grid grid-cols-8"> | |
| <div class="bg-gray-50 border-r border-gray-200"></div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Lundi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Mardi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Mercredi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Jeudi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Vendredi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Samedi</div> | |
| <div class="bg-gray-50 py-2 text-center text-xs font-medium text-gray-500 uppercase border-b border-gray-200">Dimanche</div> | |
| </div> | |
| <div class="grid grid-cols-8 divide-x divide-gray-200" id="week-grid"> | |
| <!-- Time slots will be dynamically inserted here --> | |
| </div> | |
| </div> | |
| <!-- Day View (hidden by default) --> | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden hidden" id="day-view"> | |
| <div class="grid grid-cols-1"> | |
| <div class="bg-gray-50 py-2 text-center text-sm font-medium text-gray-700 border-b border-gray-200" id="day-header"> | |
| Lundi 17 Juillet 2023 | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 divide-y divide-gray-200" id="day-grid"> | |
| <!-- Time slots will be dynamically inserted here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Events List for Selected Day --> | |
| <div class="mt-6 hidden" id="events-section"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold text-gray-800" id="events-day-title">Événements pour le 17 Juillet 2023</h3> | |
| <button | |
| class="bg-indigo-600 text-white px-3 py-1 rounded-md hover:bg-indigo-700 flex items-center space-x-1 text-sm" | |
| onclick="openModal('newEventModal')" | |
| > | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Ajouter</span> | |
| </button> | |
| </div> | |
| <div class="space-y-3" id="events-list"> | |
| <!-- Event cards will be dynamically inserted here --> | |
| </div> | |
| <div class="hidden flex-col items-center justify-center py-12 text-center" id="no-events"> | |
| <div class="w-20 h-20 bg-indigo-50 rounded-full flex items-center justify-center mb-4"> | |
| <i class="fas fa-calendar-day text-indigo-500 text-2xl"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Aucun événement aujourd'hui</h3> | |
| <p class="text-gray-500 max-w-md">Ajoutez un nouvel événement pour commencer à planifier votre journée.</p> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| <!-- New Event Modal --> | |
| <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden" id="newEventModal"> | |
| <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">Nouvel événement</h3> | |
| <button class="text-gray-400 hover:text-gray-500" onclick="closeModal('newEventModal')"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <form id="eventForm"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-title">Titre*</label> | |
| <input | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-title" | |
| type="text" | |
| placeholder="Ex: Réunion avec l'équipe" | |
| required | |
| > | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-start-date">Date de début*</label> | |
| <input | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-start-date" | |
| type="date" | |
| required | |
| > | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-start-time">Heure</label> | |
| <input | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-start-time" | |
| type="time" | |
| > | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-end-date">Date de fin</label> | |
| <input | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-end-date" | |
| type="date" | |
| > | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-end-time">Heure</label> | |
| <input | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-end-time" | |
| type="time" | |
| > | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-color">Couleur</label> | |
| <div class="flex space-x-2"> | |
| <div class="w-6 h-6 rounded-full bg-indigo-500 cursor-pointer border-2 border-indigo-600" onclick="selectColor('indigo')"></div> | |
| <div class="w-6 h-6 rounded-full bg-red-500 cursor-pointer border-2 border-white hover:border-gray-200" onclick="selectColor('red')"></div> | |
| <div class="w-6 h-6 rounded-full bg-yellow-500 cursor-pointer border-2 border-white hover:border-gray-200" onclick="selectColor('yellow')"></div> | |
| <div class="w-6 h-6 rounded-full bg-green-500 cursor-pointer border-2 border-white hover:border-gray-200" onclick="selectColor('green')"></div> | |
| <div class="w-6 h-6 rounded-full bg-blue-500 cursor-pointer border-2 border-white hover:border-gray-200" onclick="selectColor('blue')"></div> | |
| <div class="w-6 h-6 rounded-full bg-purple-500 cursor-pointer border-2 border-white hover:border-gray-200" onclick="selectColor('purple')"></div> | |
| </div> | |
| <input type="hidden" id="event-color" value="indigo"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="event-description">Description</label> | |
| <textarea | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| id="event-description" | |
| rows="3" | |
| placeholder="Détails de l'événement..." | |
| ></textarea> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button | |
| type="button" | |
| class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" | |
| onclick="closeModal('newEventModal')" | |
| > | |
| Annuler | |
| </button> | |
| <button | |
| type="submit" | |
| class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" | |
| > | |
| Créer | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- JavaScript --> | |
| <script> | |
| // Mock data for events | |
| const events = [ | |
| { | |
| id: 1, | |
| title: "Réunion d'équipe", | |
| startDate: "2023-07-17", | |
| startTime: "09:00", | |
| endDate: "2023-07-17", | |
| endTime: "10:30", | |
| color: "indigo", | |
| description: "Réunion hebdomadaire avec toute l'équipe pour faire le point sur les projets en cours.", | |
| attendees: ["JD", "AL", "MP"] | |
| }, | |
| { | |
| id: 2, | |
| title: "Déjeuner avec client", | |
| startDate: "2023-07-18", | |
| startTime: "12:30", | |
| endDate: "2023-07-18", | |
| endTime: "14:00", | |
| color: "green", | |
| description: "Déjeuner avec le client ABC Corp pour discuter du nouveau projet.", | |
| attendees: ["JD", "TM"] | |
| }, | |
| { | |
| id: 3, | |
| title: "Présentation projet", | |
| startDate: "2023-07-20", | |
| startTime: "14:00", | |
| endDate: "2023-07-20", | |
| endTime: "15:30", | |
| color: "blue", | |
| description: "Présentation du nouveau projet à la direction.", | |
| attendees: ["JD", "AL", "SM"] | |
| }, | |
| { | |
| id: 4, | |
| title: "Échéance rapport", | |
| startDate: "2023-07-22", | |
| color: "red", | |
| description: "Dernier jour pour soumettre le rapport trimestriel.", | |
| attendees: [] | |
| }, | |
| { | |
| id: 5, | |
| title: "Formation technique", | |
| startDate: "2023-07-25", | |
| startTime: "10:00", | |
| endDate: "2023-07-25", | |
| endTime: "17:00", | |
| color: "purple", | |
| description: "Journée de formation sur les nouvelles technologies.", | |
| attendees: ["JD", "AL", "DL", "SM"] | |
| } | |
| ]; | |
| // Calendar state | |
| let currentDate = new Date(); | |
| let currentView = "month"; | |
| let selectedDate = new Date(); | |
| // DOM Ready | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize calendar | |
| renderCalendar(); | |
| // Handle form submission | |
| document.getElementById('eventForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| createNewEvent(); | |
| }); | |
| // Initialize dropdown menus | |
| initDropdowns(); | |
| // Set today's date in the event form | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('event-start-date').value = today; | |
| document.getElementById('event-end-date').value = today; | |
| }); | |
| // Render calendar based on current view and date | |
| function renderCalendar() { | |
| updateCalendarTitle(); | |
| if (currentView === "month") { | |
| renderMonthView(); | |
| document.getElementById('month-view').classList.remove('hidden'); | |
| document.getElementById('week-view').classList.add('hidden'); | |
| document.getElementById('day-view').classList.add('hidden'); | |
| } else if (currentView === "week") { | |
| renderWeekView(); | |
| document.getElementById('month-view').classList.add('hidden'); | |
| document.getElementById('week-view').classList.remove('hidden'); | |
| document.getElementById('day-view').classList.add('hidden'); | |
| } else { | |
| renderDayView(); | |
| document.getElementById('month-view').classList.add('hidden'); | |
| document.getElementById('week-view').classList.add('hidden'); | |
| document.getElementById('day-view').classList.remove('hidden'); | |
| } | |
| // Show events for selected date if not in month view | |
| if (currentView !== "month") { | |
| showEventsForDate(selectedDate); | |
| } | |
| } | |
| // Update the calendar title based on current view and date | |
| function updateCalendarTitle() { | |
| const titleElement = document.getElementById('calendar-title'); | |
| const options = { year: 'numeric', month: 'long' }; | |
| if (currentView === "month") { | |
| titleElement.textContent = currentDate.toLocaleDateString('fr-FR', options); | |
| } else if (currentView === "week") { | |
| const weekStart = new Date(currentDate); | |
| weekStart.setDate(currentDate.getDate() - currentDate.getDay() + 1); | |
| const weekEnd = new Date(weekStart); | |
| weekEnd.setDate(weekStart.getDate() + 6); | |
| titleElement.textContent = `${weekStart.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })} - ${weekEnd.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' })}`; | |
| } else { | |
| titleElement.textContent = selectedDate.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); | |
| } | |
| } | |
| // Render the month view | |
| function renderMonthView() { | |
| const monthGrid = document.getElementById('month-grid'); | |
| monthGrid.innerHTML = ''; | |
| const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); | |
| const lastDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); | |
| const startingDay = firstDayOfMonth.getDay(); | |
| const daysInMonth = lastDayOfMonth.getDate(); | |
| // Previous month's days | |
| const prevMonthLastDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0).getDate(); | |
| for (let i = 0; i < startingDay; i++) { | |
| const dayElement = document.createElement('div'); | |
| dayElement.className = 'p-2 h-24 border border-gray-100 text-gray-400'; | |
| dayElement.textContent = prevMonthLastDay - startingDay + i + 1; | |
| monthGrid.appendChild(dayElement); | |
| } | |
| // Current month's days | |
| const today = new Date(); | |
| for (let i = 1; i <= daysInMonth; i++) { | |
| const dayElement = document.createElement('div'); | |
| dayElement.className = 'p-2 h-24 border border-gray-100 relative calendar-day'; | |
| // Check if this day has events | |
| const dayDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), i); | |
| const hasEvents = events.some(event => { | |
| const eventDate = new Date(event.startDate); | |
| return eventDate.getDate() === dayDate.getDate() && | |
| eventDate.getMonth() === dayDate.getMonth() && | |
| eventDate.getFullYear() === dayDate.getFullYear(); | |
| }); | |
| if (hasEvents) { | |
| dayElement.classList.add('has-events'); | |
| } | |
| // Highlight today | |
| if (dayDate.getDate() === today.getDate() && | |
| dayDate.getMonth() === today.getMonth() && | |
| dayDate.getFullYear() === today.getFullYear()) { | |
| dayElement.classList.add('today'); | |
| } | |
| // Highlight selected date | |
| if (dayDate.getDate() === selectedDate.getDate() && | |
| dayDate.getMonth() === selectedDate.getMonth() && | |
| dayDate.getFullYear() === selectedDate.getFullYear()) { | |
| dayElement.classList.add('selected'); | |
| } | |
| dayElement.innerHTML = ` | |
| <div class="text-right font-medium">${i}</div> | |
| <div class="mt-1 space-y-1 overflow-y-auto max-h-16"> | |
| ${renderDayEvents(dayDate)} | |
| </div> | |
| `; | |
| dayElement.addEventListener('click', () => { | |
| selectedDate = dayDate; | |
| renderMonthView(); | |
| showEventsForDate(selectedDate); | |
| }); | |
| monthGrid.appendChild(dayElement); | |
| } | |
| // Next month's days | |
| const daysLeft = 42 - (startingDay + daysInMonth); | |
| for (let i = 1; i <= daysLeft; i++) { | |
| const dayElement = document.createElement('div'); | |
| dayElement.className = 'p-2 h-24 border border-gray-100 text-gray-400'; | |
| dayElement.textContent = i; | |
| monthGrid.appendChild(dayElement); | |
| } | |
| } | |
| // Render events for a specific day in month view | |
| function renderDayEvents(dayDate) { | |
| const dayEvents = events.filter(event => { | |
| const eventDate = new Date(event.startDate); | |
| return eventDate.getDate() === dayDate.getDate() && | |
| eventDate.getMonth() === dayDate.getMonth() && | |
| eventDate.getFullYear() === dayDate.getFullYear(); | |
| }); | |
| if (dayEvents.length === 0) return ''; | |
| return dayEvents.slice(0, 2).map(event => { | |
| let colorClass = 'bg-indigo-100 text-indigo-800'; | |
| if (event.color === 'red') colorClass = 'bg-red-100 text-red-800'; | |
| else if (event.color === 'yellow') colorClass = 'bg-yellow-100 text-yellow-800'; | |
| else if (event.color === 'green') colorClass = 'bg-green-100 text-green-800'; | |
| else if (event.color === 'blue') colorClass = 'bg-blue-100 text-blue-800'; | |
| else if (event.color === 'purple') colorClass = 'bg-purple-100 text-purple-800'; | |
| return ` | |
| <div class="text-xs p-1 rounded truncate ${colorClass}"> | |
| ${event.startTime ? event.startTime + ' ' : ''}${event.title} | |
| </div> | |
| `; | |
| }).join(''); | |
| } | |
| // Render the week view | |
| function renderWeekView() { | |
| const weekGrid = document.getElementById('week-grid'); | |
| weekGrid.innerHTML = ''; | |
| // Time column | |
| const timeColumn = document.createElement('div'); | |
| timeColumn.className = 'bg-gray-50 border-r border-gray-200'; | |
| for (let hour = 8; hour < 20; hour++) { | |
| const timeSlot = document.createElement('div'); | |
| timeSlot.className = 'h-16 border-b border-gray-200 relative'; | |
| timeSlot.innerHTML = ` | |
| <span class="absolute top-0 left-1 text-xs text-gray-500 -mt-2">${hour}:00</span> | |
| `; | |
| timeColumn.appendChild(timeSlot); | |
| } | |
| weekGrid.appendChild(timeColumn); | |
| // Week days | |
| const weekStart = new Date(currentDate); | |
| weekStart.setDate(currentDate.getDate() - currentDate.getDay() + 1); | |
| for (let i = 0; i < 7; i++) { | |
| const dayDate = new Date(weekStart); | |
| dayDate.setDate(weekStart.getDate() + i); | |
| const dayColumn = document.createElement('div'); | |
| dayColumn.className = 'relative'; | |
| // Highlight today | |
| const today = new Date(); | |
| if (dayDate.getDate() === today.getDate() && | |
| dayDate.getMonth() === today.getMonth() && | |
| dayDate.getFullYear() === today.getFullYear()) { | |
| dayColumn.classList.add('bg-blue-50'); | |
| } | |
| // Highlight selected date | |
| if (dayDate.getDate() === selectedDate.getDate() && | |
| dayDate.getMonth() === selectedDate.getMonth() && | |
| dayDate.getFullYear() === selectedDate.getFullYear()) { | |
| dayColumn.classList.add('bg-indigo-50'); | |
| } | |
| for (let hour = 8; hour < 20; hour++) { | |
| const timeSlot = document.createElement('div'); | |
| timeSlot.className = 'h-16 border-b border-gray-200 relative'; | |
| // Add events to time slots | |
| const slotStart = new Date(dayDate); | |
| slotStart.setHours(hour, 0, 0, 0); | |
| const slotEnd = new Date(dayDate); | |
| slotEnd.setHours(hour + 1, 0, 0, 0); | |
| const slotEvents = events.filter(event => { | |
| if (!event.startTime) return false; | |
| const eventStart = new Date(`${event.startDate}T${event.startTime}`); | |
| return eventStart >= slotStart && eventStart < slotEnd; | |
| }); | |
| if (slotEvents.length > 0) { | |
| slotEvents.forEach(event => { | |
| const eventElement = document.createElement('div'); | |
| eventElement.className = 'absolute top-0 left-0 right-0 mx-1 mt-1 p-1 text-xs rounded overflow-hidden'; | |
| let colorClass = 'bg-indigo-100 text-indigo-800 border-l-4 border-indigo-500'; | |
| if (event.color === 'red') colorClass = 'bg-red-100 text-red-800 border-l-4 border-red-500'; | |
| else if (event.color === 'yellow') colorClass = 'bg-yellow-100 text-yellow-800 border-l-4 border-yellow-500'; | |
| else if (event.color === 'green') colorClass = 'bg-green-100 text-green-800 border-l-4 border-green-500'; | |
| else if (event.color === 'blue') colorClass = 'bg-blue-100 text-blue-800 border-l-4 border-blue-500'; | |
| else if (event.color === 'purple') colorClass = 'bg-purple-100 text-purple-800 border-l-4 border-purple-500'; | |
| eventElement.className += ' ' + colorClass; | |
| eventElement.textContent = event.title; | |
| eventElement.title = `${event.title}\n${event.startTime} - ${event.endTime || ''}\n${event.description || ''}`; | |
| timeSlot.appendChild(eventElement); | |
| }); | |
| } | |
| timeSlot.addEventListener('click', () => { | |
| selectedDate = dayDate; | |
| showEventsForDate(selectedDate); | |
| // Open new event modal with pre-filled time | |
| document.getElementById('event-start-date').value = dayDate.toISOString().split('T')[0]; | |
| document.getElementById('event-start-time').value = `${hour.toString().padStart(2, '0')}:00`; | |
| const endHour = hour + 1; | |
| document.getElementById('event-end-date').value = dayDate.toISOString().split('T')[0]; | |
| document.getElementById('event-end-time').value = `${endHour.toString().padStart(2, '0')}:00`; | |
| openModal('newEventModal'); | |
| }); | |
| dayColumn.appendChild(timeSlot); | |
| } | |
| // Day header | |
| const dayHeader = document.createElement('div'); | |
| dayHeader.className = 'text-center py-1 font-medium'; | |
| dayHeader.textContent = dayDate.toLocaleDateString('fr-FR', { weekday: 'short', day: 'numeric' }); | |
| dayHeader.addEventListener('click', () => { | |
| selectedDate = dayDate; | |
| changeView('day'); | |
| }); | |
| dayColumn.prepend(dayHeader); | |
| weekGrid.appendChild(dayColumn); | |
| } | |
| } | |
| // Render the day view | |
| function renderDayView() { | |
| const dayHeader = document.getElementById('day-header'); | |
| dayHeader.textContent = selectedDate.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); | |
| const dayGrid = document.getElementById('day-grid'); | |
| dayGrid.innerHTML = ''; | |
| for (let hour = 8; hour < 20; hour++) { | |
| const timeSlot = document.createElement('div'); | |
| timeSlot.className = 'h-16 relative'; | |
| timeSlot.innerHTML = ` | |
| <div class="absolute top-0 left-0 w-16 text-right pr-2 text-xs text-gray-500 -mt-2">${hour}:00</div> | |
| <div class="ml-16 h-full border-t border-gray-200"></div> | |
| `; | |
| // Add events to time slots | |
| const slotStart = new Date(selectedDate); | |
| slotStart.setHours(hour, 0, 0, 0); | |
| const slotEnd = new Date(selectedDate); | |
| slotEnd.setHours(hour + 1, 0, 0, 0); | |
| const slotEvents = events.filter(event => { | |
| if (!event.startTime) return false; | |
| const eventStart = new Date(`${event.startDate}T${event.startTime}`); | |
| return eventStart >= slotStart && eventStart < slotEnd; | |
| }); | |
| if (slotEvents.length > 0) { | |
| slotEvents.forEach(event => { | |
| const eventElement = document.createElement('div'); | |
| eventElement.className = 'absolute top-0 left-16 right-0 mx-4 mt-1 p-2 text-sm rounded overflow-hidden event-card'; | |
| let colorClass = 'bg-indigo-100 text-indigo-800 border-l-4 border-indigo-500'; | |
| if (event.color === 'red') colorClass = 'bg-red-100 text-red-800 border-l-4 border-red-500'; | |
| else if (event.color === 'yellow') colorClass = 'bg-yellow-100 text-yellow-800 border-l-4 border-yellow-500'; | |
| else if (event.color === 'green') colorClass = 'bg-green-100 text-green-800 border-l-4 border-green-500'; | |
| else if (event.color === 'blue') colorClass = 'bg-blue-100 text-blue-800 border-l-4 border-blue-500'; | |
| else if (event.color === 'purple') colorClass = 'bg-purple-100 text-purple-800 border-l-4 border-purple-500'; | |
| eventElement.className += ' ' + colorClass; | |
| const timeRange = event.endTime | |
| ? `${event.startTime} - ${event.endTime}` | |
| : `${event.startTime}`; | |
| eventElement.innerHTML = ` | |
| <div class="font-medium">${event.title}</div> | |
| <div class="text-xs mt-1">${timeRange}</div> | |
| ${event.description ? `<div class="text-xs mt-1 text-gray-600">${event.description}</div>` : ''} | |
| ${event.attendees && event.attendees.length > 0 ? ` | |
| <div class="mt-2 flex -space-x-2"> | |
| ${event.attendees.map(user => | |
| `<div class="w-6 h-6 rounded-full border-2 border-white bg-indigo-500 flex items-center justify-center text-white text-xs">${user}</div>` | |
| ).join('')} | |
| </div> | |
| ` : ''} | |
| `; | |
| timeSlot.appendChild(eventElement); | |
| }); | |
| } | |
| timeSlot.addEventListener('click', () => { | |
| // Open new event modal with pre-filled time | |
| document.getElementById('event-start-date').value = selectedDate.toISOString().split('T')[0]; | |
| document.getElementById('event-start-time').value = `${hour.toString().padStart(2, '0')}:00`; | |
| const endHour = hour + 1; | |
| document.getElementById('event-end-date').value = selectedDate.toISOString().split('T')[0]; | |
| document.getElementById('event-end-time').value = `${endHour.toString().padStart(2, '0')}:00`; | |
| openModal('newEventModal'); | |
| }); | |
| dayGrid.appendChild(timeSlot); | |
| } | |
| } | |
| // Show events for a specific date | |
| function showEventsForDate(date) { | |
| const eventsSection = document.getElementById('events-section'); | |
| const eventsList = document.getElementById('events-list'); | |
| const noEvents = document.getElementById('no-events'); | |
| const eventsDayTitle = document.getElementById('events-day-title'); | |
| const dateEvents = events.filter(event => { | |
| const eventDate = new Date(event.startDate); | |
| return eventDate.getDate() === date.getDate() && | |
| eventDate.getMonth() === date.getMonth() && | |
| eventDate.getFullYear() === date.getFullYear(); | |
| }); | |
| eventsDayTitle.textContent = `Événements pour le ${date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}`; | |
| if (dateEvents.length === 0) { | |
| eventsList.innerHTML = ''; | |
| noEvents.classList.remove('hidden'); | |
| } else { | |
| noEvents.classList.add('hidden'); | |
| eventsList.innerHTML = ''; | |
| dateEvents.forEach(event => { | |
| const eventCard = document.createElement('div'); | |
| eventCard.className = 'bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden event-card p-4'; | |
| let colorClass = 'bg-indigo-100 text-indigo-800'; | |
| if (event.color === 'red') colorClass = 'bg-red-100 text-red-800'; | |
| else if (event.color === 'yellow') colorClass = 'bg-yellow-100 text-yellow-800'; | |
| else if (event.color === 'green') colorClass = 'bg-green-100 text-green-800'; | |
| else if (event.color === 'blue') colorClass = 'bg-blue-100 text-blue-800'; | |
| else if (event.color === 'purple') colorClass = 'bg-purple-100 text-purple-800'; | |
| const timeRange = event.startTime | |
| ? (event.endTime | |
| ? `${event.startTime} - ${event.endTime}` | |
| : `${event.startTime}`) | |
| : 'Toute la journée'; | |
| eventCard.innerHTML = ` | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <h3 class="font-medium text-gray-900">${event.title}</h3> | |
| <div class="mt-1 flex items-center space-x-2"> | |
| <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${colorClass}"> | |
| ${timeRange} | |
| </span> | |
| </div> | |
| </div> | |
| <div class="dropdown relative"> | |
| <button class="text-gray-400 hover:text-gray-600"> | |
| <i class="fas fa-ellipsis-h"></i> | |
| </button> | |
| <div class="dropdown-menu hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10 border border-gray-200"> | |
| <div class="py-1"> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Modifier</a> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Supprimer</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ${event.description ? `<p class="mt-2 text-sm text-gray-600">${event.description}</p>` : ''} | |
| ${event.attendees && event.attendees.length > 0 ? ` | |
| <div class="mt-4 flex items-center"> | |
| <span class="text-xs text-gray-500 mr-2">Participants :</span> | |
| <div class="flex -space-x-2"> | |
| ${event.attendees.map(user => | |
| `<div class="w-6 h-6 rounded-full border-2 border-white bg-indigo-500 flex items-center justify-center text-white text-xs">${user}</div>` | |
| ).join('')} | |
| </div> | |
| </div> | |
| ` : ''} | |
| `; | |
| eventsList.appendChild(eventCard); | |
| }); | |
| } | |
| eventsSection.classList.remove('hidden'); | |
| } | |
| // Change calendar view | |
| function changeView(view) { | |
| currentView = view; | |
| document.getElementById('current-view').textContent = | |
| view === 'day' ? 'Jour' : view === 'week' ? 'Semaine' : 'Mois'; | |
| renderCalendar(); | |
| } | |
| // Navigate calendar (previous/next month/week/day) | |
| function navigateCalendar(direction) { | |
| if (currentView === "month") { | |
| if (direction === "prev") { | |
| currentDate.setMonth(currentDate.getMonth() - 1); | |
| } else { | |
| currentDate.setMonth(currentDate.getMonth() + 1); | |
| } | |
| } else if (currentView === "week") { | |
| if (direction === "prev") { | |
| currentDate.setDate(currentDate.getDate() - 7); | |
| } else { | |
| currentDate.setDate(currentDate.getDate() + 7); | |
| } | |
| } else { | |
| if (direction === "prev") { | |
| currentDate.setDate(currentDate.getDate() - 1); | |
| } else { | |
| currentDate.setDate(currentDate.getDate() + 1); | |
| } | |
| selectedDate = new Date(currentDate); | |
| } | |
| renderCalendar(); | |
| } | |
| // Go to today's date | |
| function goToToday() { | |
| currentDate = new Date(); | |
| selectedDate = new Date(); | |
| renderCalendar(); | |
| } | |
| // Create a new event | |
| function createNewEvent() { | |
| const title = document.getElementById('event-title').value; | |
| const startDate = document.getElementById('event-start-date').value; | |
| const startTime = document.getElementById('event-start-time').value; | |
| const endDate = document.getElementById('event-end-date').value; | |
| const endTime = document.getElementById('event-end-time').value; | |
| const color = document.getElementById('event-color').value; | |
| const description = document.getElementById('event-description').value; | |
| // Create a new event object | |
| const newEvent = { | |
| id: events.length + 1, | |
| title: title, | |
| startDate: startDate, | |
| startTime: startTime || null, | |
| endDate: endDate || startDate, | |
| endTime: endTime || null, | |
| color: color, | |
| description: description || null, | |
| attendees: ["JD"] | |
| }; | |
| // Add to our events array (in a real app, this would be an API call) | |
| events.push(newEvent); | |
| // Reload calendar | |
| renderCalendar(); | |
| // Close modal | |
| closeModal('newEventModal'); | |
| // Reset form | |
| document.getElementById('eventForm').reset(); | |
| document.getElementById('event-color').value = "indigo"; | |
| // Show events for the selected date | |
| selectedDate = new Date(startDate); | |
| showEventsForDate(selectedDate); | |
| } | |
| // Select color for new event | |
| function selectColor(color) { | |
| document.getElementById('event-color').value = color; | |
| // Update UI to show selected color | |
| document.querySelectorAll('#eventForm [class*="bg-"].border-indigo-600').forEach(el => { | |
| el.classList.remove('border-indigo-600'); | |
| el.classList.add('border-white'); | |
| }); | |
| document.querySelector(`#eventForm .bg-${color}-500`).classList.remove('border-white'); | |
| document.querySelector(`#eventForm .bg-${color}-500`).classList.add('border-indigo-600'); | |
| } | |
| // Initialize dropdown menus | |
| function initDropdowns() { | |
| document.querySelectorAll('.dropdown button').forEach(button => { | |
| button.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| const dropdown = this.parentElement; | |
| const menu = dropdown.querySelector('.dropdown-menu'); | |
| // Close all other dropdowns first | |
| document.querySelectorAll('.dropdown-menu').forEach(m => { | |
| if (m !== menu) m.classList.add('hidden'); | |
| }); | |
| // Toggle current dropdown | |
| menu.classList.toggle('hidden'); | |
| }); | |
| }); | |
| // Close dropdowns when clicking elsewhere | |
| document.addEventListener('click', function() { | |
| document.querySelectorAll('.dropdown-menu').forEach(menu => { | |
| menu.classList.add('hidden'); | |
| }); | |
| }); | |
| } | |
| // Modal functions | |
| function openModal(modalId) { | |
| document.getElementById(modalId).classList.remove('hidden'); | |
| } | |
| function closeModal(modalId) { | |
| document.getElementById(modalId).classList.add('hidden'); | |
| } | |
| </script> | |
| </body> | |
| </html> |