diff --git a/data/test.db b/data/test.db index 7be481e590a39fba8ed2fbafb68d5dce01950b3c..f0fc7e7d6d6501152a1221e211ccefa82a4ddc90 100644 Binary files a/data/test.db and b/data/test.db differ diff --git a/web/components/management/area.js b/web/components/management/area.js new file mode 100644 index 0000000000000000000000000000000000000000..f13e766db58fb963e0b0160fe75f420f1c5ae9f7 --- /dev/null +++ b/web/components/management/area.js @@ -0,0 +1,332 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Common from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import { Delete } from "/services/common/delete.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where, parent) { + const areaComponent = new Area(parent); + await areaComponent.mount(where); + return areaComponent; +} + +class Area { + constructor(parent) { + this.parent = parent; + } + + async mount(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <header class="card-header"> + <p class="card-header-title" id="area-title"> + Circonscriptions + </p> + <button id="area-new" class="button is-success" disabled> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </header> + <div class="card-content"> + <div id="area-list" class="content"> + Sélectionner une élection. + </div> + </div> + `; + current_user = await Auth.GetUser(); + this.mountModal("area-modal"); + this.handleDom(); + } + + handleDom() { + let areaHandler = this; + document.getElementById(`area-new`).addEventListener("click", function () { + areaHandler.newArea(areaHandler.election); + }); + + document + .getElementById(`area-modal-close`) + .addEventListener("click", function () { + Common.toggleModal("area-modal", "area-modal-card"); + }); + document + .getElementById(`area-modal-cancel`) + .addEventListener("click", function () { + Common.toggleModal("area-modal", "area-modal-card"); + }); + document + .getElementById(`area-modal-save`) + .addEventListener("click", async function () { + await areaHandler.saveArea(); + }); + document + .getElementById(`area-modal-save-area`) + .addEventListener("click", async function () { + await areaHandler.saveArea(); + await new Promise((r) => setTimeout(r, 800)); + areaHandler.newArea(areaHandler.election); + }); + document + .getElementById(`area-modal-save-section`) + .addEventListener("click", async function () { + let area = await areaHandler.saveArea(); + await new Promise((r) => setTimeout(r, 800)); + areaHandler.parent.sectionHandler.newSection(area); + }); + } + + mountModal(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="area-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title"> + Ajout/modification d'une circonscription + </p> + <button + class="delete" + aria-label="close" + id="area-modal-close" + ></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input class="input" type="number" id="area-modal-id" disabled /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="area-modal-name" /> + </div> + </div> + <div class="field"> + <label>Nombre de siège</label> + <div class="control"> + <input class="input" type="number" id="area-modal-seat-number" /> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="area-modal-save" class="button is-success"> + Sauvegarder + </button> + <button id="area-modal-save-area" class="button is-success"> + Sauvegarder <br /> + (ajouter circonscription) + </button> + <button id="area-modal-save-section" class="button is-success"> + Sauvegarder <br />(ajouter ville) + </button> + <button id="area-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + } + + async displayAreas() { + let areaHandler = this; + let areas = await this.updateAreas(); + const markup = areas.map((area) => this.areaTemplate(area)).join(""); + document.getElementById("area-list").innerHTML = markup; + document.getElementById("area-new").removeAttribute("disabled"); + document.getElementById("area-title").innerHTML = + "Circonscriptions de <em> " + this.election.Name + "</em>"; + + areas.map((area) => { + document + .getElementById(`areas-area-edit-${area.ID}`) + .addEventListener("click", function () { + areaHandler.editArea(area); + }); + document + .getElementById(`areas-area-delete-${area.ID}`) + .addEventListener("click", function () { + new Delete(() => { + areaHandler.deleteArea(area); + }); + }); + document + .getElementById(`areas-area-${area.ID}`) + .addEventListener("click", function () { + areaHandler.activateArea(area); + areaHandler.parent.sectionHandler.area = area; + areaHandler.parent.sectionHandler.displaySections(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + document + .getElementById("section-new") + .setAttribute("disabled", "true"); + }); + }); + } + + emptyAreas() { + this.election = null; + document.getElementById("area-title").innerHTML = "Circonscriptions"; + document.getElementById("area-list").innerHTML = + "Veuillez sélectionner une élection"; + } + + areaTemplate(area) { + return /* HTML */ `<div class="card card-list"> + <div id="areas-area-${area.ID}" class="card-content"> + <div class="content"> + <nav class="level"> + <div class="level-left"> + ${area.Name} (Nombre de siège : ${area.SeatNumber}) + </div> + <div class="level-right"> + <a + id="areas-area-edit-${area.ID}" + class="button is-link is-small" + title="Modifier" + > + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a + id="areas-area-delete-${area.ID}" + class="button is-danger is-small" + title="Supprimer" + > + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </div> + </nav> + </div> + </div> + </div>`; + } + + async activateArea(area) { + this.parent.sectionHandler.emptySections(); + this.parent.deskHandler.emptyDesks(); + let areas = await this.updateAreas(); + areas.forEach((area) => { + document + .getElementById(`areas-area-${area.ID}`) + .classList.remove("active-card"); + }); + document + .getElementById(`areas-area-${area.ID}`) + .classList.add("active-card"); + } + + newArea(election) { + this.method = "POST"; + this.election = election; + document.getElementById("area-modal-id").value = null; + document.getElementById("area-modal-name").value = election.Name; + document.getElementById("area-modal-seat-number").value = null; + Common.toggleModal("area-modal", "area-modal-card"); + } + + editArea(area) { + this.method = "PUT"; + document.getElementById("area-modal-id").value = area.ID; + document.getElementById("area-modal-name").value = area.Name; + document.getElementById("area-modal-seat-number").value = area.SeatNumber; + Common.toggleModal("area-modal", "area-modal-card"); + } + + async saveArea() { + let area; + if (this.method == "POST") + document.getElementById("area-modal-id").value = null; + + try { + const response = await fetch( + "/api/Area/" + document.getElementById("area-modal-id").value, + { + method: this.method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(document.getElementById("area-modal-id").value), + ElectionID: this.election.ID, + Name: document.getElementById("area-modal-name").value, + SeatNumber: parseInt( + document.getElementById("area-modal-seat-number").value + ), + }), + } + ); + if (response.status !== 200) { + throw new Error( + `Area could not be updated or created (status ${response.status})` + ); + } + area = await response.json(); + await this.displayAreas(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Common.toggleModal("area-modal", "area-modal-card"); + this.activateArea(area); + this.parent.sectionHandler.area = area; + this.parent.sectionHandler.displaySections(); + return area; + } + + async updateAreas() { + let areaHandler = this; + try { + const response = await fetch("/api/Area/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Areas could not be fetched (status ${response.status})` + ); + } + let areas = await response.json(); + return areas.filter(function (area) { + return area.ElectionID == areaHandler.election.ID; + }); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async deleteArea(area) { + try { + const response = await fetch("/api/Area/" + area.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Area could not be deleted (status ${response.status})` + ); + } + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + this.displayAreas(); + this.parent.sectionHandler.emptySections(); + this.parent.deskHandler.emptyDesks(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + document.getElementById("section-new").setAttribute("disabled", "true"); + } +} diff --git a/web/components/management/desk.js b/web/components/management/desk.js new file mode 100644 index 0000000000000000000000000000000000000000..c009bde58e089e7c4b0d95dff20c2526ae9b4145 --- /dev/null +++ b/web/components/management/desk.js @@ -0,0 +1,297 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Common from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import { Delete } from "/services/common/delete.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where) { + const deskComponent = new Desk(); + await deskComponent.mount(where); + return deskComponent; +} + +class Desk { + constructor() {} + + async mount(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <header class="card-header"> + <p class="card-header-title" id="desk-title"> + Bureaux + </p> + <button id="desk-new" class="button is-success" disabled> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </header> + <div class="card-content"> + <div id="desk-list" class="content"> + Sélectionner une section. + </div> + </div> + `; + current_user = await Auth.GetUser(); + this.mountModal("desk-modal"); + this.handleDom(); + } + + handleDom() { + let deskHandler = this; + document.getElementById(`desk-new`).addEventListener("click", function () { + deskHandler.newDesk(deskHandler.section) + }); + + document + .getElementById(`desk-modal-close`) + .addEventListener("click", function () { + Common.toggleModal("desk-modal", "desk-modal-card"); + }); + document + .getElementById(`desk-modal-cancel`) + .addEventListener("click", function () { + Common.toggleModal("desk-modal", "desk-modal-card"); + }); + document + .getElementById(`desk-modal-save`) + .addEventListener("click", async function () { + await deskHandler.saveDesk(); + }); + document + .getElementById(`desk-modal-save-desk`) + .addEventListener("click", async function () { + await deskHandler.saveDesk(); + await new Promise((r) => setTimeout(r, 800)); + deskHandler.newDesk(deskHandler.section); + }); + } + + mountModal(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="desk-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title"> + Ajout/modification d'un bureau + </p> + <button + class="delete" + aria-label="close" + id="desk-modal-close" + ></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input class="input" type="number" id="desk-modal-id" disabled /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="desk-modal-name" /> + </div> + </div> + <div class="field"> + <label>Subscribed</label> + <div class="control"> + <input class="input" type="number" id="desk-modal-subscribed" /> + </div> + </div> + <div class="field"> + <div class="control"> + <label class="checkbox"> + <input class="input" type="checkbox" id="desk-modal-witness" /> + Bureau témoin + </label> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="desk-modal-save" class="button is-success"> + Sauvegarder + </button> + <button id="desk-modal-save-desk" class="button is-success"> + Sauvegarder (ajouter bureau) + </button> + <button id="desk-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + } + + async displayDesks() { + let deskHandler = this; + let desks = await this.updateDesks(); + const markup = desks.map((section) => this.deskTemplate(section)).join(""); + document.getElementById("desk-list").innerHTML = markup; + document.getElementById("desk-new").removeAttribute("disabled"); + document.getElementById("desk-title").innerHTML = + "Bureaux de <em> " + this.section.Name + "</em>"; + + desks.map((desk) => { + document + .getElementById(`desks-desk-edit-${desk.ID}`) + .addEventListener("click", function () { + deskHandler.editSection(desk); + }); + document + .getElementById(`desks-desk-delete-${desk.ID}`) + .addEventListener("click", function () { + new Delete(() => { + deskHandler.deleteDesk(desk); + }); + }); + }); + } + + emptyDesks() { + document.getElementById("desk-title").innerHTML = + "Bureaux"; + this.section = null; + document.getElementById("desk-list").innerHTML = "Veuillez sélectionner une section"; + } + + deskTemplate(desk) { + return /* HTML */ `<div class="card card-list"> + <div class="card-content"> + <div class="content"> + <nav class="level"> + <div class="level-left"> + ${desk.Name} (Inscrits : ${desk.Subscribed}) + </div> + <div class="level-right"> + <a + id="desks-desk-edit-${desk.ID}" + class="button is-link is-small" + title="Modifier" + > + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a + id="desks-desk-delete-${desk.ID}" + class="button is-danger is-small" + title="Supprimer" + > + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </div> + </nav> + </div> + </div> + </div>`; + } + + newDesk(section) { + this.method = "POST"; + this.section = section; + document.getElementById("desk-modal-id").value = null; + document.getElementById("desk-modal-name").value = section.Name; + document.getElementById("desk-modal-subscribed").value = section.Subscribed; + document.getElementById("desk-modal-witness").checked = section.WitnessDesk; + Common.toggleModal("desk-modal", "desk-modal-card"); + } + + editSection(section) { + this.method = "PUT"; + document.getElementById("desk-modal-id").value = section.ID; + document.getElementById("desk-modal-name").value = section.Name; + document.getElementById("desk-modal-subscribed").value = section.Subscribed; + document.getElementById("desk-modal-witness").checked = section.WitnessDesk; + Common.toggleModal("desk-modal", "desk-modal-card"); + } + + async saveDesk() { + let desk; + if (this.method == "POST") + document.getElementById("desk-modal-id").value = null; + + try { + const response = await fetch( + "/api/Desk/" + document.getElementById("desk-modal-id").value, + { + method: this.method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(document.getElementById("desk-modal-id").value), + SectionID: this.section.ID, + Name: document.getElementById("desk-modal-name").value, + Subscribed: parseInt( + document.getElementById("desk-modal-subscribed").value + ), + WitnessDesk: document.getElementById("desk-modal-witness").checked, + }), + } + ); + if (response.status !== 200) { + throw new Error( + `Desk could not be updated or created (status ${response.status})` + ); + } + desk = await response.json(); + await this.displayDesks(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Common.toggleModal("desk-modal", "desk-modal-card"); + } + + async updateDesks() { + let deskHandler = this; + try { + const response = await fetch("/api/Desk/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Desks could not be fetched (status ${response.status})` + ); + } + let desks = await response.json(); + return desks.filter(function (desk) { + return desk.SectionID == deskHandler.section.ID; + }); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async deleteDesk(desk) { + try { + const response = await fetch("/api/Desk/" + desk.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Desk could not be deleted (status ${response.status})` + ); + } + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + this.displayDesks(); + } +} diff --git a/web/components/management/election.js b/web/components/management/election.js index c7eaf2293d6c5d6f25de1df9b5223e3da6d271ce..a7b7f3e0a533eeeede5a4bdf29b26e4f4f26be14 100644 --- a/web/components/management/election.js +++ b/web/components/management/election.js @@ -1,24 +1,322 @@ // Imports import * as Auth from "/services/auth/auth.js"; +import * as Common from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import { Delete } from "/services/common/delete.js"; // DOM elements // local variables let current_user; -export async function mount(where) { - const roundComponent = new Election(); - await roundComponent.mount(where); +export async function mount(where, parent) { + const electionComponent = new Election(parent); + await electionComponent.mount(where); + return electionComponent; } class Election { - constructor() {} + constructor(parent) { + this.method = null; + this.parent = parent; + } async mount(where) { const mountpoint = where; document.getElementById(mountpoint).innerHTML = /* HTML */ ` - Ceci est une élection + <header class="card-header"> + <p class="card-header-title"> + Elections + </p> + <button id="election-new" class="button is-success"> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </header> + <div class="card-content"> + <div id="election-list" class="content">Liste des élections</div> + </div> `; current_user = await Auth.GetUser(); + this.mountModal("election-modal"); + this.handleDom(); + this.displayElections(); + } + + handleDom() { + let electionHandler = this; + document + .getElementById(`election-new`) + .addEventListener("click", function () { + electionHandler.newElection(); + }); + + document + .getElementById(`election-modal-close`) + .addEventListener("click", function () { + Common.toggleModal("election-modal", "election-modal-card"); + }); + document + .getElementById(`election-modal-cancel`) + .addEventListener("click", function () { + Common.toggleModal("election-modal", "election-modal-card"); + }); + document + .getElementById(`election-modal-save`) + .addEventListener("click", async function () { + await electionHandler.saveElection(); + }); + document + .getElementById(`election-modal-save-area`) + .addEventListener("click", async function () { + let election = await electionHandler.saveElection(); + electionHandler.parent.areaHandler.newArea(election); + }); + } + + mountModal(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="election-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Ajout/modification d'une élection</p> + <button + class="delete" + aria-label="close" + id="election-modal-close" + ></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input + class="input" + type="number" + id="election-modal-id" + disabled + /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="election-modal-name" /> + </div> + </div> + <div class="field"> + <label>Système de vote</label><br /> + <div class="control select"> + <select name="ballot-type" id="election-modal-ballot-type"> + <option value="local-counsil-direct" + >Conseil communautaire au suffrage direct</option + > + <option value="local-counsil-indirect" + >Conseil communautaire au suffrage indirect</option + > + </select> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="election-modal-save" class="button is-success"> + Sauvegarder + </button> + <button id="election-modal-save-area" class="button is-success"> + Sauvegarder (ajouter circonscription) + </button> + <button id="election-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + } + + async displayElections() { + let elections = await this.updateElections(); + const markup = elections + .map((election) => this.electionTemplate(election)) + .join(""); + document.getElementById("election-list").innerHTML = markup; + + let electionHandler = this; + elections.map((election) => { + document + .getElementById(`elections-election-edit-${election.ID}`) + .addEventListener("click", function () { + electionHandler.editElection(election); + }); + document + .getElementById(`elections-election-delete-${election.ID}`) + .addEventListener("click", function () { + new Delete(() => { + electionHandler.deleteElection(election); + }); + }); + document + .getElementById(`elections-election-${election.ID}`) + .addEventListener("click", function () { + electionHandler.activateElection(election); + electionHandler.parent.areaHandler.election = election; + electionHandler.parent.areaHandler.displayAreas(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + document + .getElementById("section-new") + .setAttribute("disabled", "true"); + document.getElementById("area-new").setAttribute("disabled", "true"); + }); + }); + } + + electionTemplate(election) { + return /* HTML */ `<div class="card card-list"> + <div id="elections-election-${election.ID}" class="card-content"> + <div class="content"> + <nav class="level"> + <div class="level-left"> + ${election.Name} (${election.BallotType}) + </div> + <div class="level-right"> + <a + id="elections-election-edit-${election.ID}" + class="button is-link is-small" + title="Modifier" + > + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a + id="elections-election-delete-${election.ID}" + class="button is-danger is-small" + title="Supprimer" + > + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </div> + </nav> + </div> + </div> + </div>`; + } + + async activateElection(election) { + this.parent.areaHandler.emptyAreas(); + this.parent.sectionHandler.emptySections(); + this.parent.deskHandler.emptyDesks(); + let elections = await this.updateElections(); + elections.forEach((election) => { + document + .getElementById(`elections-election-${election.ID}`) + .classList.remove("active-card"); + }); + document + .getElementById(`elections-election-${election.ID}`) + .classList.add("active-card"); + } + + newElection() { + this.method = "POST"; + document.getElementById("election-modal-id").value = null; + document.getElementById("election-modal-name").value = null; + document.getElementById("election-modal-ballot-type").value = null; + Common.toggleModal("election-modal", "election-modal-card"); + } + + editElection(election) { + this.method = "PUT"; + document.getElementById("election-modal-id").value = election.ID; + document.getElementById("election-modal-name").value = election.Name; + document.getElementById("election-modal-ballot-type").value = + election.BallotType; + Common.toggleModal("election-modal", "election-modal-card"); + } + + async saveElection() { + let election; + if (this.method == "POST") + document.getElementById("election-modal-id").value = null; + + try { + const response = await fetch( + "/api/Election/" + document.getElementById("election-modal-id").value, + { + method: this.method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(document.getElementById("election-modal-id").value), + Name: document.getElementById("election-modal-name").value, + BallotType: document.getElementById("election-modal-ballot-type") + .value, + }), + } + ); + if (response.status !== 200) { + throw new Error( + `Election could not be updated or created (status ${response.status})` + ); + } + election = await response.json(); + this.displayElections(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Common.toggleModal("election-modal", "election-modal-card"); + this.activateElection(election); + this.parent.areaHandler.election = election; + this.parent.areaHandler.displayAreas(); + return election; + } + + async updateElections() { + try { + const response = await fetch("/api/Election/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Elections could not be fetched (status ${response.status})` + ); + } + return await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async deleteElection(election) { + try { + const response = await fetch("/api/Election/" + election.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Election could not be deleted (status ${response.status})` + ); + } + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + this.displayElections(); + this.parent.areaHandler.emptyAreas(); + this.parent.sectionHandler.emptySections(); + this.parent.deskHandler.emptyDesks(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + document.getElementById("section-new").setAttribute("disabled", "true"); + document.getElementById("area-new").setAttribute("disabled", "true"); } } diff --git a/web/components/management/genericElection.js b/web/components/management/genericElection.js new file mode 100644 index 0000000000000000000000000000000000000000..21b09fa18c83c588763311dfd79d836e6fe72287 --- /dev/null +++ b/web/components/management/genericElection.js @@ -0,0 +1,50 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Election from "/components/management/election.js"; +import * as Areas from "/components/management/area.js"; +import * as Sections from "/components/management/section.js"; +import * as Desks from "/components/management/desk.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where) { + const genericElectionComponent = new GenericElection(); + await genericElectionComponent.mount(where); +} + +class GenericElection { + constructor() {} + + async mount(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="columns"> + <div class="column"> + <div id="election" class="card"></div> + </div> + <div class="column"> + <div id="areas" class="card"></div> + </div> + <div class="column"> + <div id="sections" class="card"></div> + </div> + <div class="column"> + <div id="desks" class="card"></div> + </div> + </div> + + <div class="modal" id="election-modal"></div> + <div class="modal" id="area-modal"></div> + <div class="modal" id="section-modal"></div> + <div class="modal" id="desk-modal"></div> + `; + current_user = await Auth.GetUser(); + this.electionHandler = await Election.mount("election", this); + this.areaHandler = await Areas.mount("areas", this); + this.sectionHandler = await Sections.mount("sections", this); + this.deskHandler = await Desks.mount("desks", this); + } +} diff --git a/web/components/management/management.js b/web/components/management/management.js index ab678413f85c45ee4ccff2593d62730d8aec9ffa..6a6fadeaf0d4405beadfe2af03c066e373ea5254 100644 --- a/web/components/management/management.js +++ b/web/components/management/management.js @@ -1,6 +1,6 @@ // Imports import * as Auth from "/services/auth/auth.js"; -import * as Election from "/components/management/election.js"; +import * as Election from "/components/management/genericElection.js"; import * as Round from "/components/management/round.js"; // DOM elements @@ -21,7 +21,7 @@ class Management { document.getElementById(mountpoint).innerHTML = /* HTML */ ` <div class="tabs is-boxed is-toggle is-fullwidth"> <ul> - <li id="election" class="is-active"> + <li id="elections" class="is-active"> <a> <span class="icon is-small" ><i class="fas fa-vote-yea" aria-hidden="true"></i @@ -29,7 +29,7 @@ class Management { <span>Election</span> </a> </li> - <li id="round"> + <li id="rounds"> <a> <span class="icon is-small" ><i class="fas fa-calendar" aria-hidden="true"></i @@ -43,20 +43,20 @@ class Management { `; current_user = await Auth.GetUser(); this.handleDom(); - document.getElementById("election").click() + document.getElementById("elections").click() } handleDom(){ - document.getElementById("election").addEventListener("click", async function (){ + document.getElementById("elections").addEventListener("click", async function (){ await Election.mount("management-section"); - document.getElementById("election").setAttribute("class", "is-active") - document.getElementById("round").setAttribute("class", "") + document.getElementById("elections").setAttribute("class", "is-active") + document.getElementById("rounds").setAttribute("class", "") }); - document.getElementById("round").addEventListener("click", async function (){ + document.getElementById("rounds").addEventListener("click", async function (){ await Round.mount("management-section"); - document.getElementById("election").setAttribute("class", "") - document.getElementById("round").setAttribute("class", "is-active") + document.getElementById("elections").setAttribute("class", "") + document.getElementById("rounds").setAttribute("class", "is-active") }); } } diff --git a/web/components/management/section.js b/web/components/management/section.js new file mode 100644 index 0000000000000000000000000000000000000000..c6a08c3c4d7268a6b8e81cc6ddfc1797a7822123 --- /dev/null +++ b/web/components/management/section.js @@ -0,0 +1,326 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Common from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import { Delete } from "/services/common/delete.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where, parent) { + const sectionComponent = new Section(parent); + await sectionComponent.mount(where); + return sectionComponent; +} + +class Section { + constructor(parent) { + this.parent = parent; + } + + async mount(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <header class="card-header"> + <p class="card-header-title" id="section-title"> + Sections + </p> + <button id="section-new" class="button is-success" disabled> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </header> + <div class="card-content"> + <div id="section-list" class="content"> + Sélectionner une circonscription. + </div> + </div> + `; + current_user = await Auth.GetUser(); + this.mountModal("section-modal"); + this.handleDom(); + } + + handleDom() { + let sectionHandler = this; + document + .getElementById(`section-new`) + .addEventListener("click", function () { + sectionHandler.newSection(sectionHandler.area); + }); + + document + .getElementById(`section-modal-close`) + .addEventListener("click", function () { + Common.toggleModal("section-modal", "section-modal-card"); + }); + document + .getElementById(`section-modal-cancel`) + .addEventListener("click", function () { + Common.toggleModal("section-modal", "section-modal-card"); + }); + document + .getElementById(`section-modal-save`) + .addEventListener("click", async function () { + await sectionHandler.saveSection(); + }); + document + .getElementById(`section-modal-save-section`) + .addEventListener("click", async function () { + await sectionHandler.saveSection(); + await new Promise((r) => setTimeout(r, 800)); + sectionHandler.newSection(sectionHandler.area); + }); + document + .getElementById(`section-modal-save-desk`) + .addEventListener("click", async function () { + let section = await sectionHandler.saveSection(); + await new Promise((r) => setTimeout(r, 800)); + sectionHandler.parent.deskHandler.section = section; + sectionHandler.parent.deskHandler.newDesk(section); + }); + } + + mountModal(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="section-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title"> + Ajout/modification d'une section + </p> + <button + class="delete" + aria-label="close" + id="section-modal-close" + ></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input + class="input" + type="number" + id="section-modal-id" + disabled + /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="section-modal-name" /> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="section-modal-save" class="button is-success"> + Sauvegarder + </button> + <button id="section-modal-save-section" class="button is-success"> + Sauvegarder <br /> + (ajouter section) + </button> + <button id="section-modal-save-desk" class="button is-success"> + Sauvegarder <br /> + (ajouter bureau) + </button> + <button id="section-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + } + + async displaySections() { + let sectionHandler = this; + let sections = await this.updateSections(); + const markup = sections + .map((section) => this.sectionTemplate(section)) + .join(""); + document.getElementById("section-list").innerHTML = markup; + document.getElementById("section-new").removeAttribute("disabled"); + document.getElementById("section-title").innerHTML = + "Sections de <em> " + this.area.Name + "</em>"; + + sections.map((section) => { + document + .getElementById(`sections-section-edit-${section.ID}`) + .addEventListener("click", function () { + sectionHandler.editSection(section); + }); + document + .getElementById(`sections-section-delete-${section.ID}`) + .addEventListener("click", function () { + new Delete(() => { + sectionHandler.deleteSection(section); + }); + }); + document + .getElementById(`sections-section-${section.ID}`) + .addEventListener("click", function () { + sectionHandler.activateSection(section); + sectionHandler.parent.deskHandler.section = section; + sectionHandler.parent.deskHandler.displayDesks(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + }); + }); + } + + emptySections() { + this.area = null; + document.getElementById("section-title").innerHTML = "Sections"; + document.getElementById("section-list").innerHTML = + "Veuillez sélectionner une circonscription"; + } + + sectionTemplate(section) { + return /* HTML */ `<div class="card card-list"> + <div id="sections-section-${section.ID}" class="card-content"> + <div class="content"> + <nav class="level"> + <div class="level-left"> + ${section.Name} + </div> + <div class="level-right"> + <a + id="sections-section-edit-${section.ID}" + class="button is-link is-small" + title="Modifier" + > + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a + id="sections-section-delete-${section.ID}" + class="button is-danger is-small" + title="Supprimer" + > + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </div> + </nav> + </div> + </div> + </div>`; + } + + async activateSection(section) { + this.parent.deskHandler.emptyDesks(); + let sections = await this.updateSections(); + sections.forEach((section) => { + document + .getElementById(`sections-section-${section.ID}`) + .classList.remove("active-card"); + }); + document + .getElementById(`sections-section-${section.ID}`) + .classList.add("active-card"); + } + + newSection(area) { + this.method = "POST"; + this.area = area; + document.getElementById("section-modal-id").value = null; + document.getElementById("section-modal-name").value = area.Name; + Common.toggleModal("section-modal", "section-modal-card"); + } + + editSection(section) { + this.method = "PUT"; + document.getElementById("section-modal-id").value = section.ID; + document.getElementById("section-modal-name").value = section.Name; + Common.toggleModal("section-modal", "section-modal-card"); + } + + async saveSection() { + let section; + if (this.method == "POST") + document.getElementById("section-modal-id").value = null; + + try { + const response = await fetch( + "/api/Section/" + document.getElementById("section-modal-id").value, + { + method: this.method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(document.getElementById("section-modal-id").value), + AreaID: this.area.ID, + Name: document.getElementById("section-modal-name").value, + }), + } + ); + if (response.status !== 200) { + throw new Error( + `Section could not be updated or created (status ${response.status})` + ); + } + section = await response.json(); + await this.displaySections(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Common.toggleModal("section-modal", "section-modal-card"); + this.activateSection(section); + this.parent.deskHandler.section = section; + this.parent.deskHandler.displayDesks(); + return section; + } + + async updateSections() { + let sectionHandler = this; + try { + const response = await fetch("/api/Section/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Sections could not be fetched (status ${response.status})` + ); + } + let sections = await response.json(); + return sections.filter(function (section) { + return section.AreaID == sectionHandler.area.ID; + }); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async deleteSection(section) { + try { + const response = await fetch("/api/Section/" + section.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Section could not be deleted (status ${response.status})` + ); + } + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + this.displaySections(); + this.parent.deskHandler.emptyDesks(); + document.getElementById("desk-new").setAttribute("disabled", "true"); + } +} diff --git a/web/components/users/handleUser.js b/web/components/users/handleUser.js index 3626f1ace1f08a224b4d5304b3a8f4abb35a342a..1778178cefa549b16ae04fd8b8f5bf482085ec8d 100644 --- a/web/components/users/handleUser.js +++ b/web/components/users/handleUser.js @@ -2,7 +2,7 @@ import { RandomString } from "/services/common/common.js"; import * as Messages from "/services/messages/messages.js"; import * as Auth from "/services/auth/auth.js"; -import * as Users from "/components/users/users.js"; +import * as Common from "/services/common/common.js"; // DOM elements let id_field; @@ -104,12 +104,12 @@ class HandleUser { document .getElementById(`users-modal-close`) .addEventListener("click", function () { - Users.toggleModal("users-modal", "users-modal-card"); + Common.toggleModal("users-modal", "users-modal-card"); }); document .getElementById(`users-modal-cancel`) .addEventListener("click", function () { - Users.toggleModal("users-modal", "users-modal-card"); + Common.toggleModal("users-modal", "users-modal-card"); }); let userHandler = this; document @@ -129,7 +129,7 @@ class HandleUser { password_field.value = RandomString(48); name_field.value = ""; role_field.value = ""; - Users.toggleModal("users-modal", "users-modal-card"); + Common.toggleModal("users-modal", "users-modal-card"); } async deleteUser(user) { @@ -165,7 +165,7 @@ class HandleUser { password_field.value = ""; name_field.value = user.name; role_field.value = user.role; - Users.toggleModal("users-modal", "users-modal-card"); + Common.toggleModal("users-modal", "users-modal-card"); } async updateUsers() { @@ -227,7 +227,7 @@ class HandleUser { Messages.Show("is-warning", e.message); console.error(e); } - Users.toggleModal("users-modal", "users-modal-card"); + Common.toggleModal("users-modal", "users-modal-card"); } async postCapturer(user, method) { diff --git a/web/components/users/users.js b/web/components/users/users.js index 7be58a02d5f22dd40fe6a7d0b5be277089763436..246c2ae3cd7789221d73b1f4d85b220e54a47793 100644 --- a/web/components/users/users.js +++ b/web/components/users/users.js @@ -1,5 +1,4 @@ // Imports -import { AnimateCSS } from "/services/common/common.js"; import * as Auth from "/services/auth/auth.js"; import { Delete } from "/services/common/delete.js"; import * as HandleUser from "/components/users/handleUser.js"; @@ -99,19 +98,4 @@ class Users { </tr> `; } -} - -export function toggleModal(modalID, cardID) { - const modal = document.getElementById(modalID); - const card = document.getElementById(cardID); - if (modal.classList.contains("is-active")) { - AnimateCSS(modal, "fadeOut"); - AnimateCSS(card, "zoomOut", function () { - modal.classList.remove("is-active"); - }); - } else { - modal.classList.add("is-active"); - AnimateCSS(modal, "fadeIn"); - AnimateCSS(card, "zoomIn"); - } -} +} \ No newline at end of file diff --git a/web/services/common/common.js b/web/services/common/common.js index 7a6b5cbca13b4ae3cc5745f463140be10aafd258..658c53b513515ce6ea3be14b30895d5b5c7499cd 100644 --- a/web/services/common/common.js +++ b/web/services/common/common.js @@ -19,4 +19,19 @@ export function RandomString(length) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; -} \ No newline at end of file +} + +export function toggleModal(modalID, cardID) { + const modal = document.getElementById(modalID); + const card = document.getElementById(cardID); + if (modal.classList.contains("is-active")) { + AnimateCSS(modal, "fadeOut"); + AnimateCSS(card, "zoomOut", function () { + modal.classList.remove("is-active"); + }); + } else { + modal.classList.add("is-active"); + AnimateCSS(modal, "fadeIn"); + AnimateCSS(card, "zoomIn"); + } +} diff --git a/web/style.css b/web/style.css index e3fd68954006a09aa10e5af3e75e14fbddec4980..2267274a1b80fa2ba7c498da1e460938909f241c 100644 --- a/web/style.css +++ b/web/style.css @@ -90,4 +90,17 @@ img { .select, select{ width :100%; +} + +.card-list{ + border: solid lightgray; + border-width: 0.5px; + border-radius: 2px; + margin:6px; +} + +.active-card{ + background-color: rgba(55,122,195,.95); + font-weight: bold; + color: white; } \ No newline at end of file