PageSourceSearch

https://www.monlet.fr/assets/controllers/agenda_modal_controller-0848064f29058dbaaa96fad6dde160ff.js

js monlet.fr collected 2026-10-02 21:12:31 UTC 17,644 bytes, 514 lines download raw bytes

1import {Controller} from "@hotwired/stimulus";
2import moment from "moment-timezone";
3import {errorSwal, successSwal, warningSwal} from 'swal-utils';
4import { trans } from 'translator';
5
6export default class extends Controller {
7    static  values = {
8        stationSlug: String
9    }
10
11    static targets = [
12        'title',
13        'body',
14        'dateDebut',
15        'dateFin',
16        'dateNotification',
17        'notificationContainer',
18        'evenementForm',
19        'submitButton',
20        'fullDay',
21        'dateFinContainer',
22        'recurrenceMois',
23        'recurrenceRepetition',
24    ];
25
26    connect() {
27
28        this._handleFocusIn = (event) => {
29        // Si la cible du focus est dans une boîte de dialogue CKEditor,
30        // on empêche Bootstrap de récupérer le focus.
31        if (event.target.closest('.cke_dialog')) {
32            event.stopPropagation();
33        }
34    };
35        this.initializeFullDayUI();
36        this.initializeCkeditorImageWarning();
37    // true = phase de capture, important pour passer AVANT Bootstrap
38    document.addEventListener('focusin', this._handleFocusIn, true);
39    }
40
41    disconnect() {
42        if (this._handleFocusIn) {
43            document.removeEventListener('focusin', this._handleFocusIn, true);
44            this._handleFocusIn = null;
45        }
46    }
47
48    openModal(event) {
49        const {title, body} = event.detail;
50
51        // Injecter le titre et le formulaire dans la modale
52        this.titleTarget.innerText = title;
53        this.bodyTarget.innerHTML = body;
54
55        const scripts = this.bodyTarget.querySelectorAll('script');
56        scripts.forEach(oldScript => {
57            const newScript = document.createElement('script');
58            [...oldScript.attributes].forEach(attr =>
59                newScript.setAttribute(attr.name, attr.value)
60            );
61            newScript.textContent = oldScript.textContent;
62            oldScript.replaceWith(newScript);
63        });
64
65        this.initializeFullDayUI();
66        this.initializeCkeditorImageWarning();
67        // Afficher la modale
68        const modal = new bootstrap.Modal(this.element);
69        modal.show();
70
71    }
72
73    onDateSelect(event) {
74        const {date, datepicker} = event.detail;
75
76        if (datepicker === this.dateDebutTarget) {
77            this.handleDateDebutChange(date);
78        } else if (datepicker === this.dateFinTarget) {
79            this.handleDateFinChange(date);
80        }
81    }
82
83    afficheNotification(event) {
84        const selectedChoice = event.params.choice;
85        switch (selectedChoice) {
86            case 'notifPersonnalise':
87                this.handleNotificationPersonnalise();
88                break;
89            case 'notifMaintenant':
90                this.handleNotificationMaintenant();
91                break;
92            case 'notifJamais':
93            default:
94                this.handleNotificationJamais();
95                break;
96        }
97    }
98
99    handleDateDebutChange(dateDebut) {
100        const dateFinPicker = this.dateFinTarget.datepickerInstance;
101        const datefin = dateFinPicker.selectedDates[0]
102
103        if (datefin && dateDebut > datefin) {
104            const newDateFin = new Date(dateDebut.getTime() + 60 * 60 * 1000); // + 1heure
105            dateFinPicker.selectDate(newDateFin, {silent: false, updateTime: true});
106        }
107
108        const checked = this.fullDayTarget.checked;
109
110        this.updateRecurrenceMoisLabels(dateDebut);
111    }
112
113    onRecurrenceRepetitionChange() {
114        const dateDebutPicker = this.dateDebutTarget?.datepickerInstance;
115        this.updateRecurrenceMoisLabels(dateDebutPicker?.selectedDates[0]);
116    }
117
118    updateRecurrenceMoisLabels(dateDebut) {
119        if (!this.hasRecurrenceMoisTarget || !dateDebut) {
120            return;
121        }
122
123        const recurrenceRepetition = this.hasRecurrenceRepetitionTarget ? this.recurrenceRepetitionTarget.value : '';
124
125        const url = Routing.generate('app_agenda_ajax_mois', {station_slug: this.stationSlugValue});
126        const data = new URLSearchParams({
127            dateDebut: moment(dateDebut).format('YYYY-MM-DD HH:mm'),
128            recurrenceRepetition: recurrenceRepetition
129        });
130
131        fetch(url, {method: 'POST', body: data})
132            .then((response) => {
133                if (!response.ok) {
134                    throw new Error(response.statusText);
135                }
136                return response.json();
137            })
138            .then((labels) => this.applyRecurrenceMoisLabels(labels))
139            .catch((error) => console.error(error));
140    }
141
142    applyRecurrenceMoisLabels(labels) {
143        const select = this.recurrenceMoisTarget;
144
145        [['0', labels.numero], ['1', labels.jour], ['2', labels.dernier]].forEac
145h(([value, label]) => {
146            const option = select.querySelector(`option[value="${value}"]`);
147            if (option) {
148                option.textContent = label;
149            }
150        });
151    }
152
153    handleDateFinChange(dateFin) {
154        const dateNotificationPicker = this.dateNotificationTarget.datepickerInstance;
155        const dateNotification = dateNotificationPicker.selectedDates[0]
156
157        if (dateFin && dateNotification && dateNotification > dateFin) {
158            dateNotificationPicker.selectDate(dateFin, {silent: false});
159        }
160        dateNotificationPicker.update({
161            maxDate: dateFin
162        }, {silent: false});
163
164        const dateFinPicker = this.dateFinTarget.datepickerInstance;
165
166        const checked = this.fullDayTarget.checked;
167
168        if (checked) {
169            const end = new Date(dateFinPicker.selectedDates[0]);
170
171            end.setHours(23, 59, 59, 59);
172            // end.setDate(start.getDate() + 1);
173            dateFinPicker.selectDate(end, {silent: false, updateTime: true});
174        }
175    }
176
177    handleNotificationPersonnalise() {
178        const date = this.dateDebutTarget.datepickerInstance.selectedDates[0];
179        this.dateNotificationTarget.datepickerInstance.selectDate(date, {silent: true, updateTime: true})
180        this.notificationContainerTarget.classList.remove('d-none');
181    }
182
183    handleNotificationMaintenant() {
184        const date = moment.tz(this.dateNotificationTarget.usertimezone).toDate()
185        this.dateNotificationTarget.datepickerInstance.selectDate(date, {silent: true, updateTime: true})
186        this.notificationContainerTarget.classList.add('d-none');
187    }
188
189    handleNotificationJamais() {
190        this.dateNotificationTarget.datepickerInstance.clear({silent: true});
191        this.notificationContainerTarget.classList.add('d-none');
192    }
193
194    toggleFullDay() {
195        if (!this.hasFullDayTarget) {
196            return;
197        }
198
199        const checked = this.fullDayTarget.checked;
200
201        const dateDebutPicker = this.dateDebutTarget.datepickerInstance;
202        const dateFinPicker = this.dateFinTarget.datepickerInstance;
203
204        if (!dateDebutPicker || !dateFinPicker) {
205            return;
206        }
207
208        if (checked) {
209
210            const start = dateDebutPicker.selectedDates[0];
211            const end = dateFinPicker.selectedDates[0];
212
213            start.setHours(0, 0, 0, 0);
214            end.setHours(23, 59, 59, 59);
215
216            dateDebutPicker.selectDate(start, {silent: true, updateTime: true});
217            dateFinPicker.selectDate(end, {silent: true, updateTime: true});
218
219            try {
220                dateDebutPicker.update({timepicker: false}, true);
221            }
222            catch (e) {}
223            try {
224                dateFinPicker.update({timepicker: false}, true);
225            }
226            catch (e) {}
227        }
228        if (!checked) {
229
230            dateDebutPicker.update({timepicker: true}, true);
231
232            dateFinPicker.update({timepicker: true}, true);
233        }
234    }
235
236    initializeFullDayUI(retryCount = 10) {
237        if (!this.hasFullDayTarget || !this.fullDayTarget.checked) {
238            return;
239        }
240        const dateDebutPicker = this.dateDebutTarget?.datepickerInstance;
241        const dateFinPicker   = this.dateFinTarget?.datepickerInstance;
242
243        if (!dateDebutPicker || !dateFinPicker) {
244            if (retryCount > 0) {
245                setTimeout(() => this.initializeFullDayUI(retryCount - 1), 30);
246            }
247            return;
248        }
249
250        const checked = this.fullDayTarget.checked;
251
252        if (checked) {
253            const start = dateDebutPicker.selectedDates[0];
254            const end = dateFinPicker.selectedDates[0];
255            if(start.getDate() === end.getDate() - 1) {
256                end.setMinutes(end.getMinutes() - 1);
257
258                dateFinPicker.selectDate(end, {silent: true, updateTime: true});
259            }
260                try {
261                    dateDebutPicker.update({timepicker: false}, true);
262                } catch (e) {
263                }
264                try {
265                    dateFinPicker.update({timepicker: false}, true);
266                } catch (e) {
267                }
268
269        }
270    }
271
272    normalizeBeforeFormFlowSubmit(event) {
273        this.normalizeDatesBeforeSubmit();
274    }
275
276    async onSubmit(event) {
277        const form = this.evenementFormTarget;
278
279        if (window.CKEDITOR) {
280        Object.values(CKEDITOR.instances).forEach(instance => instance.updateElement());
281        // ou, plus ciblé :
282        // CKEDITOR.instances.qsmart_illiwapbundle_evenement_description?.updateElement();
283    }
284
285        if (form.checkValidity()) {
286            const url = form.getAttribute('action');
287            this.normalizeDatesBeforeSubmit();
288            const data = new URLSearchParams(new FormData(form));
289
290            this.submitButtonTarget.disabled = true;
291
292            try {
293                const response = await fetch(url, {
294                    method: 'POST',
295                    body: data,
296                });
297
298                if (!response.ok) {
299                    throw new Error(response.statusText);
300                }
301
302                document.dispatchEvent(new CustomEvent('calendar:refresh'));
303                const modal = bootstrap.Modal.getInstance(this.element);
304                modal.hide();
305
306                const message = url.includes("create") ? trans('agenda.swal.evenement_success_create') : trans('agenda.swal.evenement_success_edit');
307                successSwal(message);
308            } catch (error) {
309                errorSwal(trans('agenda.swal.evenement_error_create'), error.message);
310                this.submitButtonTarget.disabled = false;
311            }
312        } else {
313            form.reportValidity();
314            this.submitButtonTarget.disabled = false;
315        }
316    }
317
318    normalizeDatesBeforeSubmit() {
319        // Chercher les éléments dans le DOM au lieu d'utiliser les targets
320        const dateDebutElement = this.element.querySelector('[data-agenda-modal-target="dateDebut"]');
321        const dateFinElement = this.element.querySelector('[data-agenda-modal-target="dateFin"]');
322        const fullDayElement = this.element.querySelector('[data-agenda-modal-target="fullDay"]');
323        
324        const fields = [
325            dateDebutElement,
326            dateFinElement,
327        ];
328        
329        const checked = fullDayElement && fullDayElement.checked;
330
331        if (checked) {
332            const dateFinPicker = dateFinElement.datepickerInstance;
333            const end = dateFinPicker.selectedDates[0];
334            const dateDebutPicker = dateDebutElement.datepickerInstance;
335            const start = dateDebutPicker.selectedDates[0];
336
337            if (start.getDate() === end.getDate()) {
338                end.setHours(0, 0, 0, 0);
339                end.setDate(end.getDate() + 1);
340                // this.dateFinTarget.value = moment(end).format('YYYY-MM-DD HH:mm');
341                dateFinPicker.selectDate(end, {silent: true, updateTime: true});
342            }
343        }
344        fields.forEach((input) => {
345            if (!input || !input.datepickerInstance) {
346                return;
347
348            }
349            const picker = input.datepickerInstance;
350
351            const date = picker.selectedDates[0];
352            if (!date) {
353                return;
354            }
355
356            const dateFormatAttr = input.getAttribute('data-datepicker-dateFormat') || 'YYYY-MM-DD';
357            const timeFormatAttr = input.getAttribute('data-datepicker-timeFormat') || 'HH:mm';
358            const separator = input.getAttribute('data-datepicker-dateTimeSeparator') || ' ';
359
360            const backendFormat = `${dateFormatAttr.toUpperCase()}${separator}${timeFormatAttr}`;
361
362            input.value = moment(date).format(backendFormat);
363        });
364    }
365
366    async clicDeleteEvenement(event) {
367        const confirmed = await warningSwal({
368            title: trans('agenda.swal.evenement_warning_delete_title'),
369            text: trans('agenda.swal.evenement_warning_delete'),
370            confirmButtonText: trans('agenda.swal.evenement_warning_delete_button'),
371            cancelButtonText: trans('agenda.swal.evenement_warning_cancel_button')
372        });
373
374        if (confirmed) {
375            this.deleteEvenement(event.params.eventId);
376        }
377    }
378
379    deleteEvenement(eventId) {
380        const url = Routing.generate('app_agenda_delete', {
381            id: eventId,
382            station_slug: this.stationSlugValue
383        });
384        fetch(url, {method: 'POST'})
385            .then(response => {
386                if (!response.ok) {
387                    throw new Error(response.statusText);
388                }
389                successSwal(trans('agenda.swal.evenement_success_delete'));
390                document.dispatchEvent(new CustomEvent('calendar:refresh'));
391                const modal = bootstrap.Modal.getInstance(this.element);
392                modal.hide();
393            })
394            .catch(err => {
395                console.error(err);
396                errorSwal(err.message);
397            });
398    }
399
400    duplicateEvenement(event) {
401        const url = Routing.generate('app_agenda_clone', {
402            id: event.params.eventId,
403            station_slug: this.stationSlugValue
404        });
405
406        const currentModal = bootstrap.Modal.getInstance(this.element);
407        if (currentModal) {
408            currentModal.hide();
409        }
410
411        fetch(url)
412            .then(response => {
413                if (!response.ok) {
414                    throw Error(response.statusText)
415                }
416                return response.text()
417            })
418            .then(htmlContent => {
419                const modalEvent = new CustomEvent('modal:open', {
420                    detail: {
421                        title: trans('agenda.index.duplicate_title'),
422                        body: htmlContent
423                    }
424                });
425                window.dispatchEvent(modalEvent);
426            }).catch(function () {
427                errorSwal(trans('agenda.new.duplicate_error'))
428        });
429    }
430
431    initializeCkeditorImageWarning() {
432        if (typeof CKEDITOR === 'undefined') {
433            return;
434        }
435
436        const editorName = 'evenement_description';
437        const controller = this;
438
439        function attachImageListener(editor) {
440            if (!editor) {
441                return;
442            }
443
444            let hasShownWarning = false;
445
446            editor.on('change', function () {
447
448                if (hasShownWarning) {
449                    return;
450                }
451
452                const data = editor.getData() || '';
453                if (data.indexOf('<img') !== -1) {
454                    controller.showImageCopyrightWarning();
455                    hasShownWarning = true;
456                }
457            });
458        }
459
460        const existingEditor = CKEDITOR.instances[editorName];
461
462        if (existingEditor && existingEditor.status === 'ready') {
463            attachImageListener(existingEditor);
464        } else {
465            CKEDITOR.on('instanceReady', function (event) {
466                if (event.editor.name === editorName) {
467                    attachImageListener(event.editor);
468                }
469            });
470        }
471    }
472
473    showImageCopyrightWarning() {
474        if (typeof Swal === 'undefined') {
475            return;
476        }
477
478        const swalOptions = {
479            title: trans('avertissement_soumission_image_titre'),
480            html: `
481                <div>
482                    <p id="swal-text" class="mb-0">${trans('avertissement_soumission_image_1')}</p>
483                    <div class="collapse mt-0" id="collapseFullText">
484                        <p>${trans('avertissement_soumission_image_2')}</p>
485                    </div>
486                    <a id="toggleLink" class="btn btn-link" data-toggle="collapse" href="#collapseFullText" role="button" aria-expanded="false" aria-controls="collapseFullText">
487                        <i id="chevronIcon" class="fa fa-chevron-down"></i>
488                    </a>
489                </div>
490            `,
491            icon: "info",
492            confirmButtonColor: '#9FBA00',
493            willOpen: () => {
494                const chevronIcon = document.getElementById('chevronIcon');
495                const toggleLink = document.getElementById('toggleLink');
496                if (!chevronIcon || !toggleLink) {
497                    return;
498                }
499
500                toggleLink.addEventListener('click', () => {
501                    const fullText = document.getElementById('collapseFullText');
502                    if (!fullText) {
503                        return;
504                    }
505                    fullText.classList.toggle('show');
506                    chevronIcon.classList.toggle('fa-chevron-down');
507                    chevronIcon.classList.toggle('fa-chevron-up');
508                });
509            },
510        };
511
512        Swal.fire(swalOptions);
513    }
514}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.