diff options
| author | polo <ordipolo@gmx.fr> | 2025-06-23 03:33:38 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2025-06-23 03:33:38 +0200 |
| commit | cebc19ef236aac2968d2ffccfcff9b975b63fa8d (patch) | |
| tree | 5b8e08045a45063475f533bfae4b4524720fe7bd /public/js/calendar_admin.js | |
| parent | 8cf5ac1abf9e2a6134cb82d4582aecaa99b1331a (diff) | |
| download | cms-cebc19ef236aac2968d2ffccfcff9b975b63fa8d.tar.gz cms-cebc19ef236aac2968d2ffccfcff9b975b63fa8d.tar.bz2 cms-cebc19ef236aac2968d2ffccfcff9b975b63fa8d.zip | |
fullcalendar
Diffstat (limited to 'public/js/calendar_admin.js')
| -rw-r--r-- | public/js/calendar_admin.js | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/public/js/calendar_admin.js b/public/js/calendar_admin.js new file mode 100644 index 0000000..a99b069 --- /dev/null +++ b/public/js/calendar_admin.js | |||
| @@ -0,0 +1,375 @@ | |||
| 1 | // js/calendar_admin.js | ||
| 2 | |||
| 3 | document.addEventListener('DOMContentLoaded', function(){ | ||
| 4 | const calendarEl = document.getElementById('calendar'); | ||
| 5 | let selected_start_string = null; | ||
| 6 | let event_selected = false; // pour event.remove() | ||
| 7 | |||
| 8 | const calendar = new FullCalendar.Calendar(calendarEl,{ | ||
| 9 | editable: true, | ||
| 10 | locale: 'fr', | ||
| 11 | //timeZone: 'local', // à modifier pour être à l'heure d'un autre pays | ||
| 12 | initialView: 'dayGridMonth', | ||
| 13 | headerToolbar:{ | ||
| 14 | left: 'prev,next today', | ||
| 15 | center: 'title', | ||
| 16 | right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' | ||
| 17 | //right: 'dayGridMonth,timeGridWeek' | ||
| 18 | }, | ||
| 19 | slotMinWidth: 70, | ||
| 20 | defaultAllDay: false, | ||
| 21 | |||
| 22 | // numéros de semaine | ||
| 23 | //weekNumbers: true, | ||
| 24 | //weekText: 's', | ||
| 25 | |||
| 26 | // vue mois | ||
| 27 | contentHeight: 600, // après initialisation: calendar.setOption('contentHeight', 650); | ||
| 28 | //aspectRatio: 1.5, // après initialisation: calendar.setOption('aspectRatio', 1.8); | ||
| 29 | // pour recalculer la taille au redimensionnement du parent, exécuter: calendar.updateSize() | ||
| 30 | stickyHeaderDates: true, // garder les en-tête de colonnes lors du scroll | ||
| 31 | fixedWeekCount: false, // avec false, affiche 4, 5 ou 6 semaines selon le mois | ||
| 32 | selectable: true, // sélection de jours multiples | ||
| 33 | navLinks: true, // numéros de jour et de semaines clicables | ||
| 34 | |||
| 35 | // vue semaine | ||
| 36 | slotEventOverlap: true, // superposition (limitée) de deux évènements simultanés | ||
| 37 | allDayContent: 'Journée', // texte dans la case "toute la journée" | ||
| 38 | nowIndicator: true, // barre rouge pour maintenant | ||
| 39 | |||
| 40 | // params en plus: https://fullcalendar.io/docs/events-json-feed | ||
| 41 | events: 'index.php?action=get_events', // fichier PHP qui retourne les événements | ||
| 42 | |||
| 43 | select: function(info){ | ||
| 44 | selected_start_string = info.startStr; // variable "globale" | ||
| 45 | event_selected = false; | ||
| 46 | const aside = document.querySelector('aside'); | ||
| 47 | let checked = ''; | ||
| 48 | let input = 'datetime-local'; | ||
| 49 | |||
| 50 | // on veut des chaines de la forme 2025-05-20T07:05 | ||
| 51 | // il faut retirer les secondes et le fuseau horaire du format ISO, c'est chiant | ||
| 52 | // on enverra par contre une chaine ISO au serveur pour avoir un enregistrement correct | ||
| 53 | |||
| 54 | let start_value; | ||
| 55 | let end_value; | ||
| 56 | const end = new Date(info.endStr); | ||
| 57 | |||
| 58 | if(calendar.view.type == 'dayGridMonth'){ | ||
| 59 | start_value = info.startStr + 'T10:00'; | ||
| 60 | end.setDate(end.getDate() - 1); // jour de fin modifié pour ne pas faire bizarre pour l'utilisateur | ||
| 61 | end.setHours(11); | ||
| 62 | end_value = end.toISOString().split('T')[0] + 'T11:00'; | ||
| 63 | } | ||
| 64 | else if(calendar.view.type == 'timeGridWeek' || calendar.view.type == 'timeGridDay'){ | ||
| 65 | const start_array = info.startStr.split("T"); | ||
| 66 | const end_array = info.endStr.split("T"); | ||
| 67 | |||
| 68 | // clic sur la ligne "Journée", = 'dayGridMonth' | ||
| 69 | if(start_array.length == 1){ | ||
| 70 | checked = 'checked'; | ||
| 71 | input = 'date'; | ||
| 72 | start_value = info.startStr; | ||
| 73 | end.setDate(end.getDate() - 1); | ||
| 74 | end_value = end.toISOString().split('T')[0]; | ||
| 75 | } | ||
| 76 | else if(start_array.length == 2){ | ||
| 77 | start_value = start_array[0] + "T" + start_array[1].substr(0,5); // format 2025-06-12T10:00 | ||
| 78 | end_value = end_array[0] + "T" + end_array[1].substr(0,5); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | const aside_content = `<div class="form_event"> | ||
| 83 | <div class="event_title_box"> | ||
| 84 | <h2>Nouvel évènement</h2> | ||
| 85 | </div> | ||
| 86 | <div class=""> | ||
| 87 | <label for="event_title">Nom</label> | ||
| 88 | <input type="text" id="event_title"> | ||
| 89 | </div> | ||
| 90 | <div class=""> | ||
| 91 | <label for="event_description">Description</label> | ||
| 92 | <textarea id="event_description" cols="27"></textarea> | ||
| 93 | </div> | ||
| 94 | <div class=""> | ||
| 95 | <input type="checkbox" id="event_all_day" class="event_all_day" ` + checked + `> | ||
| 96 | <label for="event_all_day">Journée entière</label> | ||
| 97 | </div> | ||
| 98 | <div class=""> | ||
| 99 | <label for="event_start">Début</label> | ||
| 100 | <input type="` + input + `" id="event_start" value="` + start_value + `"> | ||
| 101 | </div> | ||
| 102 | <div class=""> | ||
| 103 | <label for="event_end">Fin</label> | ||
| 104 | <input type="` + input + `" id="event_end" value="` + end_value + `"> | ||
| 105 | </div> | ||
| 106 | <div class=""> | ||
| 107 | <label for="event_color">Couleur</label> | ||
| 108 | <input type="color" id="event_color" value="#3788D8"> | ||
| 109 | </div> | ||
| 110 | <button class="submit_new_event">Enregistrer</button> | ||
| 111 | <button class="event_close_button">Annuler</button> | ||
| 112 | </div>`; | ||
| 113 | aside.innerHTML = aside_content; | ||
| 114 | calendar.updateSize(); | ||
| 115 | }, | ||
| 116 | //unselect: function(event, view){}, | ||
| 117 | eventClick: function(info){ | ||
| 118 | event_selected = true; // variable "globale" | ||
| 119 | const aside = document.querySelector('aside'); | ||
| 120 | const checked = info.event.allDay ? 'checked' : ''; | ||
| 121 | const input = info.event.allDay ? 'date' : 'datetime-local'; | ||
| 122 | |||
| 123 | // change des objets Date en chaînes compatibles avec les input | ||
| 124 | function formatDate(date){ | ||
| 125 | return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0') | ||
| 126 | + (info.event.allDay ? '' : 'T' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0')); | ||
| 127 | } | ||
| 128 | function minusOneDay(date){ | ||
| 129 | date.setDate(date.getDate() - 1); // jour de fin modifié pour ne pas faire bizarre pour l'utilisateur | ||
| 130 | return date; | ||
| 131 | } | ||
| 132 | |||
| 133 | const formated_start = formatDate(info.event.start); | ||
| 134 | const formated_end = formatDate(info.event.allDay ? minusOneDay(info.event.end) : info.event.end, info.event.allDay); | ||
| 135 | |||
| 136 | const aside_content = `<div class="form_event"> | ||
| 137 | <div class="event_title_box"> | ||
| 138 | <h2>Modifier un évènement</h2> | ||
| 139 | </div> | ||
| 140 | <div class=""> | ||
| 141 | <label for="event_title">Nom</label> | ||
| 142 | <input type="text" id="event_title" value="` + info.event.title + `"> | ||
| 143 | <input type="hidden" id="event_id" value="` + info.event.id + `"> | ||
| 144 | </div> | ||
| 145 | <div class=""> | ||
| 146 | <label for="event_description">Description</label> | ||
| 147 | <textarea id="event_description" cols="27">` + info.event.extendedProps.description + `</textarea> | ||
| 148 | </div> | ||
| 149 | <div class=""> | ||
| 150 | <input type="checkbox" id="event_all_day" class="event_all_day" ` + checked + `> | ||
| 151 | <label for="event_all_day">Journée entière</label> | ||
| 152 | </div> | ||
| 153 | <div class=""> | ||
| 154 | <label for="event_start">Début</label> | ||
| 155 | <input type="` + input + `" id="event_start" value="` + formated_start + `"> | ||
| 156 | </div> | ||
| 157 | <div class=""> | ||
| 158 | <label for="event_end">Fin</label> | ||
| 159 | <input type="` + input + `" id="event_end" value="` + formated_end + `"> | ||
| 160 | </div> | ||
| 161 | <div class=""> | ||
| 162 | <label for="event_color">Couleur</label> | ||
| 163 | <input type="color" id="event_color" value="` + info.event.backgroundColor + `"> | ||
| 164 | </div> | ||
| 165 | <button class="submit_update_event">Modifier</button> | ||
| 166 | <button class="event_close_button">Annuler</button> | ||
| 167 | <button class="delete_event">Supprimer</button> | ||
| 168 | </div>`; | ||
| 169 | aside.innerHTML = aside_content; | ||
| 170 | calendar.updateSize(); | ||
| 171 | }, | ||
| 172 | viewDidMount: function(info){ // déclenché lorsque qu'une nouvelle vue est chargée (mois, semaine...) | ||
| 173 | if(selected_start_string){ | ||
| 174 | calendar.gotoDate(new Date(selected_start_string)); | ||
| 175 | } | ||
| 176 | }, | ||
| 177 | //datesSet: function(info){}, // déclenché lorsque des dates affichées sont chargées (= comme viewDidMount + changement de date) | ||
| 178 | }); | ||
| 179 | |||
| 180 | function hideModal(){ | ||
| 181 | const aside = document.querySelector('aside'); | ||
| 182 | event_selected = false; | ||
| 183 | aside.innerHTML = ''; | ||
| 184 | calendar.updateSize(); | ||
| 185 | } | ||
| 186 | |||
| 187 | function submitEvent(new_event){ | ||
| 188 | const event_title = document.getElementById('event_title').value; | ||
| 189 | const event_description = document.getElementById('event_description').value; | ||
| 190 | const event_all_day = document.getElementById('event_all_day').checked; | ||
| 191 | let event_start = document.getElementById('event_start').value; | ||
| 192 | let event_end = document.getElementById('event_end').value; | ||
| 193 | const event_color = document.getElementById('event_color').value; // #3788d8 par défaut | ||
| 194 | const event_id = new_event ? '' : document.getElementById('event_id').value; | ||
| 195 | |||
| 196 | if(event_title.length !== 0 && event_start.length !== 0 && event_end.length !== 0 && event_color.length !== 0 | ||
| 197 | && (new_event || event_id.length !== 0)) | ||
| 198 | { | ||
| 199 | if(event_all_day){ | ||
| 200 | // on remet le jour de fin exclu | ||
| 201 | const tmp_object = new Date(event_end); | ||
| 202 | tmp_object.setDate(tmp_object.getDate() + 1); | ||
| 203 | event_end = tmp_object.toISOString().split('T')[0]; | ||
| 204 | } | ||
| 205 | else{ | ||
| 206 | event_start = new Date(event_start).toISOString(); | ||
| 207 | event_end = new Date(event_end).toISOString(); | ||
| 208 | } | ||
| 209 | console.log(event_end); | ||
| 210 | |||
| 211 | if(event_start > event_end || (!event_all_day && event_start == event_end)){ | ||
| 212 | return; | ||
| 213 | } | ||
| 214 | |||
| 215 | // création | ||
| 216 | if(new_event){ | ||
| 217 | const event = { | ||
| 218 | title: event_title, | ||
| 219 | description: event_description, | ||
| 220 | allDay: event_all_day, | ||
| 221 | start: event_start, | ||
| 222 | end: event_end, | ||
| 223 | color: event_color | ||
| 224 | }; | ||
| 225 | |||
| 226 | fetch('index.php?action=new_event', { | ||
| 227 | method: 'POST', | ||
| 228 | headers: { | ||
| 229 | 'Content-Type': 'application/json', | ||
| 230 | }, | ||
| 231 | body: JSON.stringify(event), | ||
| 232 | }) | ||
| 233 | .then(response => response.json()) | ||
| 234 | .then(data => { | ||
| 235 | if(data.success){ | ||
| 236 | event.id = data.id; | ||
| 237 | calendar.addEvent(event); | ||
| 238 | hideModal(); | ||
| 239 | } | ||
| 240 | }) | ||
| 241 | .catch((error) => { | ||
| 242 | console.error('Error:', error); | ||
| 243 | }); | ||
| 244 | |||
| 245 | } | ||
| 246 | // modification | ||
| 247 | else{ | ||
| 248 | const event = calendar.getEventById(event_id); | ||
| 249 | |||
| 250 | if(event){ | ||
| 251 | const event_copy = { | ||
| 252 | id: parseInt(event.id), | ||
| 253 | description: event_description, | ||
| 254 | title: event_title, | ||
| 255 | allDay: event_all_day, | ||
| 256 | start: event_start, | ||
| 257 | end: event_end, | ||
| 258 | color: event_color | ||
| 259 | }; | ||
| 260 | |||
| 261 | fetch('index.php?action=update_event', { | ||
| 262 | method: 'POST', | ||
| 263 | headers: { | ||
| 264 | 'Content-Type': 'application/json', | ||
| 265 | }, | ||
| 266 | body: JSON.stringify(event_copy), | ||
| 267 | }) | ||
| 268 | .then(response => response.json()) | ||
| 269 | .then(data => { | ||
| 270 | if(data.success){ | ||
| 271 | event.setProp('title', event_title); | ||
| 272 | event.setExtendedProp('description', event_description); | ||
| 273 | event.setAllDay(event_all_day); | ||
| 274 | event.setStart(event_start); | ||
| 275 | event.setEnd(event_end); | ||
| 276 | event.setProp('color', event_color); | ||
| 277 | hideModal(); | ||
| 278 | } | ||
| 279 | }) | ||
| 280 | .catch((error) => { | ||
| 281 | console.error('Error:', error); | ||
| 282 | }); | ||
| 283 | } | ||
| 284 | else{ | ||
| 285 | console.log("Événement non trouvé !"); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | else{ | ||
| 290 | // notif input vide | ||
| 291 | console.log('erreur: input vide'); | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | function checkAllDay(){ | ||
| 296 | const event_start_input = document.getElementById('event_start'); | ||
| 297 | const event_end_input = document.getElementById('event_end'); | ||
| 298 | |||
| 299 | const start = event_start_input.value; | ||
| 300 | const end = event_end_input.value; | ||
| 301 | |||
| 302 | if(document.getElementById('event_all_day').checked){ | ||
| 303 | event_start_input.type = 'date'; | ||
| 304 | event_end_input.type = 'date'; | ||
| 305 | |||
| 306 | event_start_input.value = start.split('T')[0]; | ||
| 307 | event_end_input.value = end.split('T')[0]; | ||
| 308 | } | ||
| 309 | else{ | ||
| 310 | event_start_input.type = 'datetime-local'; | ||
| 311 | event_end_input.type = 'datetime-local'; | ||
| 312 | |||
| 313 | event_start_input.value = start + 'T10:00'; | ||
| 314 | event_end_input.value = end + 'T11:00'; | ||
| 315 | } | ||
| 316 | } | ||
| 317 | function removeEvent(){ | ||
| 318 | if(!confirm("Voulez-vous vraiment supprimer cet évènement du calendrier?")){ | ||
| 319 | return; | ||
| 320 | } | ||
| 321 | const event_id = document.getElementById('event_id').value; | ||
| 322 | const event = calendar.getEventById(event_id); | ||
| 323 | |||
| 324 | fetch('index.php?action=remove_event', { | ||
| 325 | method: 'POST', | ||
| 326 | headers: { | ||
| 327 | 'Content-Type': 'application/json', | ||
| 328 | }, | ||
| 329 | body: JSON.stringify({'id': event_id}), | ||
| 330 | }) | ||
| 331 | .then(response => response.json()) | ||
| 332 | .then(data => { | ||
| 333 | if(data.success){ | ||
| 334 | event.remove(); | ||
| 335 | hideModal(); | ||
| 336 | } | ||
| 337 | }) | ||
| 338 | .catch((error) => { | ||
| 339 | console.error('Error:', error); | ||
| 340 | }); | ||
| 341 | event_selected = false; | ||
| 342 | } | ||
| 343 | |||
| 344 | // touches de clavier | ||
| 345 | document.addEventListener('keydown', function(event){ | ||
| 346 | if(event.key === 'Escape'){ | ||
| 347 | hideModal(); | ||
| 348 | } | ||
| 349 | else if(event.key === 'Delete' && event_selected === true){ | ||
| 350 | removeEvent(); | ||
| 351 | } | ||
| 352 | }); | ||
| 353 | |||
| 354 | // boutons dans la "modale" | ||
| 355 | // technique de la délégation d'événements pour utiliser un bouton ajouté dynamiquement | ||
| 356 | document.addEventListener('click', function(event){ | ||
| 357 | if(event.target.classList.contains('event_close_button')){ | ||
| 358 | hideModal(); | ||
| 359 | } | ||
| 360 | else if(event.target.classList.contains('event_all_day')){ | ||
| 361 | checkAllDay(); | ||
| 362 | } | ||
| 363 | else if(event.target.classList.contains('submit_new_event')){ | ||
| 364 | submitEvent(true); | ||
| 365 | } | ||
| 366 | else if(event.target.classList.contains('submit_update_event')){ | ||
| 367 | submitEvent(false); | ||
| 368 | } | ||
| 369 | else if(event.target.classList.contains('delete_event')){ | ||
| 370 | removeEvent(); | ||
| 371 | } | ||
| 372 | }); | ||
| 373 | |||
| 374 | calendar.render(); | ||
| 375 | }); \ No newline at end of file | ||
