diff options
Diffstat (limited to 'public/js/calendar.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 | ||