diff --git a/internal/models/deskRound.go b/internal/models/deskRound.go index 231b3b06adf0746f064c4154db4379af52c607a3..b939019069b7dcda66dd47e662fc5c9f04e9356d 100644 --- a/internal/models/deskRound.go +++ b/internal/models/deskRound.go @@ -57,6 +57,11 @@ func (d *DataHandler) putDeskRound(w http.ResponseWriter, r *http.Request, id in return } + if !o.Completed { + http.Error(w, "Le bureau doit être complété avant de le valider", http.StatusInternalServerError) + return + } + var deskRound DeskRound err := json.NewDecoder(r.Body).Decode(&deskRound) if err != nil { diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go index 4e4cf2cec715e6d34a9ee60098b8608d1e66851e..b80792167f94c7f0e8ec61a9e3abc04e5752f7e4 100644 --- a/internal/rootmux/admin_test.go +++ b/internal/rootmux/admin_test.go @@ -84,11 +84,12 @@ func AdminTests(t *testing.T) { do("GET", "/api/DeskRound/1", xsrfHeader, ``, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":[]}`) // Get DeskRounds do("GET", "/api/DeskRound/", xsrfHeader, ``, 200, `[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":[]}]`) - // Update a DeskRound - do("PUT", "/api/DeskRound/1", xsrfHeader, `{"ID":1,"Validated":true}`, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":true,"Votes":[]}`) // Delete a DeskRound should fail with 400 do("DELETE", "/api/DeskRound/1", xsrfHeader, ``, 400, `method not allowed`) + // Update a DeskRound should be done after desk completion by capturing votes + // do("PUT", "/api/DeskRound/1", xsrfHeader, `{"ID":1,"Validated":true}`, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":true,"Votes":[]}`) + // Delete a Round do("DELETE", "/api/Round/1", xsrfHeader, ``, 200, ``) // Delete a desk diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 1a5d922fe6f510bfb5b57e2c9612736f7b892c0f..4295acfdc663451b32807634239330eabdcd70ab 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -98,6 +98,9 @@ func appTests(t *testing.T) { do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`) do("GET", "/api/Desk/1", xsrfHeader, ``, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}]}`) + // Verify that a DeskRound can't be validated witout being completed + do("PUT", "/api/DeskRound/1", xsrfHeader, `{"ID":1,"Validated":true}`, 500, `Le bureau doit être complété avant de le valider`) + // Verify that on Desk deletion deskRounds are deleted do("GET", "/api/Desk/1", xsrfHeader, ``, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}]}`) do("DELETE", "/api/Desk/1", xsrfHeader, ``, 200, ``) diff --git a/web/components/management/management.js b/web/components/management/management.js index 6a6fadeaf0d4405beadfe2af03c066e373ea5254..d14167fd18fa06a685fb6e397bb67d5ecd61a319 100644 --- a/web/components/management/management.js +++ b/web/components/management/management.js @@ -39,7 +39,7 @@ class Management { </li> </ul> </div> - <section class="section" id="management-section" style="margin-bottom: 230px;"></section> + <section id="management-section" style="margin-bottom: 230px;"></section> `; current_user = await Auth.GetUser(); this.handleDom(); diff --git a/web/components/management/round-desks.js b/web/components/management/round-desks.js index ea924e85a718cef4af6b6b98df802158b7c1f225..28ce765bd5beaaabb9011eb5c691816a6909682b 100644 --- a/web/components/management/round-desks.js +++ b/web/components/management/round-desks.js @@ -1,5 +1,7 @@ // Imports import * as Auth from "/services/auth/auth.js"; +import * as Getters from "/services/common/getters.js"; +import * as Messages from "/services/messages/messages.js"; // DOM elements @@ -14,7 +16,6 @@ export async function mount(where, parent) { class RoundDesk { constructor(parent) { - this.method = null; this.parent = parent; } @@ -26,12 +27,185 @@ class RoundDesk { Bureaux de votes </p> </header> - <div class="card-content"> - <div id="round-desks-list" class="content"> - Liste des bureaux de votes + <div class="columns card-content"> + <div class="column"> + <div id="desk-rounds"></div> + </div> + <div class="column"> + <div id="desk-round-details"></div> + </div> + <div class="column"> + <div id="active-capturer" class="card"></div> + </div> + <div class="column"> + <div id="available-capturer" class="card"></div> </div> </div> `; current_user = await Auth.GetUser(); } + + deskRoundTemplate(deskRound) { + return /* HTML */ `<div class="card card-list"> + <div id="deskrounds-deskround-${deskRound.ID}" class="card-content"> + <div class="content"> + <nav class="level"> + <div + id="deskrounds-deskround-desc-${deskRound.ID}" + class="level-left" + ></div> + <div class="level-right"></div> + </nav> + </div> + </div> + </div>`; + } + + deskRoundDetailsTemplate() { + return /* HTML */ `<div> + <section class="modal-card-body"> + <div class="field"> + <div class="control"> + <label class="checkbox" disabled> + <input + class="input" + type="checkbox" + id="deskround-completed" + ${this.deskRound.Completed ? 'checked="true"' : ""} + disabled + /> + Complété + ${this.deskRound.Completed + ? "(" + + new Date(this.deskRound.DateCompletion).toLocaleDateString() + + ")" + : ""} + </label> + </div> + </div> + <div class="field"> + <div class="control"> + <label class="checkbox" ${!this.deskRound.Completed ? 'disabled="true"' : ""}> + <input + class="input" + type="checkbox" + id="deskround-validated" + ${this.deskRound.Validated ? 'checked="true"' : ""} + ${!this.deskRound.Completed ? 'disabled="true"' : ""} + /> + Validé + </label> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="deskround-save" class="button is-success"> + Sauvegarder + </button> + <button id="deskround-cancel" class="button"> + Annuler + </button> + </footer> + </div>`; + } + + async displayDesks() { + let deskRounds = await this.updateDeskRounds(); + const markup = deskRounds + .map((deskRound) => this.deskRoundTemplate(deskRound)) + .join(""); + document.getElementById("desk-rounds").innerHTML = markup; + + let deskRoundHandler = this; + deskRounds.map(async (deskRound) => { + let desk = await Getters.getDesk(current_user, deskRound.DeskID); + document.getElementById( + `deskrounds-deskround-desc-${deskRound.ID}` + ).innerHTML = desk.Name; + document + .getElementById(`deskrounds-deskround-${deskRound.ID}`) + .addEventListener("click", function () { + deskRoundHandler.activateDeskRound(deskRound); + deskRoundHandler.deskRound = deskRound; + deskRoundHandler.displayDeskRoundsDetails(); + // TODO open capturers affected + }); + }); + } + + async displayDeskRoundsDetails() { + document.getElementById( + "desk-round-details" + ).innerHTML = this.deskRoundDetailsTemplate(); + let deskRoundHandler = this; + document + .getElementById(`deskround-save`) + .addEventListener("click", async function () { + await deskRoundHandler.saveDeskRound(); + }); + } + + async activateDeskRound(deskRoundToActivate) { + let deskRounds = await this.updateDeskRounds(); + deskRounds.forEach((deskRound) => { + document + .getElementById(`deskrounds-deskround-${deskRound.ID}`) + .classList.remove("active-card"); + }); + document + .getElementById(`deskrounds-deskround-${deskRoundToActivate.ID}`) + .classList.add("active-card"); + } + + async updateDeskRounds() { + let deskRoundHandler = this; + try { + const response = await fetch("/api/DeskRound/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `DeskRounds could not be fetched (status ${response.status})` + ); + } + let deskRounds = await response.json(); + return deskRounds.filter(function (deskRound) { + return deskRound.RoundID == deskRoundHandler.round.ID; + }); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async saveDeskRound() { + let round; + try { + const response = await fetch("/api/DeskRound/" + this.deskRound.ID, { + method: "PUT", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: this.deskRound.ID, + Validated: document.getElementById("deskround-validated").checked, + }), + }); + if (response.status !== 200) { + throw new Error( + `DeskRound could not be updated (status ${response.status})` + ); + } + await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + return; + } + Messages.Show("is-success", "Bureau de vote mis à jour"); + this.displayDesks(); + } } diff --git a/web/components/management/round.js b/web/components/management/round.js index 864b26b1dee9623b8d662127b2a97a4f813e0720..a977ceaa39cdc2e3e620bc1dc3b11f6aff80b61f 100644 --- a/web/components/management/round.js +++ b/web/components/management/round.js @@ -39,8 +39,8 @@ class Round { <div class="modal" id="round-modal"></div> `; current_user = await Auth.GetUser(); - this.roundsHandler = RoundsCard.mount("rounds-list", this) - this.roundsHandler = RoundDesks.mount("round-desks", this) - this.roundsHandler = CandidateList.mount("candidate-lists", this) + this.roundsHandler = await RoundsCard.mount("rounds-list", this) + this.deskRoundsHandler = await RoundDesks.mount("round-desks", this) + this.candidateListHandler = await CandidateList.mount("candidate-lists", this) } } diff --git a/web/components/management/rounds-card.js b/web/components/management/rounds-card.js index 81dad9801cbd1baabd79fda895b4bbcd51fbecb4..561b6b09c074174bb958c4ce36267ba68514fb8e 100644 --- a/web/components/management/rounds-card.js +++ b/web/components/management/rounds-card.js @@ -168,8 +168,11 @@ class Round { }); document .getElementById(`rounds-round-${round.ID}`) - .addEventListener("click", function () { - // TODO open desk rounds + .addEventListener("click", async function () { + roundHandler.activateRound(round); + roundHandler.parent.deskRoundsHandler.round = round; + await roundHandler.parent.deskRoundsHandler.displayDesks(); + document.getElementById("candidate-list-new").setAttribute("disabled", "true"); // TODO open candidate list }); }); @@ -211,16 +214,16 @@ class Round { } async activateRound(roundToActivate) { - // TODO unselect desks and candidateLists let rounds = await this.updateRounds(); rounds.forEach((round) => { document - .getElementById(`rounds-round-${round.ID}`) - .classList.remove("active-card"); + .getElementById(`rounds-round-${round.ID}`) + .classList.remove("active-card"); }); document - .getElementById(`rounds-round-${roundToActivate.ID}`) - .classList.add("active-card"); + .getElementById(`rounds-round-${roundToActivate.ID}`) + .classList.add("active-card"); + // TODO unselect candidateLists } newRound() { @@ -235,7 +238,9 @@ class Round { this.method = "PUT"; document.getElementById("round-modal-id").value = round.ID; document.getElementById("round-modal-election-id").value = round.ElectionID; - document.getElementById("round-modal-date").value = new Date(round.Date).toISOString().split('T')[0]; + document.getElementById("round-modal-date").value = new Date(round.Date) + .toISOString() + .split("T")[0]; document.getElementById("round-modal-round").value = round.Round; Common.toggleModal("round-modal", "round-modal-card"); } @@ -265,7 +270,7 @@ class Round { ); if (response.status !== 200) { throw new Error( - `Area could not be updated or created (status ${response.status})` + `Round could not be updated or created (status ${response.status})` ); } round = await response.json(); diff --git a/web/services/common/getters.js b/web/services/common/getters.js index 4691a741319da3a92865a4987eba3a0d5f2ebcdb..25b4e4ea8e808105c71b714afb8d71d812c8f22b 100644 --- a/web/services/common/getters.js +++ b/web/services/common/getters.js @@ -36,4 +36,24 @@ export async function getElection(current_user, id) { Messages.Show("is-warning", e.message); console.error(e); } +} + +export async function getDesk(current_user, id) { + try { + const response = await fetch("/api/Desk/" + id, { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error( + `Desk could not be fetched (status ${response.status})` + ); + } + return await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } } \ No newline at end of file diff --git a/web/style.css b/web/style.css index 1c4154cd551cf442dc6356d64203047cd30ff9e9..8039cb53528ec56e52a9bfae911e6ea69dc50a9a 100644 --- a/web/style.css +++ b/web/style.css @@ -88,19 +88,20 @@ img { cursor: auto; } -.select, select{ - width :100%; +.select, +select { + width: 100%; } -.card-list{ +.card-list { border: solid lightgray; border-width: 0.5px; border-radius: 2px; - margin:6px; + margin: 6px; } -.active-card{ - background-color: rgba(55,122,195,.95); +.active-card { + background-color: rgba(55, 122, 195, 0.95); font-weight: bold; color: white; } @@ -109,6 +110,14 @@ img { flex-basis: 70%; } -#round-desks{ +#round-desks { height: 35vh; -} \ No newline at end of file +} + +#round-desks .column { + overflow-y: auto; +} + +#round-desks .columns { + max-height: 90%; +}