{% block pageContent %} {% include "assiduites/widgets/alert.j2" %} <div class="pageContent"> {{minitimeline | safe }} <h2>Assiduité de {{sco.etud.nomprenom}}</h2> <div class="options"> <input type="checkbox" id="show_pres" name="show_pres" class="memo"><label for="show_pres">afficher les présences</label> <input type="checkbox" name="show_reta" id="show_reta" class="memo"><label for="show_reta">afficher les retards</label> <input type="checkbox" name="mode_demi" id="mode_demi" class="memo" checked><label for="mode_demi">mode demi journée</label> </div> <div class="calendrier"> </div> <div class="annee"> <span id="label-annee">Année scolaire 2022-2023</span><span id="label-changer" style="margin-left: 5px;">Changer année: </span> <select name="" id="annee" onchange="setterAnnee(this.value)"> </select> <span id="label-nom">Assiduité de {{sco.etud.nomprenom}}</span> </div> <div class="help"> <h3>Calendrier</h3> <p>Code couleur</p> <ul class="couleurs"> <li><span title="Vert" class="present demo"></span> → présence de l'étudiant lors de la période </li> <li><span title="Bleu clair" class="nonwork demo"></span> → la période n'est pas travaillée </li> <li><span title="Rouge" class="absent demo"></span> → absence de l'étudiant lors de la période </li> <li><span title="Rose" class="demo color absent est_just"></span> → absence justifiée </li> <li><span title="Orange" class="retard demo"></span> → retard de l'étudiant lors de la période </li> <li><span title="Jaune clair" class="demo color retard est_just"></span> → retard justifié </li> <li><span title="Quart Bleu" class="est_just demo"></span> → la période est justifiée par un justificatif valide</li> <li><span title="Quart Violet" class="invalide demo"></span> → la période est justifiée par un justificatif non valide / en attente de validation </li> </ul> <p>Vous pouvez passer le curseur sur les jours colorés afin de voir les informations supplémentaires</p> </div> <ul class="couleurs print"> <li><span title="Vert" class="present demo"></span> présence </li> <li><span title="Bleu clair" class="nonwork demo"></span> non travaillé </li> <li><span title="Rouge" class="absent demo"></span> absence </li> <li><span title="Rose" class="demo color absent est_just"></span> absence justifiée </li> <li><span title="Orange" class="retard demo"></span> retard </li> <li><span title="Jaune clair" class="demo color retard est_just"></span>retard justifié </li> <li><span title="Quart Bleu" class="est_just demo"></span> justificatif valide</li> <li><span title="Quart Violet" class="invalide demo"></span> justificatif non valide </li> </ul> </div> <style> .help .couleurs { grid-template-columns: 2; grid-template-rows: auto; display: grid; } .couleurs.print { display: none; } .help .couleurs li:nth-child(odd) { grid-column: 1; list-style-type: none; } .help .couleurs li:nth-child(even) { grid-column: 2; list-style-type: none; } .color.present { background-color: var(--color-present) !important; } .color.absent { background-color: var(--color-absent) !important; } .color.absent.est_just { background-color: var(--color-absent-justi) !important; } .color.retard { background-color: var(--color-retard) !important; } .color.retard.est_just { background-color: var(--color-retard-justi) !important; } .color.nonwork { background-color: var(--color-nonwork) !important; } .color { background-color: var(--color-defaut) !important; } .pageContent { margin-top: 1vh; max-width: var(--sco-content-max-width); } .calendrier { display: flex; justify-content: space-evenly; flex-wrap: wrap; border: 1px solid #444; border-radius: 12px; margin-bottom: 12px; } .month h3 { text-align: center; } .day, .demi .day.color.nonwork { text-align: left; margin: 2px; cursor: default; font-size: 13px; position: relative; font-weight: normal; min-width: 6em; display: flex; justify-content: start; } .color.est_just.sans_etat::before { content: ""; position: absolute; width: 25%; height: 100%; background-color: var(--color-justi) !important; right: 0; } .color.invalide::before { content: ""; position: absolute; width: 25%; height: 100%; right: 0; background-color: var(--color-justi-invalide) !important; } .demo.invalide { background-color: var(--color-justi-invalide) !important; } .demo.est_just { background-color: var(--color-justi) !important; } .demi .day.nonwork>span { flex: none; border: none; } .demi .day { border-radius: 0; } .day .dayline { position: absolute; display: none; bottom: -390%; z-index: 50; width: max-content; height: 75px; background-color: #dedede; border-radius: 15px; padding: 5px; } .day:hover .dayline { display: block; } .dayline .mini-timeline { margin-top: 10%; } .dayline-title { margin: 0; } .dayline .mini_tick { position: absolute; text-align: center; top: 0; transform: translateY(-110%); z-index: 50; } .dayline .mini_tick::after { display: block; content: "|"; position: absolute; bottom: -69%; z-index: 2; transform: translateX(200%); } #label-nom, #label-justi { display: none; } .demi .day { display: flex; justify-content: space-evenly; } .demi .day>span { display: block; flex: 1; text-align: center; z-index: 1; width: 100%; border: 1px solid #d5d5d5; position: relative; } .demi .day>span:first-of-type { width: 3em; min-width: 3em; } .options>* { margin-right: 5px; } .options input { margin-right: 6px; } .options label { font-weight: normal; margin-right: 16px; } @media print { .couleurs.print { display: flex; justify-content: space-evenly; align-items: center; } .couleurs.print li { list-style-type: none !important; -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } .day, .demi .day.color.color.nonwork { min-width: 5em; font-size: 11px; } .demi .day>span:first-of-type { width: 2.5em; min-width: 2.5em; } .color { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } .day.est_just, .demi .day span.est_just { background-image: none; } .day.invalide, .demi .day span.invalide { background-image: none; } .demi .day span.est_just::before { content: "J"; } .demi .day span.invalide::before { content: "JI"; } #sidebar, .help, h2, #annee, #label-changer, .options { display: none; } #label-nom, #label-justi { display: inline; } #gtrcontent { margin: 5px; } .annee { display: flex; justify-content: space-evenly; align-items: center; } } </style> <script> const datePivot = "{{scu.get_assiduites_time_config("pivot")}}".split(":").map((el) => Number(el)) function getDaysBetweenDates(start, end) { let now = new Date(start); end = new Date(end); let dates = []; while (now.isBefore(end) || now.isSame(end)) { dates.push(now.clone()); now.add(1, "days"); } return dates; } function organizeByMonth(dates) { let datesByMonth = {}; dates.forEach((date) => { let month = date.toLocaleString('fr-FR', { month: "short" }); // Obtenir le mois if (!datesByMonth[month]) { datesByMonth[month] = []; } datesByMonth[month].push(date); }); return datesByMonth; } function organizeAssiduitiesByDay(datesByMonth, assiduities, justificatifs) { let assiduitiesByDay = {}; Object.keys(datesByMonth).forEach((month) => { assiduitiesByDay[month] = {}; datesByMonth[month].forEach((date) => { let dayAssiduities = assiduities.filter((assiduity) => { return new Date(date).isBetween( new Date(assiduity.date_debut).startOf("day"), new Date(assiduity.date_fin).endOf("day"), "[]" ) }); let dayJustificatifs = justificatifs.filter((justif) => { return new Date(date).isBetween( new Date(justif.date_debut).startOf("day"), new Date(justif.date_fin).endOf("day"), "[]" ) }); assiduitiesByDay[month][date.toLocaleDateString("en-US")] = { assiduites: dayAssiduities, justificatifs: dayJustificatifs }; }); }); return assiduitiesByDay; } function generateCalendar(assiduitiesByDay, nonWorkdays = []) { // assiduitiesByDay[month][date] avec date au format m/d/y !!! const calendar = document.querySelector('.calendrier') const options = getOptions(); calendar.innerHTML = "" const days = { 1: "Lun", 2: "Mar", 3: "Mer", 4: "Jeu", 5: "Ven", 6: "Sam", 0: "Dim", }; // XXX formats de données très exotiques ! // XXX assiduitiesByDay["oct."]["10/12/2023"] // XXX Object { assiduites: [], justificatifs: [] } Object.keys(assiduitiesByDay).forEach((month) => { const monthEl = document.createElement('div') monthEl.classList.add("month") const title = document.createElement('h3'); title.textContent = `${month.capitalize()}`; monthEl.appendChild(title) const daysEl = document.createElement('div') daysEl.classList.add('days'); if (options.mode_demi) daysEl.classList.add("demi"); Object.keys(assiduitiesByDay[month]).forEach((date) => { let dayAssiduities = assiduitiesByDay[month][date].assiduites; let dayJustificatifs = assiduitiesByDay[month][date].justificatifs; let color = "sans_etat"; if (dayAssiduities.some((a) => a.etat.toLowerCase() === "absent")) color = "absent"; else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta) color = "retard"; else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "present") && options.show_pres) color = "present"; let est_just = "" if (dayJustificatifs.some((j) => j.etat.toLowerCase() === "valide")) { est_just = "est_just"; } else if (dayJustificatifs.some((j) => j.etat.toLowerCase() !== "valide")) { est_just = "invalide"; } const momentDate = new Date(date); let dayOfMonth = momentDate.getDate(); let dayOfWeek = momentDate.getDay(); dayOfWeek = days[dayOfWeek]; let isNonWorkDayVar = nonWorkdays.includes(dayOfWeek.toLowerCase()); const day = document.createElement('div'); day.className = `day`; if (isNonWorkDayVar) { color = "nonwork"; } else if (!options.mode_demi) { day.className = `day ${est_just}`; } if (options.mode_demi && !isNonWorkDayVar) { est_just = [] // affichage n° jour + matin + aprem const span_jour = document.createElement("span") span_jour.textContent = dayOfWeek[0] + dayOfMonth; const span_matin = document.createElement("span"); span_matin.classList.add("color"); const matin = [new Date(date), new Date(date)] color = "sans_etat" matin[0].setHours(0, 0, 0, 0) matin[1].setHours(...datePivot) const assiduitesMatin = dayAssiduities.filter((el) => { const deb = new Date(el.date_debut); const fin = new Date(el.date_fin); return Date.intersect({ deb: deb, fin: fin }, { deb: matin[0], fin: matin[1] }) }) const justificatifsMatin = dayJustificatifs.filter((el) => { const deb = new Date(el.date_debut); const fin = new Date(el.date_fin); return Date.intersect({ deb: deb, fin: fin }, { deb: matin[0], fin: matin[1] }) }) if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "absent")) color = "absent"; else if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta) color = "retard"; else if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "present") && options.show_pres) color = "present"; if (color != "") { span_matin.classList.add(color); } if (justificatifsMatin.some((j) => j.etat.toLowerCase() === "valide")) { est_just = ["est_just"]; } else if (justificatifsMatin.some((j) => j.etat.toLowerCase() !== "valide")) { est_just = ["invalide"]; } span_matin.classList.add(...est_just) est_just = [] const span_aprem = document.createElement("span"); span_aprem.classList.add("color"); const aprem = [new Date(date), new Date(date)] color = "sans_etat" aprem[0].setHours(...datePivot) aprem[0].add(1, "seconds") aprem[1].setHours(23, 59, 59) const assiduitesAprem = dayAssiduities.filter((el) => { const deb = new Date(el.date_debut); const fin = new Date(el.date_fin); return Date.intersect({ deb: deb, fin: fin }, { deb: aprem[0], fin: aprem[1] }) }) const justificatifsAprem = dayJustificatifs.filter((el) => { const deb = new Date(el.date_debut); const fin = new Date(el.date_fin); return Date.intersect({ deb: deb, fin: fin }, { deb: aprem[0], fin: aprem[1] }) }) if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "absent")) color = "absent"; else if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta) color = "retard"; else if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "present") && options.show_pres) color = "present"; if (color != "") { span_aprem.classList.add(color); } if (justificatifsAprem.some((j) => j.etat.toLowerCase() === "valide")) { est_just = ["est_just"]; } else if (justificatifsAprem.some((j) => j.etat.toLowerCase() !== "valide")) { est_just = ["invalide"]; } span_aprem.classList.add(...est_just) day.appendChild(span_jour) day.appendChild(span_matin) day.appendChild(span_aprem) } else { day.classList.add("color") if (color != "") { day.classList.add(color); } if (isNonWorkDayVar) { const span_jour = document.createElement("span") span_jour.textContent = dayOfWeek[0] + dayOfMonth; day.appendChild(span_jour); } else { day.textContent = `${dayOfWeek} ${dayOfMonth}`; } } if (!nonWorkdays.includes(dayOfWeek.toLowerCase()) && dayAssiduities.length > 0) { const cache = document.createElement('div') cache.classList.add('dayline'); const title = document.createElement('div') title.className = "dayline-title"; title.innerHTML = "<span>Assiduité du </span><br>" + `<span>${formatDate(momentDate)}</span>`; cache.appendChild(title) cache.appendChild( createMiniTimeline(dayAssiduities, date) ) day.appendChild(cache) } daysEl.appendChild(day); }); monthEl.appendChild(daysEl) calendar.appendChild(monthEl) }); } function getEtudAssiduites(deb, fin, callback = () => { }) { const url_api = getUrl() + `/api/assiduites/${etudid}/query?date_debut=${deb}&date_fin=${fin}`; async_get(url_api, (data) => { callback(data); }); } function getOptions() { return { "show_pres": document.getElementById("show_pres").checked, "show_reta": document.getElementById("show_reta").checked, "mode_demi": document.getElementById("mode_demi").checked, } } function getEtudJustificatifs(deb, fin) { let list = []; const url_api = getUrl() + `/api/justificatifs/${etudid}/query?date_debut=${deb}&date_fin=${fin}`; sync_get(url_api, (data, status) => { if (status === "success") { list = data; } }); return list } function generate(annee) { if (annee < 1999 || annee > 2999) { openAlertModal("Année impossible", document.createTextNode("L'année demandé n'existe pas.")); return; } const bornes = { deb: `${annee}-09-01T00:00`, fin: `${annee + 1}-08-31T23:59` } let assiduities = getEtudAssiduites(bornes.deb, bornes.fin, (data) => { let dates = getDaysBetweenDates(bornes.deb, bornes.fin); let datesByMonth = organizeByMonth(dates); const justifs = getEtudJustificatifs(bornes.deb, bornes.fin); let assiduitiesByDay = organizeAssiduitiesByDay(datesByMonth, data, justifs); generateCalendar(assiduitiesByDay, nonwork); }); } function setterAnnee(annee) { annee = parseInt(annee); document.querySelector('.annee #label-annee').textContent = `Année scolaire ${annee}-${annee + 1}` generate(annee) } const defAnnee = {{ annee }} let annees = {{ annees | safe }} annees = annees.filter((x, i) => annees.indexOf(x) === i) const etudid = {{ sco.etud.id }}; const nonwork = [{{ nonworkdays | safe }}]; window.onload = () => { const select = document.querySelector('#annee'); annees.forEach((a) => { const opt = document.createElement("option"); opt.value = a + "", opt.textContent = `${a} - ${a + 1}`; if (a === defAnnee) { opt.selected = true; } select.appendChild(opt) }) document.querySelectorAll(".options input").forEach((e) => { e.addEventListener("click", () => { setterAnnee(select.value) }) }) setterAnnee(defAnnee) }; function isCalendrier() { return true } /* --- Mémorisation des checkbox ---- */ document.querySelectorAll('input[type="checkbox"].memo').forEach(checkbox => { checkbox.addEventListener('change', function() { localStorage.setItem(this.id, this.checked); }); // Load the saved state document.querySelectorAll('input[type="checkbox"].memo').forEach(checkbox => { const checked = localStorage.getItem(checkbox.id) === 'true'; checkbox.checked = checked; }); }); </script> {% endblock pageContent %}