diff options
Diffstat (limited to 'public/js/calendar_admin.js')
-rw-r--r-- | public/js/calendar_admin.js | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/public/js/calendar_admin.js b/public/js/calendar_admin.js new file mode 100644 index 0000000..253d127 --- /dev/null +++ b/public/js/calendar_admin.js | |||
@@ -0,0 +1,331 @@ | |||
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 | selected_start_string = info.startStr; // variable "globale" | ||
38 | const aside = document.querySelector('aside'); | ||
39 | let checked = ''; | ||
40 | let input = 'datetime-local'; | ||
41 | |||
42 | // on veut des chaines de la forme 2025-05-20T07:05 | ||
43 | // il faut retirer les secondes et le fuseau horaire du format ISO, c'est chiant | ||
44 | // on enverra par contre une chaine ISO au serveur pour avoir un enregistrement correct | ||
45 | |||
46 | let start_value; | ||
47 | let end_value; | ||
48 | const end = new Date(info.endStr); | ||
49 | |||
50 | if(calendar.view.type == 'dayGridMonth'){ | ||
51 | start_value = info.startStr + 'T10:00'; | ||
52 | end.setDate(end.getDate() - 1); // jour de fin modifié pour ne pas faire bizarre pour l'utilisateur | ||
53 | end.setHours(11); | ||
54 | end_value = end.toISOString().split('T')[0] + 'T11:00'; | ||
55 | } | ||
56 | else if(calendar.view.type == 'timeGridWeek' || calendar.view.type == 'timeGridDay'){ | ||
57 | const start_array = info.startStr.split("T"); | ||
58 | const end_array = info.endStr.split("T"); | ||
59 | |||
60 | // clic sur la ligne "Journée", = 'dayGridMonth' | ||
61 | if(start_array.length == 1){ | ||
62 | checked = 'checked'; | ||
63 | input = 'date'; | ||
64 | start_value = info.startStr; | ||
65 | end.setDate(end.getDate() - 1); | ||
66 | end_value = end.toISOString().split('T')[0]; | ||
67 | } | ||
68 | else if(start_array.length == 2){ | ||
69 | start_value = start_array[0] + "T" + start_array[1].substr(0,5); // format 2025-06-12T10:00 | ||
70 | end_value = end_array[0] + "T" + end_array[1].substr(0,5); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | const aside_content = `<div class="form_event"> | ||
75 | <div class="event_title_box"> | ||
76 | <h2>Nouvel évènement</h2> | ||
77 | </div> | ||
78 | <div class=""> | ||
79 | <label for="event_title">Nom</label> | ||
80 | <input type="text" id="event_title"> | ||
81 | </div> | ||
82 | <div class=""> | ||
83 | <label for="event_description">Description</label> | ||
84 | <textarea id="event_description" cols="27"></textarea> | ||
85 | </div> | ||
86 | <div class=""> | ||
87 | <input type="checkbox" id="event_all_day" class="event_all_day" ` + checked + `> | ||
88 | <label for="event_all_day">Journée entière</label> | ||
89 | </div> | ||
90 | <div class=""> | ||
91 | <label for="event_start">Début</label> | ||
92 | <input type="` + input + `" id="event_start" value="` + start_value + `"> | ||
93 | </div> | ||
94 | <div class=""> | ||
95 | <label for="event_end">Fin</label> | ||
96 | <input type="` + input + `" id="event_end" value="` + end_value + `"> | ||
97 | </div> | ||
98 | <div class=""> | ||
99 | <label for="event_color">Couleur</label> | ||
100 | <input type="color" id="event_color" value="#3788D8"> | ||
101 | </div> | ||
102 | <button class="submit_new_event">Enregistrer</button> | ||
103 | <button class="event_close_button">Annuler</button> | ||
104 | </div>`; | ||
105 | aside.innerHTML = aside_content; | ||
106 | calendar.updateSize(); | ||
107 | }, | ||
108 | //unselect: function(event, view){}, | ||
109 | eventClick: function(info){ | ||
110 | const aside = document.querySelector('aside'); | ||
111 | const checked = info.event.allDay ? 'checked' : ''; | ||
112 | const input = info.event.allDay ? 'date' : 'datetime-local'; | ||
113 | |||
114 | // change des objets Date en chaînes compatibles avec les input | ||
115 | function formatDate(date){ | ||
116 | return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0') | ||
117 | + (info.event.allDay ? '' : 'T' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0')); | ||
118 | } | ||
119 | function getEndMinusOneDay(date){ | ||
120 | date.setDate(date.getDate() - 1); // jour de fin modifié pour ne pas faire bizarre pour l'utilisateur | ||
121 | return date; | ||
122 | } | ||
123 | |||
124 | const formated_start = formatDate(info.event.start); | ||
125 | const formated_end = formatDate(info.event.allDay ? getEndMinusOneDay(info.event.end) : info.event.end, info.event.allDay); | ||
126 | |||
127 | const aside_content = `<div class="form_event"> | ||
128 | <div class="event_title_box"> | ||
129 | <h2>Modifier un évènement</h2> | ||
130 | </div> | ||
131 | <div class=""> | ||
132 | <label for="event_title">Nom</label> | ||
133 | <input type="text" id="event_title" value="` + info.event.title + `"> | ||
134 | <input type="hidden" id="event_id" value="` + info.event.id + `"> | ||
135 | </div> | ||
136 | <div class=""> | ||
137 | <label for="event_description">Description</label> | ||
138 | <textarea id="event_description" cols="27">` + info.event.extendedProps.description + `</textarea> | ||
139 | </div> | ||
140 | <div class=""> | ||
141 | <input type="checkbox" id="event_all_day" class="event_all_day" ` + checked + `> | ||
142 | <label for="event_all_day">Journée entière</label> | ||
143 | </div> | ||
144 | <div class=""> | ||
145 | <label for="event_start">Début</label> | ||
146 | <input type="` + input + `" id="event_start" value="` + formated_start + `"> | ||
147 | </div> | ||
148 | <div class=""> | ||
149 | <label for="event_end">Fin</label> | ||
150 | <input type="` + input + `" id="event_end" value="` + formated_end + `"> | ||
151 | </div> | ||
152 | <div class=""> | ||
153 | <label for="event_color">Couleur</label> | ||
154 | <input type="color" id="event_color" value="` + info.event.backgroundColor + `"> | ||
155 | </div> | ||
156 | <button class="submit_update_event">Modifier</button> | ||
157 | <button class="event_close_button">Annuler</button> | ||
158 | </div>`; | ||
159 | aside.innerHTML = aside_content; | ||
160 | calendar.updateSize(); | ||
161 | }, | ||
162 | viewDidMount: function(info){ // déclenché lorsque qu'une nouvelle vue est chargée (mois, semaine...) | ||
163 | if(selected_start_string){ | ||
164 | calendar.gotoDate(new Date(selected_start_string)); | ||
165 | } | ||
166 | }, | ||
167 | //datesSet: function(info){}, // déclenché lorsque des dates affichées sont chargées (= comme viewDidMount + changement de date) | ||
168 | events: '../src/load-events.php' // fichier PHP qui retourne les événements | ||
169 | }); | ||
170 | |||
171 | function hideModal(){ | ||
172 | const aside = document.querySelector('aside'); | ||
173 | aside.innerHTML = ''; | ||
174 | calendar.updateSize(); | ||
175 | } | ||
176 | |||
177 | function submitEvent(new_event){ | ||
178 | const event_title = document.getElementById('event_title').value; | ||
179 | const event_description = document.getElementById('event_description').value; | ||
180 | const event_all_day = document.getElementById('event_all_day').checked; | ||
181 | let event_start = document.getElementById('event_start').value; | ||
182 | let event_end = document.getElementById('event_end').value; | ||
183 | const event_color = document.getElementById('event_color').value; // #3788d8 par défaut | ||
184 | const event_id = new_event ? '' : document.getElementById('event_id').value; | ||
185 | |||
186 | if(event_title.length !== 0 && event_start.length !== 0 && event_end.length !== 0 && event_color.length !== 0 | ||
187 | && (new_event || event_id.length !== 0)) | ||
188 | { | ||
189 | if(event_all_day){ | ||
190 | // on remet le jour de fin exclu | ||
191 | const tmp_object = new Date(event_end); | ||
192 | tmp_object.setDate(tmp_object.getDate() + 1); | ||
193 | event_end = tmp_object.toISOString().split('T')[0]; | ||
194 | } | ||
195 | else{ | ||
196 | event_start = new Date(event_start).toISOString(); | ||
197 | event_end = new Date(event_end).toISOString(); | ||
198 | } | ||
199 | console.log(event_end); | ||
200 | |||
201 | if(event_start > event_end || (!event_all_day && event_start == event_end)){ | ||
202 | return; | ||
203 | } | ||
204 | |||
205 | // création | ||
206 | if(new_event){ | ||
207 | const event = { | ||
208 | title: event_title, | ||
209 | description: event_description, | ||
210 | allDay: event_all_day, | ||
211 | start: event_start, | ||
212 | end: event_end, | ||
213 | color: event_color | ||
214 | }; | ||
215 | |||
216 | fetch('../src/post-ajax.php', { | ||
217 | method: 'POST', | ||
218 | headers: { | ||
219 | 'Content-Type': 'application/json', | ||
220 | }, | ||
221 | body: JSON.stringify(event), | ||
222 | }) | ||
223 | .then(response => response.json()) | ||
224 | .then(data => { | ||
225 | if(data.success){ | ||
226 | event.id = data.id; | ||
227 | calendar.addEvent(event); | ||
228 | hideModal(); | ||
229 | } | ||
230 | }) | ||
231 | .catch((error) => { | ||
232 | console.error('Error:', error); | ||
233 | }); | ||
234 | |||
235 | } | ||
236 | // modification | ||
237 | else{ | ||
238 | const event = calendar.getEventById(event_id); | ||
239 | |||
240 | if(event){ | ||
241 | const event_copy = { | ||
242 | id: parseInt(event.id), | ||
243 | description: event_description, | ||
244 | title: event_title, | ||
245 | allDay: event_all_day, | ||
246 | start: event_start, | ||
247 | end: event_end, | ||
248 | color: event_color | ||
249 | }; | ||
250 | |||
251 | fetch('../src/post-ajax.php', { | ||
252 | method: 'POST', | ||
253 | headers: { | ||
254 | 'Content-Type': 'application/json', | ||
255 | }, | ||
256 | body: JSON.stringify(event_copy), | ||
257 | }) | ||
258 | .then(response => response.json()) | ||
259 | .then(data => { | ||
260 | if(data.success){ | ||
261 | event.setProp('title', event_title); | ||
262 | event.setExtendedProp('description', event_description); | ||
263 | event.setAllDay(event_all_day); | ||
264 | event.setStart(event_start); | ||
265 | event.setEnd(event_end); | ||
266 | event.setProp('color', event_color); | ||
267 | hideModal(); | ||
268 | } | ||
269 | }) | ||
270 | .catch((error) => { | ||
271 | console.error('Error:', error); | ||
272 | }); | ||
273 | } | ||
274 | else{ | ||
275 | console.log("Événement non trouvé !"); | ||
276 | } | ||
277 | } | ||
278 | } | ||
279 | else{ | ||
280 | // notif input vide | ||
281 | console.log('erreur: input vide'); | ||
282 | } | ||
283 | } | ||
284 | |||
285 | function checkAllDay(){ | ||
286 | const event_start_input = document.getElementById('event_start'); | ||
287 | const event_end_input = document.getElementById('event_end'); | ||
288 | |||
289 | const start = event_start_input.value; | ||
290 | const end = event_end_input.value; | ||
291 | |||
292 | if(document.getElementById('event_all_day').checked){ | ||
293 | event_start_input.type = 'date'; | ||
294 | event_end_input.type = 'date'; | ||
295 | |||
296 | event_start_input.value = start.split('T')[0]; | ||
297 | event_end_input.value = end.split('T')[0]; | ||
298 | } | ||
299 | else{ | ||
300 | event_start_input.type = 'datetime-local'; | ||
301 | event_end_input.type = 'datetime-local'; | ||
302 | |||
303 | event_start_input.value = start + 'T10:00'; | ||
304 | event_end_input.value = end + 'T11:00'; | ||
305 | } | ||
306 | } | ||
307 | |||
308 | document.addEventListener('keydown', function(event){ | ||
309 | if(event.key === 'Escape') { | ||
310 | hideModal(); | ||
311 | } | ||
312 | }); | ||
313 | |||
314 | // technique de la délégation d'événements pour utiliser un bouton ajouté dynamiquement | ||
315 | document.addEventListener('click', function(event){ | ||
316 | if(event.target.classList.contains('event_close_button')){ | ||
317 | hideModal(); | ||
318 | } | ||
319 | else if(event.target.classList.contains('event_all_day')){ | ||
320 | checkAllDay(); | ||
321 | } | ||
322 | else if(event.target.classList.contains('submit_new_event')){ | ||
323 | submitEvent(true); | ||
324 | } | ||
325 | else if(event.target.classList.contains('submit_update_event')){ | ||
326 | submitEvent(false); | ||
327 | } | ||
328 | }); | ||
329 | |||
330 | calendar.render(); | ||
331 | }); \ No newline at end of file | ||