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.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 | ||