Assiduites : dates naives front-end

This commit is contained in:
Iziram 2023-11-20 16:55:26 +01:00
parent 441a893f12
commit 552095b979
17 changed files with 153 additions and 127 deletions

View File

@ -213,6 +213,7 @@ def sco_header(
window.onload=function(){{enableTooltips("gtrcontent")}};
const SCO_URL="{scu.ScoURL()}";
const SCO_TIMEZONE="{scu.TIME_ZONE}";
</script>"""
)

View File

@ -305,8 +305,8 @@ function executeMassActionQueue() {
*/
const tlTimes = getTimeLineTimes();
let assiduite = {
date_debut: tlTimes.deb.toIsoUtcString(),
date_fin: tlTimes.fin.toIsoUtcString(),
date_debut: tlTimes.deb.toFakeIso(),
date_fin: tlTimes.fin.toFakeIso(),
};
assiduite = setModuleImplId(assiduite);
@ -601,7 +601,10 @@ function toTime(time) {
* @returns
*/
function formatDate(date, styles = { dateStyle: "full" }) {
return new Intl.DateTimeFormat("fr-FR", styles).format(date);
return new Intl.DateTimeFormat("fr-FR", {
...{ timeZone: SCO_TIMEZONE },
...styles,
}).format(date);
}
/**
@ -610,19 +613,25 @@ function formatDate(date, styles = { dateStyle: "full" }) {
function updateDate() {
const dateInput = document.querySelector("#tl_date");
let date = $(dateInput).datepicker("getDate");
if (date == null) {
date = new Date(Date.fromFRA(dateInput.value));
}
const intlOptions = {
dateStyle: "full",
timeZone: SCO_TIMEZONE,
};
let dateStr = "";
if (!isNonWorkDay(date.getDay(), nonWorkDays)) {
dateStr = formatDate(date).capitalize();
if (!isNonWorkDay(date, nonWorkDays)) {
dateStr = formatDate(date, intlOptions).capitalize();
} else {
// On se rend au dernier jour travaillé disponible
const lastWorkDay = getNearestWorkDay(date);
const att = document.createTextNode(
`Le jour sélectionné (${formatDate(date)}) n'est pas un jour travaillé.`
`Le jour sélectionné (${formatDate(
date,
intlOptions
)}) n'est pas un jour travaillé.`
);
const div = document.createElement("div");
div.appendChild(att);
@ -630,22 +639,23 @@ function updateDate() {
div.appendChild(
document.createTextNode(
`Le dernier jour travaillé disponible a été sélectionné : ${formatDate(
lastWorkDay
lastWorkDay,
intlOptions
)}.`
)
);
openAlertModal("Attention", div, "", "#eec660");
const date_fra = Date.toFRA(lastWorkDay.toIsoUtcString().split("T")[0]);
$(dateInput).datepicker("setDate", date_fra);
dateInput.value = date_fra;
date = lastWorkDay;
dateStr = formatDate(lastWorkDay).capitalize();
dateStr = formatDate(lastWorkDay, {
dateStyle: "full",
timeZone: SCO_TIMEZONE,
}).capitalize();
}
console.warn(dateStr, date, date.toIsoUtcString());
document.querySelector("#datestr").textContent = dateStr;
return true;
}
@ -654,7 +664,7 @@ function getNearestWorkDay(date) {
const aDay = 86400000; // 24 * 3600 * 1000 | H * s * ms
let day = date;
let count = 0;
while (isNonWorkDay(day.getDay(), nonWorkDays) && count++ < 7) {
while (isNonWorkDay(day, nonWorkDays) && count++ < 7) {
day = new Date(day - aDay);
}
return day;
@ -712,31 +722,12 @@ function formatDateModal(str, separator = " ") {
* Renvoie Vrai si le jour est non travaillé
*/
function isNonWorkDay(day, nonWorkdays) {
let d = "";
switch (day) {
case 0:
d = "dim";
break;
case 1:
d = "lun";
break;
case 2:
d = "mar";
break;
case 3:
d = "mer";
break;
case 4:
d = "jeu";
break;
case 5:
d = "ven";
break;
case 6:
d = "sam";
break;
}
const d = Intl.DateTimeFormat("fr-FR", {
timeZone: SCO_TIMEZONE,
weekday: "short",
})
.format(day)
.replace(".", "");
return nonWorkdays.indexOf(d) != -1;
}
@ -783,8 +774,8 @@ function getTimeLineTimes() {
function isConflictSameAsPeriod(conflict, period = undefined) {
const tlTimes = period == undefined ? getTimeLineTimes() : period;
const clTimes = {
deb: new Date(conflict.date_debut),
fin: new Date(conflict.date_fin),
deb: new Date(Date.removeUTC(conflict.date_debut)),
fin: new Date(Date.removeUTC(conflict.date_fin)),
};
return tlTimes.deb.isSame(clTimes.deb) && tlTimes.fin.isSame(clTimes.fin);
}
@ -850,8 +841,8 @@ function numberTimeToDate(nb) {
function getAssiduitesFromEtuds(clear, deb, fin) {
const etudIds = Object.keys(etuds).join(",");
const date_debut = deb ? deb : getPrevDate().toIsoUtcString();
const date_fin = fin ? fin : getNextDate().toIsoUtcString();
const date_debut = deb ? deb : getPrevDate().toFakeIso();
const date_fin = fin ? fin : getNextDate().toFakeIso();
if (clear) {
assiduites = {};
@ -894,8 +885,8 @@ function getAssiduitesFromEtuds(clear, deb, fin) {
function createAssiduite(etat, etudid) {
const tlTimes = getTimeLineTimes();
let assiduite = {
date_debut: tlTimes.deb.toIsoUtcString(),
date_fin: tlTimes.fin.toIsoUtcString(),
date_debut: tlTimes.deb.toFakeIso(),
date_fin: tlTimes.fin.toFakeIso(),
etat: etat,
};
@ -937,6 +928,19 @@ function createAssiduite(etat, etudid) {
openAlertModal("Sélection du module", content);
}
if (
data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
) {
const HTML = `
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Sélection du module", content);
}
with_errors = true;
}
},
@ -1076,8 +1080,8 @@ function getAssiduitesConflict(etudid, periode) {
return etudAssiduites.filter((assi) => {
const interval = {
deb: new Date(assi.date_debut),
fin: new Date(assi.date_fin),
deb: new Date(Date.removeUTC(assi.date_debut)),
fin: new Date(Date.removeUTC(assi.date_fin)),
};
const test = hasTimeConflict(periode, interval);
return test;
@ -1101,15 +1105,15 @@ function getLastAssiduiteOfPrevDate(etudid) {
const prevAssiduites = etudAssiduites
.filter((assi) => {
const interval = {
deb: new Date(assi.date_debut),
fin: new Date(assi.date_fin),
deb: new Date(Date.removeUTC(assi.date_debut)),
fin: new Date(Date.removeUTC(assi.date_fin)),
};
return hasTimeConflict(period, interval);
})
.sort((a, b) => {
const a_fin = new Date(a.date_fin);
const b_fin = new Date(b.date_fin);
const a_fin = new Date(Date.removeUTC(a.date_fin));
const b_fin = new Date(Date.removeUTC(b.date_fin));
return b_fin < a_fin;
});
@ -1144,8 +1148,8 @@ function getAssiduiteValue(field) {
* @param {String | Number} etudid identifiant de l'étudiant
*/
function actualizeEtudAssiduite(etudid) {
const date_debut = getPrevDate().toIsoUtcString();
const date_fin = getNextDate().toIsoUtcString();
const date_debut = getPrevDate().toFakeIso();
const date_fin = getNextDate().toFakeIso();
const url_api =
getUrl() +
@ -1391,8 +1395,8 @@ function insertEtudRow(etud, index, output = false) {
assiduite.etatAssiduite = conflict[0].etat;
assiduite.id = conflict[0].assiduite_id;
assiduite.date_debut = conflict[0].date_debut;
assiduite.date_fin = conflict[0].date_fin;
assiduite.date_debut = Date.removeUTC(conflict[0].date_debut);
assiduite.date_fin = Date.removeUTC(conflict[0].date_fin);
if (isConflictSameAsPeriod(conflict[0])) {
assiduite.type = "édition";
} else {
@ -1623,9 +1627,7 @@ function getJustificatifFromPeriod(date, etudid, update) {
getUrl() +
`/api/justificatifs/${etudid}/query?date_debut=${date.deb
.add(1, "seconds")
.toIsoUtcString()}&date_fin=${date.fin
.add(-1, "seconds")
.toIsoUtcString()}`,
.toFakeIso()}&date_fin=${date.fin.add(-1, "seconds").toFakeIso()}`,
success: (data) => {
update(data);
},
@ -1657,8 +1659,8 @@ function fastJustify(assiduite) {
}
const period = {
deb: new Date(assiduite.date_debut),
fin: new Date(assiduite.date_fin),
deb: new Date(Date.removeUTC(assiduite.date_debut)),
fin: new Date(Date.removeUTC(assiduite.date_fin)),
};
const action = (justifs) => {
//créer un nouveau justificatif
@ -1671,8 +1673,8 @@ function fastJustify(assiduite) {
//créer justificatif
const justif = {
date_debut: new Date(assiduite.date_debut).toIsoUtcString(),
date_fin: new Date(assiduite.date_fin).toIsoUtcString(),
date_debut: new Date(Date.removeUTC(assiduite.date_debut)).toFakeIso(),
date_fin: new Date(Date.removeUTC(assiduite.date_fin)).toFakeIso(),
raison: raison,
etat: etat,
};

View File

@ -59,6 +59,11 @@ Date.intersect = function (period, interval) {
return period.deb <= interval.fin && period.fin >= interval.deb;
};
Date.removeUTC = function (isoString) {
const reg = new RegExp(/[+-][\d:]+$/);
return isoString.replace(reg, "");
};
Object.defineProperty(Date.prototype, "isValid", {
value: function () {
return !Number.isNaN(this.getTime());
@ -198,13 +203,29 @@ Object.defineProperty(Date.prototype, "toIsoUtcString", {
* @returns date au format iso utc (yyyy-mm-ddThh:MM±oo:oo:oo)
*/
value: function () {
// Formater la date et l'heure
const date = this;
var tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? "+" : "-",
pad = function (num) {
return (num < 10 ? "0" : "") + num;
};
return (
this.toFakeIso() +
dif +
pad(Math.floor(Math.abs(tzo) / 60)) +
":" +
pad(Math.abs(tzo) % 60)
);
},
});
Object.defineProperty(Date.prototype, "toFakeIso", {
value: function () {
const date = this;
pad = function (num) {
return (num < 10 ? "0" : "") + num;
};
return (
date.getFullYear() +
"-" +
@ -216,11 +237,7 @@ Object.defineProperty(Date.prototype, "toIsoUtcString", {
":" +
pad(date.getMinutes()) +
":" +
pad(date.getSeconds()) +
dif +
pad(Math.floor(Math.abs(tzo) / 60)) +
":" +
pad(Math.abs(tzo) % 60)
pad(date.getSeconds())
);
},
});
@ -246,6 +263,7 @@ Object.defineProperty(Date.prototype, "format", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: SCO_TIMEZONE,
});
case "DD/MM/YYYY HH:mm":
return this.toLocaleString("fr-FR", {
@ -255,6 +273,7 @@ Object.defineProperty(Date.prototype, "format", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: SCO_TIMEZONE,
});
case "YYYY-MM-DDTHH:mm":
@ -264,7 +283,7 @@ Object.defineProperty(Date.prototype, "format", {
case "YYYY-MM-DD":
return iso.slice(0, iso.indexOf("T"));
default:
return this.toIsoUtcString();
return this.toFakeIso();
}
},
});

View File

@ -103,7 +103,7 @@
[required]::after {
content: "*";
color: crimson;
color: var(--color-error);
}
</style>
<script>
@ -137,8 +137,8 @@
const raison = field.querySelector('#justi_raison').value;
return {
date_debut: new Date(deb).toIsoUtcString(),
date_fin: new Date(fin).toIsoUtcString(),
date_debut: new Date(deb).toFakeIso(),
date_fin: new Date(fin).toFakeIso(),
etat: etat,
raison: raison,
}

View File

@ -167,7 +167,7 @@
margin: 5px 0;
border-radius: 7px;
background-color: crimson;
background-color: var(--color-error);
}

View File

@ -101,7 +101,7 @@
openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.'));
return;
}
countAssiduites(date_debut.toIsoUtcString(), date_fin.toIsoUtcString())
countAssiduites(date_debut.toFakeIso(), date_fin.toFakeIso())
}
@ -366,7 +366,7 @@
margin: 5px 0;
border-radius: 7px;
background-color: crimson;
background-color: var(--color-error);
}

View File

@ -453,17 +453,17 @@
let dayOfWeek = momentDate.getDay();
dayOfWeek = days[dayOfWeek];
let isNonWorkDay = nonWorkdays.includes(dayOfWeek.toLowerCase());
let isNonWorkDayVar = nonWorkdays.includes(dayOfWeek.toLowerCase());
const day = document.createElement('div');
day.className = `day`;
if (isNonWorkDay) {
if (isNonWorkDayVar) {
color = "nonwork";
} else if (!options.mode_demi) {
day.className = `day ${est_just}`;
}
if (options.mode_demi && !isNonWorkDay) {
if (options.mode_demi && !isNonWorkDayVar) {
est_just = []
// affichage n° jour + matin + aprem
@ -560,7 +560,7 @@
day.classList.add(color);
}
if (isNonWorkDay) {
if (isNonWorkDayVar) {
const span_jour = document.createElement("span")
span_jour.textContent = dayOfWeek[0] + dayOfMonth;
day.appendChild(span_jour);
@ -570,7 +570,6 @@
}
console.warn(day.classList, day.classList.length)
if (!nonWorkdays.includes(dayOfWeek.toLowerCase()) && dayAssiduities.length > 0) {

View File

@ -41,7 +41,7 @@
if (defaultDates != null) {
defaultDates.forEach((dateString) => {
d = new Date(dateString).getDay();
d = new Date(dateString);
if (isNonWorkDay(d, nonWorkDays)) return;

View File

@ -105,13 +105,13 @@
updateJustifyBtn();
});
window.addEventListener("DOMContentLoaded", () => {
updateDate();
getSingleEtud({{ sco.etud.id }});
actualizeEtud({{ sco.etud.id }});
getSingleEtud(etudid);
actualizeEtud(etudid);
updateSelect()
updateJustifyBtn();
})
function setTimeLineTimes(a, b) {

View File

@ -270,14 +270,14 @@
) {
const assiduite_avant = {
etat: this.selectedAssiduite.etat,
date_debut: assiduite_debut.toIsoUtcString()(),
date_fin: separtorDate.toIsoUtcString()(),
date_debut: assiduite_debut.toFakeIso()(),
date_fin: separtorDate.toFakeIso()(),
};
const assiduite_apres = {
etat: this.selectedAssiduite.etat,
date_debut: separtorDate.toIsoUtcString()(),
date_fin: assiduite_fin.toIsoUtcString()(),
date_debut: separtorDate.toFakeIso()(),
date_fin: assiduite_fin.toFakeIso()(),
};
if (this.selectedAssiduite.moduleimpl_id) {

View File

@ -285,10 +285,10 @@
let currentDate = "{{date}}";
if (currentDate == "") {
currentDate = new Date();
currentDate = currentDate.format("YYYY-MM-DDTHH:mm");
currentDate = currentDate.toFakeIso();
} else {
currentDate = new Date(currentDate);
currentDate = currentDate.format("YYYY-MM-DDTHH:mm");
currentDate = currentDate.toFakeIso();
}
@ -439,7 +439,7 @@
date = new Date(currentDate);
const start = typeof mt_start !== 'undefined' ? mt_start : 0
date.setHours(start);
date = date.format("YYYY-MM-DDTHH:mm")
date = date.toFakeIso()
} else {
date = last.value;
}
@ -555,14 +555,14 @@
}
if (get) {
getAssiduitesFromEtuds(false, d_debut.toIsoUtcString(), d_fin.toIsoUtcString())
getAssiduitesFromEtuds(false, d_debut.toFakeIso(), d_fin.toFakeIso())
return 0x0;
}
return {
moduleimpl: moduleSelect,
deb: d_debut.toIsoUtcString(),
fin: d_fin.toIsoUtcString(),
deb: d_debut.toFakeIso(),
fin: d_fin.toFakeIso(),
}
@ -653,6 +653,10 @@
colIds.forEach((colid) => {
getAndUpdateCol(colid)
})
if (isSingleEtud()) {
actualizeEtud(etudid);
}
}
function launchToast(etudid, etat) {

View File

@ -13,14 +13,13 @@
*/
function createMiniTimeline(assiduitesArray, day = null) {
const array = [...assiduitesArray];
const dateiso = day == null ? getDate().format("YYYY-MM-DD") : day;
const date = day == null ? getDate() : new Date(day);
const timeline = document.createElement("div");
timeline.className = "mini-timeline";
if (isSingleEtud()) {
timeline.classList.add("single");
}
const timelineDate = new Date(dateiso).startOf("day");
const timelineDate = date.startOf("day");
const dayStart = timelineDate.clone().add(mt_start, "hours");
const dayEnd = timelineDate.clone().add(mt_end, "hours");
const dayDuration = new Duration(dayStart, dayEnd).minutes;
@ -36,12 +35,12 @@
date_fin: tlTimes.fin.format(),
etat: "CRENEAU",
});
}
array.forEach((assiduité) => {
let startDate = new Date(assiduité.date_debut);
let endDate = new Date(assiduité.date_fin);
let startDate = new Date(Date.removeUTC(assiduité.date_debut));
let endDate = new Date(Date.removeUTC(assiduité.date_fin));
if (startDate.isBefore(dayStart)) {
startDate = dayEnd.clone().startOf("day").add(mt_start, "hours");
}
@ -52,11 +51,12 @@
const block = document.createElement("div");
block.className = "mini-timeline-block";
const duration = new Duration(startDate, endDate).minutes;
const startOffset = new Duration(dayStart, startDate).minutes;
const duration = (new Duration(startDate, endDate)).minutes;
const startOffset = (new Duration(dayStart, startDate)).minutes;
const leftPercentage = (startOffset / dayDuration) * 100;
const widthPercentage = (duration / dayDuration) * 100;
block.style.left = `${leftPercentage}%`;
block.style.width = `${widthPercentage}%`;

View File

@ -21,9 +21,9 @@
}
function filterFormSemestres(semestres, dateIso) {
const date = new Date(dateIso);
const date = new Date(Date.removeUTC(dateIso));
semestres = semestres.filter((fm) => {
return date.isBetween(new Date(fm.date_debut_iso), new Date(fm.date_fin_iso), '[]');
return date.isBetween(new Date(Date.removeUTC(fm.date_debut_iso)), new Date(Date.removeUTC(fm.date_fin_iso)), '[]');
})
return semestres;

View File

@ -83,7 +83,7 @@
filterAssiduites.columns.forEach((k) => {
const td = document.createElement('td');
if (k.indexOf('date') != -1) {
td.textContent = new Date(assiduite[k]).format(`DD/MM/Y HH:mm`)
td.textContent = new Date(Date.removeUTC(assiduite[k])).format(`DD/MM/Y HH:mm`)
} else if (k.indexOf("module") != -1) {
td.textContent = getModuleImpl(assiduite);
} else if (k.indexOf('est_just') != -1) {
@ -115,9 +115,9 @@
const user = getUser(data);
const module = getModuleImpl(data);
const date_debut = new Date(data.date_debut).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(data.date_fin).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(data.entry_date).format("DD/MM/YYYY HH:mm");
const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm");
const etat = data.etat.capitalize();
const desc = data.desc == null ? "" : data.desc;
@ -433,7 +433,7 @@
if (l.querySelector(`#${key}_time`).value != "") {
filterAssiduites.filters[key] = {
pref: pref,
time: new Date(time)
time: new Date(Date.removeUTC(time))
}
}
} else if (key.indexOf('etat') != -1) {

View File

@ -75,7 +75,7 @@
}
}
if (k.indexOf('date') != -1) {
const assi_time = new Date(el[k]);
const assi_time = new Date(Date.removeUTC(el[k]));
const filter_time = f[k].time;
switch (f[k].pref) {
@ -262,8 +262,8 @@
let keyValueB = b[keyword];
if (keyword.indexOf("date") != -1) {
keyValueA = new Date(keyValueA)
keyValueB = new Date(keyValueB)
keyValueA = new Date(Date.removeUTC(keyValueA))
keyValueB = new Date(Date.removeUTC(keyValueB))
}
if (keyword.indexOf("module") != -1) {

View File

@ -82,7 +82,7 @@
filterJustificatifs.columns.forEach((k) => {
const td = document.createElement('td');
if (k.indexOf('date') != -1) {
td.textContent = new Date(justificatif[k]).format(`DD/MM/Y HH:mm`)
td.textContent = new Date(Date.removeUTC(justificatif[k])).format(`DD/MM/Y HH:mm`)
} else if (k.indexOf('fichier') != -1) {
td.textContent = justificatif.fichier ? "Oui" : "Non";
} else if (k.indexOf('etudid') != -1) {
@ -120,9 +120,9 @@
path,
(data) => {
const user = getUser(data);
const date_debut = new Date(data.date_debut).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(data.date_fin).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(data.entry_date).format("DD/MM/YYYY HH:mm");
const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm");
const etat = data.etat.capitalize();
const desc = data.raison == null ? "" : data.raison;
@ -305,8 +305,8 @@
assiEdit.querySelector('#justi_etat').value = data.etat.toLowerCase();
assiEdit.querySelector('#justi_raison').value = desc;
const d_deb = new Date(data.date_debut).format("YYYY-MM-DDTHH:mm")
const d_fin = new Date(data.date_fin).format("YYYY-MM-DDTHH:mm")
const d_deb = new Date(Date.removeUTC(data.date_debut)).format("YYYY-MM-DDTHH:mm")
const d_fin = new Date(Date.removeUTC(data.date_fin)).format("YYYY-MM-DDTHH:mm")
assiEdit.querySelector('#justi_date_debut').value = d_deb
assiEdit.querySelector('#justi_date_fin').value = d_fin
@ -356,8 +356,8 @@
openAlertModal("Dates erronées", document.createTextNode('Les dates sont invalides'));
return true
}
date_debut = new Date(date_debut)
date_fin = new Date(date_fin)
date_debut = new Date(Date.removeUTC(date_debut))
date_fin = new Date(Date.removeUTC(date_fin))
if (date_debut >= date_fin) {
openAlertModal("Dates erronées", document.createTextNode('La date de fin doit être après la date de début'));
@ -625,7 +625,7 @@
if (l.querySelector(`#${key}_time`).value != "") {
filterJustificatifs.filters[key] = {
pref: pref,
time: new Date(time)
time: new Date(Date.removeUTC(time))
}
}
} else if (key.indexOf('etat') != -1) {

View File

@ -72,7 +72,7 @@
</div>
<script>
setTimeout(function() {
setTimeout(function () {
var flashes = document.getElementsByClassName("flashes")[0];
if (flashes) {
flashes.style.display = "none";
@ -93,5 +93,6 @@
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
<script>
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
const SCO_TIMEZONE = {{ scu.TIME_ZONE }}
</script>
{% endblock %}