diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index b4e6c32a5..ba15e0fca 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -56,12 +56,15 @@ from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
from app.scodoc.sco_permissions import Permission
-JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
+JAVASCRIPTS = html_sco_header.BOOTSTRAP_JS + [
"js/etud_info.js",
"js/groups_view.js",
+ "js/multi-select.js",
]
-CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
+CSSSTYLES = html_sco_header.BOOTSTRAP_CSS + [
+ "libjs/bootstrap/css/bootstrap.min.css",
+]
# view:
@@ -115,13 +118,10 @@ def groups_view(
return f"""
{ html_sco_header.sco_header(
javascripts=JAVASCRIPTS,
- cssstyles=CSSSTYLES,
+ cssstyles=CSSSTYLES
)
}
+
+ `;
+
+ this.exportFormat = null;
+ this.observer = new MutationObserver(() => this.render());
+
+ this.toggleDropdown = this.toggleDropdown.bind(this);
+ this.handleDocumentClick = this.handleDocumentClick.bind(this);
+
+ this._internals = this.attachInternals();
+ this._internals.setFormValue([]);
+ }
+
+ connectedCallback() {
+ this.render();
+ this.observer.observe(this, { childList: true, subtree: true });
+ const btn = this.shadowRoot.querySelector(".dropdown-button");
+ btn.addEventListener("click", this.toggleDropdown);
+ document.addEventListener("click", this.handleDocumentClick);
+
+ this._updateSelect();
+ }
+
+ disconnectedCallback() {
+ this.observer.disconnect();
+ document.removeEventListener("click", this.handleDocumentClick);
+ }
+
+ toggleDropdown(event) {
+ event.stopPropagation();
+ const dropdownContent = this.shadowRoot.querySelector(".dropdown-content");
+ dropdownContent.style.display =
+ dropdownContent.style.display === "block" ? "none" : "block";
+ }
+
+ handleDocumentClick(event) {
+ if (!this.contains(event.target)) {
+ this.shadowRoot.querySelector(".dropdown-content").style.display = "none";
+ }
+ }
+
+ render() {
+ const container = this.shadowRoot.querySelector(".multi-select-container");
+ container.innerHTML = "";
+
+ const optgroups = this.querySelectorAll("optgroup");
+
+ optgroups.forEach((optgroup) => {
+ const groupDiv = document.createElement("div");
+ groupDiv.className = "optgroup";
+
+ const groupLabel = document.createElement("div");
+ groupLabel.textContent = optgroup.label;
+ groupDiv.appendChild(groupLabel);
+
+ const options = optgroup.querySelectorAll("option");
+ options.forEach((option) => {
+ const optionDiv = document.createElement("label");
+ optionDiv.className = "option";
+
+ const checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.value = option.value;
+ checkbox.name = this.getAttribute("name");
+ if (option.hasAttribute("selected")) {
+ checkbox.checked = true;
+ optionDiv.classList.add("selected");
+ }
+ checkbox.addEventListener("change", () => {
+ this.handleCheckboxChange(checkbox);
+ });
+
+ optionDiv.appendChild(checkbox);
+ optionDiv.appendChild(document.createTextNode(option.textContent));
+ groupDiv.appendChild(optionDiv);
+ });
+
+ container.appendChild(groupDiv);
+ });
+
+ this._updateSelect();
+ }
+
+ handleCheckboxChange(checkbox) {
+ const opt = this.querySelector(`option[value="${checkbox.value}"]`);
+ const isSingle = opt.hasAttribute("single");
+ if (!checkbox.checked) {
+ checkbox.parentElement.classList.remove("selected");
+ } else {
+ checkbox.parentElement.classList.add("selected");
+ // Gestion de l'option "single"
+ if (isSingle) {
+ // Uncheck all other checkboxes
+ const checkboxes = this.shadowRoot.querySelectorAll(
+ 'input[type="checkbox"]'
+ );
+ checkboxes.forEach((cb) => {
+ if (cb !== checkbox) {
+ cb.checked = false;
+ cb.parentElement.classList.remove("selected");
+ }
+ });
+ } else {
+ // Uncheck the single checkbox if present
+ const singleCheckbox = Array.from(
+ this.shadowRoot.querySelectorAll('input[type="checkbox"]')
+ ).find((cb) =>
+ this.querySelector(`option[value="${cb.value}"]`).hasAttribute(
+ "single"
+ )
+ );
+ if (singleCheckbox) {
+ singleCheckbox.checked = false;
+ singleCheckbox.parentElement.classList.remove("selected");
+ }
+ }
+ }
+ this._updateSelect();
+ }
+
+ _updateSelect() {
+ const checkboxes = this.shadowRoot.querySelectorAll(
+ 'input[type="checkbox"]'
+ );
+ const checkedBoxes = Array.from(checkboxes).filter(
+ (checkbox) => checkbox.checked
+ );
+
+ const values = checkedBoxes.map((checkbox) => checkbox.value);
+
+ const opts = checkedBoxes.map((checkbox) => {
+ return this.querySelector(`option[value="${checkbox.value}"]`);
+ });
+
+ const btn = this.shadowRoot.querySelector(".dropdown-button");
+
+ if (checkedBoxes.length === 0) {
+ btn.textContent = this.label || "Select options";
+ } else if (checkedBoxes.length < 4) {
+ btn.textContent = opts.map((opt) => opt.textContent).join(", ") + "";
+ } else {
+ btn.textContent = `${checkedBoxes.length} sélections`;
+ }
+
+ btn.textContent += " ⮛";
+
+ this._values(values);
+
+ this.dispatchEvent(new Event("change"));
+ }
+
+ _values(newValues = null) {
+ const checkboxes = this.shadowRoot.querySelectorAll(
+ 'input[type="checkbox"]'
+ );
+ if (newValues === null) {
+ // Get selected values
+ const values = Array.from(checkboxes)
+ .filter((checkbox) => checkbox.checked)
+ .map((checkbox) => checkbox.value);
+ if (this.exportFormat) {
+ return this.exportFormat(values);
+ }
+ return values;
+ } else {
+ // Set selected values
+ checkboxes.forEach((checkbox) => {
+ checkbox.checked = newValues.includes(checkbox.value);
+ });
+
+ this._internals.setFormValue(this._values());
+ }
+ }
+
+ get value() {
+ return this._values();
+ }
+
+ set value(values) {
+ this._values(values);
+ }
+
+ on(callback) {
+ this.addEventListener("change", callback);
+ }
+
+ format(callback) {
+ this.exportFormat = callback;
+ }
+}
+
+customElements.define("multi-select", MultiSelect);
diff --git a/app/templates/assiduites/pages/signal_assiduites_group.j2 b/app/templates/assiduites/pages/signal_assiduites_group.j2
index fddae39c1..b939944a7 100644
--- a/app/templates/assiduites/pages/signal_assiduites_group.j2
+++ b/app/templates/assiduites/pages/signal_assiduites_group.j2
@@ -7,7 +7,6 @@
{% block scripts %}
{{ super() }}
-
@@ -73,9 +72,10 @@
creerTousLesEtudiants(etuds);
});
- $("#group_ids_sel").on("change", ()=>{
+ const group_sel = document.querySelector("#group_ids_sel")
+ group_sel.on((values)=>{
main();
- })
+ });
const moduleimpls = {};
const inscriptionsModules = new Map();
@@ -83,7 +83,8 @@
async function main(){
dateCouranteEstTravaillee();
- etuds = await recupEtuds($('#group_ids_sel').val());
+ let group_ids = group_sel.value;
+ etuds = await recupEtuds(group_ids);
if (etuds.size != 0){
await recupAssiduites(etuds, $("#date").datepicker("getDate"));
}
@@ -101,8 +102,7 @@
{% block styles %}
{{ super() }}
-
-
+{# #}
@@ -153,12 +153,12 @@
Groupes : {{grp|safe}}
-
-
+
⇥
@@ -166,8 +166,8 @@
{{timeline|safe}}
- Matin
- Après-Midi
+ Matin
+ Après-Midi
diff --git a/app/templates/babase.j2 b/app/templates/babase.j2
index 2dc3aa89a..94baa48db 100644
--- a/app/templates/babase.j2
+++ b/app/templates/babase.j2
@@ -33,6 +33,7 @@
+
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index 10e092cba..c1d210ab6 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -94,7 +94,7 @@ from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
-CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
+CSSSTYLES = html_sco_header.BOOTSTRAP_CSS
# --------------------------------------------------------------------
@@ -1164,7 +1164,7 @@ def signal_assiduites_group():
formsemestre_date_fin=str(formsemestre.date_fin),
formsemestre_id=formsemestre_id,
gr_tit=gr_tit,
- grp=sco_groups_view.menu_groups_choice(groups_infos),
+ grp=sco_groups_view.menu_groups_choice(groups_infos, html_export=False),
minitimeline=_mini_timeline(),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
nonworkdays=_non_work_days(),
diff --git a/app/views/groups.py b/app/views/groups.py
index ae6bca0e7..4f8e815dc 100644
--- a/app/views/groups.py
+++ b/app/views/groups.py
@@ -45,7 +45,7 @@ def formulaire_feuille_appel():
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
- group_ids: list[int] = request.args.get("group_ids", "").split(",")
+ group_ids: list[int] = request.args.getlist("group_ids")
form: groups_form.FeuilleAppelPreForm = groups_form.FeuilleAppelPreForm(
request.form
@@ -65,7 +65,7 @@ def formulaire_feuille_appel():
"ens": form.ens.data or "",
}
- form_group_ids: list[str] = request.form.getlist("group_ids")
+ form_group_ids: list[str] = request.form.get("group_ids", "").split(",")
if form_group_ids:
groups_infos = DisplayedGroupsInfos(
form_group_ids,
@@ -89,5 +89,5 @@ def formulaire_feuille_appel():
sco_data=ScoData(formsemestre=formsemestre),
form=form,
group_name=groups_infos.groups_titles,
- grp=menu_groups_choice(groups_infos),
+ grp=menu_groups_choice(groups_infos, html_export=False),
)