{% extends "sco_page.j2" %} {% import 'wtf.j2' as wtf %} {% block styles %} {{super()}} <link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/tui.calendar/toastui-calendar.min.css"/> <link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css"/> <link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/edt.css"> {% endblock %} {% block app_content %} <div class="tab-content"> <h2>Expérimental: emploi du temps</h2> {{ form_groups_choice|safe }} <div class="dropdown"> <button class="dropbtn">Semaine</button> <div class="dropdown-content"> <ul> {% for v in views_names %} <li><a href="#" data-view="{{v}}">{{views_names[v]}}</a></li> {% endfor %} <li> <label> <input type="checkbox" id="showModulesTitles" {{ "checked" if show_modules_titles else "" }} > noms des modules </label> </li> </ul> </div> </div> <form id="calendar_control_form" method="GET"> <input type="hidden" name="current_date" value="{{current_date}}"> <input type="hidden" name="show_modules_titles" value="{{ 1 if show_modules_titles else 0 }}"> <input type="submit" value="Apply Changes" hidden> </div> <div> <span id="menu-navi"> <button type="button" class="btn btn-default btn-sm move-today" data-action="move-today">Aujourd'hui</button> <button type="button" class="btn btn-default btn-sm move-day" data-action="move-prev"> <i class="calendar-icon ic-arrow-line-left" data-action="move-prev"><</i> </button> <button type="button" class="btn btn-default btn-sm move-day" data-action="move-next"> <i class="calendar-icon ic-arrow-line-right" data-action="move-next">></i> </button> </span> <span id="renderRange" class="render-range"></span> <span id="cal_warning">Attention: dates en dehors de ce semestre !</span> </div> <div id="formsemestre-calendar" style="height: calc(100vh - 180px);"></div> <div class="help"> <ul> <li>Les heures sont toujours affichées dans le fuseau horaire du serveur, qui est en principe celui des étudiants. </li> <li>Si vous filtrez par groupe, les évènements dont le groupe n'est pas reconnu seront affichés. </li> {% if formsemestre.can_be_edited_by(current_user) %} <li><a class="stdlink" href="{{ url_for('notes.formsemestre_edt_help_config', scodoc_dept=g.scodoc_dept, formsemestre_id= formsemestre.id) }}">Aide à la configuration de l'emploi du temps</a> {% endif %} </ul> </div> </div> {% endblock app_content %} {% block scripts %} {{ super() }} <script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script> <script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script> <script src="{{scu.STATIC_DIR}}/libjs/tui.calendar/toastui-calendar.min.js"></script> <script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script> <script> let hm_formatter = new Intl.DateTimeFormat('default', { hour: '2-digit', minute: '2-digit', hour12: false }); function getDataAction(target) { return target.dataset ? target.dataset.action : target.getAttribute('data-action'); } function getNextDayISODate(isoDate) { // Parse the ISO date string into a Date object const date = new Date(isoDate); // Add one day date.setDate(date.getDate() + 1); // Convert back to ISO date string (YYYY-MM-DD) return date.toISOString().split('T')[0]; } var calendar; document.addEventListener('DOMContentLoaded', function() { document.getElementById('menu-navi').addEventListener('click', onClickNavi); const Calendar = tui.Calendar; const container = document.getElementById('formsemestre-calendar'); const options = { defaultView: '{{view}}', calendars: [ { id: 'cal1', name: 'Personal', backgroundColor: '#03bd9e', borderColor: 'white', }, ], isReadOnly: true, // timezone: { zones: [ { timezoneName: 'Europe/Paris' } ] }, template: { // ce template nous permet d'avoir du HTML dans le title de l'event time: function(event) { const date_start = new Date(event.start); const start = hm_formatter.format(date_start); return `<strong>${start}</strong> <span>${event.title}</span> <div class="raw-event">${event.raw}</div>`; }, }, timezone: { zones: [ { timezoneName: 'CET', // TODO récupérer timezone serveur }, ], }, usageStatistics: false, week: { dayNames: [ "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"], eventView: ['time'], hourStart: {{ hour_start }}, hourEnd: {{ hour_end }}, showNowIndicator: true, startDayOfWeek: 1, taskView: false, useDetailPopup:false, // on va pouvoir placer les liens scodoc workweek: true, // TODO voir samedi travaillé }, }; calendar = new Calendar(container, options); fetch(`${SCO_URL}/../api/formsemestre/{{formsemestre.id}}/edt?{{groups_query_args|safe}}&show_modules_titles={{show_modules_titles}}`) .then(r=>{return r.json()}) .then(events=>{ if (typeof events == 'string') { // error ! alert("error: " + events) } else { calendar.createEvents(events); } }); {% if current_date %} // we need to add one day because our weeks are starting on Monday calendar.setDate( getNextDayISODate("{{current_date}}") ); {% endif %} changeCalendarDate(); function formatDate(date) { let year = date.getFullYear(); let month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are zero-indexed in JavaScript let day = date.getDate().toString().padStart(2, '0'); return `${day}/${month}/${year}`; } function setRenderRangeText() { var renderRange = document.getElementById('renderRange'); var options = calendar.getOptions(); var viewName = calendar.getViewName(); var html = []; if (viewName === 'day') { html.push(calendar.getDate().toDate().toLocaleString('fr-Fr', { year: 'numeric', month: 'long', day: 'numeric'})); } else if (viewName === 'month' && (!options.month.visibleWeeksCount || options.month.visibleWeeksCount > 4)) { html.push( calendar.getDate().toDate().toLocaleString('fr-Fr', { year: 'numeric', month: 'long', })); } else { html.push(formatDate(calendar.getDateRangeStart())); html.push(' - '); html.push(formatDate(calendar.getDateRangeEnd())); } renderRange.innerHTML = html.join(''); } function onClickNavi(e) { var action = getDataAction(e.target); switch (action) { case 'move-prev': calendar.prev(); break; case 'move-next': calendar.next(); break; case 'move-today': calendar.today(); break; default: return; } // setSchedules(); changeCalendarDate(); } // Update current URL when date change (newDate=ISO string) function updateParametersInUrl(newParams) { // Parse the current URL const currentUrl = new URL(window.location.href); // Access and modify the search parameters const searchParams = currentUrl.searchParams; Object.entries(newParams).forEach(([key, value]) => { searchParams.set(key, value); }); // Create the new URL const newUrl = `${currentUrl.origin}${currentUrl.pathname}?${searchParams.toString()}`; // Update the URL without reloading the page window.history.pushState({ path: newUrl }, '', newUrl); } // Update "current" date (URL and title) function changeCalendarDate() { setRenderRangeText(); // current calendar date, ISO, without time const iso_date_start = calendar.getDateRangeStart().toDate().toISOString().split('T')[0]; const iso_date_end = calendar.getDateRangeEnd().toDate().toISOString().split('T')[0]; const cal_warning = document.querySelector('#cal_warning'); updateParametersInUrl( { current_date: iso_date_start } ); calendar_control_form.current_date.value = iso_date_start; if ((iso_date_start > "{{ formsemestre.date_fin.isoformat() }}") || (iso_date_end < "{{ formsemestre.date_debut.isoformat() }}")) { cal_warning.style.display = 'inline-block'; } else { cal_warning.style.display = 'none'; } } // View menu const dropdown = document.querySelector('.dropdown'); const dropbtn = document.querySelector('.dropbtn'); const dropdownContent = document.querySelector('.dropdown-content'); dropbtn.textContent = "{{views_names[view]}}"; dropbtn.addEventListener('click', function(event) { dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block'; event.stopPropagation(); }); document.querySelectorAll('.dropdown-content a').forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const selectedText = this.textContent; const selectedView = this.getAttribute('data-view'); calendar.changeView(selectedView); // Change the calendar view updateParametersInUrl( { view : selectedView } ); setRenderRangeText(); dropbtn.textContent = selectedText; // Update the button text dropdownContent.style.display = 'none'; }); }); const showModulesTitlesCheckbox = document.getElementById('showModulesTitles'); showModulesTitlesCheckbox.addEventListener('change', function() { calendar_control_form.show_modules_titles.value = (calendar_control_form.show_modules_titles.value=="1") ? "0" : "1"; calendar_control_form.submit(); }); // Close dropdown when clicking outside window.addEventListener('click', function() { if (dropdownContent.style.display === 'block') { dropdownContent.style.display = 'none'; } }); }); </script> {% endblock scripts %}