diff options
Diffstat (limited to 'public/js')
| -rw-r--r-- | public/js/calendar.js | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/public/js/calendar.js b/public/js/calendar.js new file mode 100644 index 0000000..3046ef0 --- /dev/null +++ b/public/js/calendar.js | |||
| @@ -0,0 +1,269 @@ | |||
| 1 | document.addEventListener('DOMContentLoaded', function(){ | ||
| 2 | const calendarEl = document.getElementById('calendar'); | ||
| 3 | let selected_start_string = null; | ||
| 4 | |||
| 5 | const calendar = new FullCalendar.Calendar(calendarEl,{ | ||
| 6 | editable: true, | ||
| 7 | locale: 'fr', | ||
| 8 | initialView: 'dayGridMonth', | ||
| 9 | headerToolbar:{ | ||
| 10 | left: 'prev,next today', | ||
| 11 | center: 'title', | ||
| 12 | right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' | ||
| 13 | //right: 'dayGridMonth,timeGridWeek' | ||
| 14 | }, | ||
| 15 | slotMinWidth: 70, | ||
| 16 | defaultAllDay: false, | ||
| 17 | |||
| 18 | // numéros de semaine | ||
| 19 | //weekNumbers: true, | ||
| 20 | //weekText: 's', | ||
| 21 | |||
| 22 | // vue mois | ||
| 23 | contentHeight: 600, // après initialisation: calendar.setOption('contentHeight', 650); | ||
| 24 | //aspectRatio: 1.5, // après initialisation: calendar.setOption('aspectRatio', 1.8); | ||
| 25 | // pour recalculer la taille au redimensionnement du parent, exécuter: calendar.updateSize() | ||
| 26 | stickyHeaderDates: true, // garder les en-tête de colonnes lors du scroll | ||
| 27 | fixedWeekCount: false, // avec false, affiche 4, 5 ou 6 semaines selon le mois | ||
| 28 | selectable: true, // sélection de jours multiples | ||
| 29 | navLinks: true, // numéros de jour et de semaines clicables | ||
| 30 | |||
| 31 | // vue semaine | ||
| 32 | slotEventOverlap: true, // superposition (limitée) de deux évènements simultanés | ||
| 33 | allDayContent: 'Journée', // texte dans la case "toute la journée" | ||
| 34 | nowIndicator: true, // barre rouge pour maintenant | ||
| 35 | |||
| 36 | select: function(info){ | ||
| 37 | const aside = document.querySelector('aside'); | ||
| 38 | let start_value; | ||
| 39 | |||
| 40 | // on veut des chaines de la forme 2025-05-20T07:05 | ||
| 41 | // il faut retirer les secondes et le fuseau horaire du format ISO, c'est chiant | ||
| 42 | // enverra par contre une chaine ISO au serveur pour avoir un enregistrement correct | ||
| 43 | //selected_start = document.getElementById('event_start'); | ||
| 44 | |||
| 45 | const end = new Date(info.endStr); | ||
| 46 | if(calendar.view.type == 'dayGridMonth'){ | ||
| 47 | start_value = info.startStr + 'T10:00'; | ||
| 48 | end.setDate(end.getDate() - 1); | ||
| 49 | end.setHours(11); | ||
| 50 | } | ||
| 51 | else if(calendar.view.type == 'timeGridWeek' || calendar.view.type == 'timeGridDay'){ | ||
| 52 | const start_array = info.startStr.split("T"); | ||
| 53 | start_value = start_array[0] + "T" + start_array[1].substr(0,5); // format 2025-06-12T10:00 | ||
| 54 | } | ||
| 55 | |||
| 56 | const end_value = end.toISOString().split('T')[0] + "T" + String(end.getHours()).padStart(2, '0') + ":" + String(end.getMinutes()).padStart(2, '0'); | ||
| 57 | selected_start_string = start_value; | ||
| 58 | |||
| 59 | //console.log(info.endStr); | ||
| 60 | //console.log(end_value.value); | ||
| 61 | |||
| 62 | const aside_content = `<div class="form_event"> | ||
| 63 | <div class="event_title_box"> | ||
| 64 | <h2>Nouvel évènement</h2> | ||
| 65 | </div> | ||
| 66 | <div class=""> | ||
| 67 | <label for="event_title">Nom</label> | ||
| 68 | <input type="text" id="event_title"> | ||
| 69 | </div> | ||
| 70 | <div class=""> | ||
| 71 | <input type="checkbox" id="event_all_day"> | ||
| 72 | <label for="event_all_day">Journée entière</label> | ||
| 73 | </div> | ||
| 74 | <div class=""> | ||
| 75 | <label for="event_start">Début</label> | ||
| 76 | <input type="datetime-local" id="event_start" value="` + start_value + `"> | ||
| 77 | </div> | ||
| 78 | <div class=""> | ||
| 79 | <label for="event_end">Fin</label> | ||
| 80 | <input type="datetime-local" id="event_end" value="` + end_value + `"> | ||
| 81 | </div> | ||
| 82 | <div class=""> | ||
| 83 | <label for="event_color">Couleur</label> | ||
| 84 | <input type="color" id="event_color" value="#3788D8"> | ||
| 85 | </div> | ||
| 86 | <button class="submit_new_event">Enregistrer</button> | ||
| 87 | <button class="event_close_button">Annuler</button> | ||
| 88 | </div>`; | ||
| 89 | aside.innerHTML = aside_content; | ||
| 90 | calendar.updateSize(); | ||
| 91 | }, | ||
| 92 | //~ unselect: function(event, view) { | ||
| 93 | //~ const aside = document.querySelector('aside'); | ||
| 94 | //~ aside.innerHTML = ''; | ||
| 95 | //~ //calendar.updateSize(); | ||
| 96 | //~ }, | ||
| 97 | eventClick: function(info){ | ||
| 98 | const aside = document.querySelector('aside'); | ||
| 99 | const checked = info.event.allDay ? 'checked' : ''; | ||
| 100 | const input = info.event.allDay ? 'date' : 'datetime-local'; | ||
| 101 | |||
| 102 | // change des objets Date en chaînes compatibles avec des input type datetime-local, ex: 2025-05-20T07:05 | ||
| 103 | function formatDate(date, all_day){ | ||
| 104 | return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0') | ||
| 105 | + (all_day ? '' : 'T' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0')); | ||
| 106 | } | ||
| 107 | const formated_start = formatDate(info.event.start, info.event.allDay); | ||
| 108 | const formated_end = formatDate(info.event.end, info.event.allDay); | ||
| 109 | |||
| 110 | const aside_content = `<div class="form_event"> | ||
| 111 | <div class="event_title_box"> | ||
| 112 | <h2>Modifier un évènement</h2> | ||
| 113 | </div> | ||
| 114 | <div class=""> | ||
| 115 | <label for="event_title">Nom</label> | ||
| 116 | <input type="text" id="event_title" value="` + info.event.title + `"> | ||
| 117 | <input type="hidden" id="event_id" value="` + info.event.id +`"> | ||
| 118 | </div> | ||
| 119 | <div class=""> | ||
| 120 | <input type="checkbox" id="event_all_day" class="event_all_day" ` + checked + `> | ||
| 121 | <label for="event_all_day">Journée entière</label> | ||
| 122 | </div> | ||
| 123 | <div class=""> | ||
| 124 | <label for="event_start">Début</label> | ||
| 125 | <input type="` + input + `" id="event_start" value="` + formated_start + `"> | ||
| 126 | </div> | ||
| 127 | <div class=""> | ||
| 128 | <label for="event_end">Fin</label> | ||
| 129 | <input type="` + input + `" id="event_end" value="` + formated_end + `"> | ||
| 130 | </div> | ||
| 131 | <div class=""> | ||
| 132 | <label for="event_color">Couleur</label> | ||
| 133 | <input type="color" id="event_color" value="` + info.event.backgroundColor + `"> | ||
| 134 | </div> | ||
| 135 | <button class="submit_update_event">Modifier</button> | ||
| 136 | <button class="event_close_button">Annuler</button> | ||
| 137 | </div>`; | ||
| 138 | aside.innerHTML = aside_content; | ||
| 139 | calendar.updateSize(); | ||
| 140 | }, | ||
| 141 | viewDidMount: function(info){ // déclenché lorsque qu'une nouvelle vue est chargée (mois, semaine...) | ||
| 142 | if(selected_start_string){ | ||
| 143 | calendar.gotoDate(new Date(selected_start_string)); | ||
| 144 | } | ||
| 145 | }, | ||
| 146 | //datesSet: function(info){}, // déclenché lorsque des dates affichées sont chargées (= comme viewDidMount + changement de date) | ||
| 147 | events: '../src/load-events.php' // fichier PHP qui retourne les événements | ||
| 148 | }); | ||
| 149 | |||
| 150 | function hideModal(){ | ||
| 151 | const aside = document.querySelector('aside'); | ||
| 152 | aside.innerHTML = ''; | ||
| 153 | calendar.updateSize(); | ||
| 154 | } | ||
| 155 | |||
| 156 | function submitEvent(new_event){ | ||
| 157 | const event_title = document.getElementById('event_title').value; | ||
| 158 | const event_all_day = document.getElementById('event_all_day').checked; | ||
| 159 | const event_start = document.getElementById('event_start').value; | ||
| 160 | const event_end = document.getElementById('event_end').value; | ||
| 161 | const event_color = document.getElementById('event_color').value; // #3788d8 par défaut | ||
| 162 | const event_id = new_event ? '' : document.getElementById('event_id').value; | ||
| 163 | |||
| 164 | if(event_title.length !== 0 && event_start.length !== 0 && event_end.length !== 0 && event_color.length !== 0 | ||
| 165 | && (new_event || event_id.length !== 0)) | ||
| 166 | { | ||
| 167 | const event_start_utc = new Date(event_start).toISOString(); // heure UTC pour fullcalendar (et pour le serveur) | ||
| 168 | const event_end_utc = new Date(event_end).toISOString(); | ||
| 169 | |||
| 170 | if(event_start_utc >= event_end_utc){ | ||
| 171 | return; | ||
| 172 | } | ||
| 173 | |||
| 174 | // création | ||
| 175 | if(new_event){ | ||
| 176 | /*const current_view = calendar.view; | ||
| 177 | switch(current_view.type){ | ||
| 178 | case 'dayGridMonth': | ||
| 179 | console.log('mois'); | ||
| 180 | break; | ||
| 181 | case 'timeGridWeek': | ||
| 182 | console.log('semaine'); | ||
| 183 | break; | ||
| 184 | case 'timeGridDay': | ||
| 185 | console.log('jour'); | ||
| 186 | break; | ||
| 187 | default: | ||
| 188 | console.log('erreur'); | ||
| 189 | }*/ | ||
| 190 | calendar.addEvent({ | ||
| 191 | // pas d'id, c'est au serveur de le créer | ||
| 192 | title: event_title, | ||
| 193 | allDay: event_all_day, | ||
| 194 | start: event_start_utc, | ||
| 195 | end: event_end_utc, | ||
| 196 | color: event_color | ||
| 197 | }); | ||
| 198 | } | ||
| 199 | // modification | ||
| 200 | else{ | ||
| 201 | const event = calendar.getEventById(event_id); | ||
| 202 | |||
| 203 | if(event){ | ||
| 204 | event.setProp('title', event_title); | ||
| 205 | event.setAllDay(event_all_day); | ||
| 206 | event.setStart(event_start_utc); | ||
| 207 | event.setEnd(event_end_utc); | ||
| 208 | event.setProp('color', event_color); | ||
| 209 | } | ||
| 210 | else{ | ||
| 211 | console.log("Événement non trouvé !"); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | hideModal(); | ||
| 216 | } | ||
| 217 | else{ | ||
| 218 | // notif input vide | ||
| 219 | console.log('erreur: input vide'); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | function checkAllDay(){ | ||
| 224 | const event_start_input = document.getElementById('event_start'); | ||
| 225 | const event_end_input = document.getElementById('event_end'); | ||
| 226 | |||
| 227 | const start = event_start_input.value; | ||
| 228 | const end = event_end_input.value; | ||
| 229 | |||
| 230 | if(document.getElementById('event_all_day').checked){ | ||
| 231 | event_start_input.type = 'date'; | ||
| 232 | event_end_input.type = 'date'; | ||
| 233 | |||
| 234 | event_start_input.value = start.split('T')[0]; | ||
| 235 | event_end_input.value = end.split('T')[0]; | ||
| 236 | } | ||
| 237 | else{ | ||
| 238 | event_start_input.type = 'datetime-local'; | ||
| 239 | event_end_input.type = 'datetime-local'; | ||
| 240 | |||
| 241 | event_start_input.value = start + 'T10:00'; | ||
| 242 | event_end_input.value = end + 'T11:00'; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | document.addEventListener('keydown', function(event){ | ||
| 247 | if(event.key === 'Escape') { | ||
| 248 | hideModal(); | ||
| 249 | } | ||
| 250 | }); | ||
| 251 | |||
| 252 | // technique de la délégation d'événements pour utiliser un bouton ajouté dynamiquement | ||
| 253 | document.addEventListener('click', function(event){ | ||
| 254 | if(event.target.classList.contains('event_close_button')){ | ||
| 255 | hideModal(); | ||
| 256 | } | ||
| 257 | else if(event.target.classList.contains('event_all_day')){ | ||
| 258 | checkAllDay(); | ||
| 259 | } | ||
| 260 | else if(event.target.classList.contains('submit_new_event')){ | ||
| 261 | submitEvent(true); | ||
| 262 | } | ||
| 263 | else if(event.target.classList.contains('submit_update_event')){ | ||
| 264 | submitEvent(false); | ||
| 265 | } | ||
| 266 | }); | ||
| 267 | |||
| 268 | calendar.render(); | ||
| 269 | }); \ No newline at end of file | ||
