From 54af653b69f20e2ac52cab5c0e022fec72bec591 Mon Sep 17 00:00:00 2001
From: Alexis POYEN <apoyen@grandlyon.com>
Date: Wed, 3 Jun 2020 13:55:51 +0200
Subject: [PATCH] Feat : manage a round

creation, updating, deletion, view
---
 data/test.db                                 | Bin 65536 -> 69632 bytes
 web/components/management/candidate-lists.js |   2 +-
 web/components/management/election.js        |  48 +--
 web/components/management/round.js           |   2 +
 web/components/management/rounds-card.js     | 292 ++++++++++++++++++-
 web/services/common/getters.js               |  39 +++
 6 files changed, 338 insertions(+), 45 deletions(-)
 create mode 100644 web/services/common/getters.js

diff --git a/data/test.db b/data/test.db
index f0fc7e7d6d6501152a1221e211ccefa82a4ddc90..04150ffcc3b3ba1f7d353fbfafed5b5e9bf1005a 100644
GIT binary patch
delta 1818
zcma)+yKmD#9LH_v;lz1df<RG(3U1mZG^x&SrxGe{RjMKeO3MHeiqghHPC_EpZY9Lv
zwB-+IWziL=SQ#K<WMHF6ES<_r45$NQX@wB#fW+B-#YvoE**EyU_xt>Qzx&?R>)!fP
z@9O!ugke}5eMirkwG5Y)u)*Pv5%eoYY+;~3x)*&Cc_021+M)I-BJeA4i4=Vwi7j8h
z_p9gI%eAWnwM<3GL~k#?k~bya&b17knyW2T_55t5P%Y;R)ndN7P^qNo*;=iVUV0cK
z6Nv=Aa!W7FR+Pn>u4wiUAG<j|GBZxkjEr6%r&HEHvlmp0MMW#;=TX37UM;3*wW=%k
zl=)0*4h2?=1zpKkR84ozE!NaI1yS$MD)UWC?1T>T$fQnkqSNLda>9J%dEMyo-^I<G
z_qRC_ct<UVgJhzw&#EgK+`EA4vU6$5BE6&s$V4)UuZ)@JsLydEu`@kpHg>6fkDnp@
ziSdi(Uy?R|f{6Je`sG!OpwE85NU)BBJ@6fDg9dmD*1<+2PS?RG<@Y3UJCa5K($j~Z
zG(n@nUQZ9MDRa77t7^WW&y%pHHPYI~Q1D<`V|zGrB|v~l3_J&uDC%Wk+$Jb&Y6=Tt
zy%^3y7G|NCg&fUrLjp6z34;t2a<afN1CSkpX#Rf{tW4a&DQtS$4jpV;$ihKh<oOvv
zW*OEc_@SnQgk>x<QlU)@JO=6Lo9I+zC-NW?4L=Kuq0L4-_9A4IDI1Rmh69}|lXy<b
z$rA5Ew~BX42nZpQwO<-S)DH9x*dwV1?KU>SjFvu%a!z2O&;{mDxU>A_z$t}^tSE>E
z!*zvOnq!GO&8L#Bm9-Qmu%g8CVryYWlZPA<cb2#0Kd!9ZG1h+Y0taP@?Y3n{65}8t
zcUsAJV9$>JK-Lo&5%MgQ-C*q!hdm63KT6(HU{HhtBS~Cq8xOl^!JHBYteNF6c`TNJ
z1>Gv+a||nSImyi**12;^Tw%Tvx<9N#-o`Sl3&e^f^Nb`5u-hSbN?c)H_3%bPuy%h~
gED4!IWb7>)Mm_0x>ojSQ#1*ztzp=3!+gOeL1E;!?Z~y=R

delta 270
zcmZozz|zpbGC^8Uoq>Tt0El6LZ=#N|wmO5J{Y_r>Dh7V$76!hT{73nw^B&^4#C@0R
zBNsO(KZhHOC7Ud(E^`a}_sxO=znCVgux_1f#{QKvmzP~!QIWA(b@Cmir<-rE#xZhn
z*fG?KYilz$c}{j<FP?mt#hruy2LlJlzR6k~C%GCcIoZXPl^L5YC;#R6$gSC!%)u@$
zDaqKZIGKm*_U0d4Kba@1@ZXvIk<S>U39NCG0F#2i83tDV%?$h>`5*J&;NQ%DaI>Jo
zLVi^q7G_z-;?(4l%>2A!R!&xCamJL?;_PB}pk5wMsKn+Q_BsxmSr+{9U$jAC(FXwl
D(>F=;

diff --git a/web/components/management/candidate-lists.js b/web/components/management/candidate-lists.js
index ad1a49e..533831d 100644
--- a/web/components/management/candidate-lists.js
+++ b/web/components/management/candidate-lists.js
@@ -25,7 +25,7 @@ class CandidateList {
         <p class="card-header-title">
           Liste des candidats
         </p>
-        <button id="round-new" class="button is-success" disabled>
+        <button id="candidate-list-new" class="button is-success" disabled>
           <span class="icon is-small">
             <i class="fas fa-plus"></i>
           </span>
diff --git a/web/components/management/election.js b/web/components/management/election.js
index 6a33f95..95fc3ec 100644
--- a/web/components/management/election.js
+++ b/web/components/management/election.js
@@ -1,6 +1,7 @@
 // Imports
 import * as Auth from "/services/auth/auth.js";
 import * as Common from "/services/common/common.js";
+import * as Getters from "/services/common/getters.js";
 import * as Messages from "/services/messages/messages.js";
 import { Delete } from "/services/common/delete.js";
 
@@ -207,7 +208,7 @@ class Election {
   }
 
   async displayElections() {
-    let elections = await this.updateElections();
+    let elections = await Getters.getElections(current_user);
     const markup = elections
       .map((election) => this.electionTemplate(election))
       .join("");
@@ -294,7 +295,7 @@ class Election {
     this.parent.areaHandler.emptyAreas();
     this.parent.sectionHandler.emptySections();
     this.parent.deskHandler.emptyDesks();
-    let elections = await this.updateElections();
+    let elections = await Getters.getElections(current_user);
     elections.forEach((election) => {
       document
         .getElementById(`elections-election-${election.ID}`)
@@ -367,26 +368,6 @@ class Election {
     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, {
@@ -424,7 +405,8 @@ class Election {
 
   async saveCloneElection() {
     let electionCloned;
-    let electionToClone = await this.getElection(
+    let electionToClone = await getters.getElection(
+      current_user,
       document.getElementById("election-modal-id-clone").value
     );
 
@@ -465,24 +447,4 @@ class Election {
     this.parent.areaHandler.election = electionCloned;
     await this.parent.areaHandler.displayAreas();
   }
-
-  async getElection(id) {
-    try {
-      const response = await fetch("/api/Election/" + id, {
-        method: "GET",
-        headers: new Headers({
-          "XSRF-Token": current_user.xsrftoken,
-        }),
-      });
-      if (response.status !== 200) {
-        throw new Error(
-          `Election could not be fetched (status ${response.status})`
-        );
-      }
-      return await response.json();
-    } catch (e) {
-      Messages.Show("is-warning", e.message);
-      console.error(e);
-    }
-  }
 }
diff --git a/web/components/management/round.js b/web/components/management/round.js
index 4c89228..864b26b 100644
--- a/web/components/management/round.js
+++ b/web/components/management/round.js
@@ -35,6 +35,8 @@ class Round {
           </div>
         </div>
       </div>
+
+      <div class="modal" id="round-modal"></div>
     `;
     current_user = await Auth.GetUser();
     this.roundsHandler = RoundsCard.mount("rounds-list", this)
diff --git a/web/components/management/rounds-card.js b/web/components/management/rounds-card.js
index b6c94b0..f1bbb7f 100644
--- a/web/components/management/rounds-card.js
+++ b/web/components/management/rounds-card.js
@@ -1,5 +1,9 @@
 // Imports
 import * as Auth from "/services/auth/auth.js";
+import * as Common from "/services/common/common.js";
+import * as Getters from "/services/common/getters.js";
+import * as Messages from "/services/messages/messages.js";
+import { Delete } from "/services/common/delete.js";
 
 // DOM elements
 
@@ -32,9 +36,295 @@ class Round {
         </button>
       </header>
       <div class="card-content">
-        <div id="rounds-list" class="content">Liste des tours</div>
+        <div id="round-list" class="content">Liste des tours</div>
       </div>
     `;
     current_user = await Auth.GetUser();
+    this.mountModal("round-modal");
+    this.handleDom();
+    this.displayRounds();
+    this.refreshElections();
+  }
+
+  handleDom() {
+    let roundHandler = this;
+    document.getElementById(`round-new`).addEventListener("click", function () {
+      roundHandler.newRound();
+    });
+
+    document
+      .getElementById(`round-modal-close`)
+      .addEventListener("click", function () {
+        Common.toggleModal("round-modal", "round-modal-card");
+      });
+    document
+      .getElementById(`round-modal-cancel`)
+      .addEventListener("click", function () {
+        Common.toggleModal("round-modal", "round-modal-card");
+      });
+    document
+      .getElementById(`round-modal-save`)
+      .addEventListener("click", async function () {
+        if (document.getElementById("round-modal-election-id").value === "0") {
+          Messages.Show("is-danger", "Veuillez sélectionner une élection.");
+          return;
+        }
+        await roundHandler.saveRound();
+      });
+  }
+
+  async refreshElections() {
+    let selectElection = document.getElementById("round-modal-election-id");
+    let elections = await Getters.getElections(current_user);
+    elections.forEach((election) => {});
+    for (let i = selectElection.options.length - 1; i >= 0; i--) {
+      selectElection.remove(i);
+    }
+
+    let el = document.createElement("option");
+    el.textContent = "Veuillez sélectionner une élection";
+    el.value = 0;
+    selectElection.appendChild(el);
+    elections.forEach((election) => {
+      el = document.createElement("option");
+      el.textContent = election.Name;
+      el.value = election.ID;
+      selectElection.appendChild(el);
+    });
+  }
+
+  mountModal(where) {
+    const mountpoint = where;
+    document.getElementById(mountpoint).innerHTML = /* HTML */ `
+      <div class="modal-background"></div>
+      <div class="modal-card" id="round-modal-card">
+        <header class="modal-card-head">
+          <p class="modal-card-title">Ajout/modification d'un tour</p>
+          <button
+            class="delete"
+            aria-label="close"
+            id="round-modal-close"
+          ></button>
+        </header>
+        <section class="modal-card-body">
+          <div class="field">
+            <label>Id</label>
+            <div class="control">
+              <input class="input" type="number" id="round-modal-id" disabled />
+            </div>
+          </div>
+          <div class="field">
+            <label>Tour de l'élection</label><br />
+            <div class="control select">
+              <select name="ballot-type" id="round-modal-election-id"></select>
+            </div>
+          </div>
+          <div class="field">
+            <label>Date</label>
+            <div class="control">
+              <input class="input" type="date" id="round-modal-date" />
+            </div>
+          </div>
+          <div class="field">
+            <label>Tour n°</label>
+            <div class="control">
+              <input class="input" type="number" id="round-modal-round" />
+            </div>
+          </div>
+        </section>
+        <footer class="modal-card-foot">
+          <button id="round-modal-save" class="button is-success">
+            Sauvegarder
+          </button>
+          <button id="round-modal-cancel" class="button">Annuler</button>
+        </footer>
+      </div>
+    `;
+  }
+
+  async displayRounds() {
+    let rounds = await this.updateRounds();
+    const markup = rounds.map((round) => this.roundTemplate(round)).join("");
+    document.getElementById("round-list").innerHTML = markup;
+
+    let roundHandler = this;
+    rounds.map(async (round) => {
+      document
+        .getElementById(`rounds-round-edit-${round.ID}`)
+        .addEventListener("click", function () {
+          roundHandler.editRound(round);
+        });
+      let election = await Getters.getElection(current_user, round.ElectionID);
+      let html = document.getElementById(`rounds-round-desc-${round.ID}`).innerHTML
+      document.getElementById(`rounds-round-desc-${round.ID}`).innerHTML =
+        election.Name +
+        " " +
+        document.getElementById(`rounds-round-desc-${round.ID}`).innerHTML;
+      document
+        .getElementById(`rounds-round-delete-${round.ID}`)
+        .addEventListener("click", function () {
+          new Delete(() => {
+            roundHandler.deleteRound(round);
+          });
+        });
+      document
+        .getElementById(`rounds-round-${round.ID}`)
+        .addEventListener("click", function () {
+          // TODO open desk rounds
+          // TODO open candidate list
+        });
+    });
+  }
+
+  roundTemplate(round) {
+    return /* HTML */ `<div class="card card-list">
+      <div id="rounds-round-${round.ID}" class="card-content">
+        <div class="content">
+          <nav class="level">
+            <div id="rounds-round-desc-${round.ID}" class="level-left">
+              (tour : ${round.Round}, date :
+              ${new Date(round.Date).toLocaleDateString()})
+            </div>
+            <div class="level-right">
+              <a
+                id="rounds-round-edit-${round.ID}"
+                class="button is-link is-small"
+                title="Modifier"
+              >
+                <span class="icon is-small">
+                  <i class="fas fa-pen"></i>
+                </span>
+              </a>
+              <a
+                id="rounds-round-delete-${round.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 activateRound(round) {
+    // TODO unselect desks and candidateLists
+    let rounds = await this.updateRounds();
+    rounds.forEach((round) => {
+      document
+        .getElementById(`rounds-round-${round.ID}`)
+        .classList.remove("active-card");
+    });
+    document
+      .getElementById(`rounds-round-${round.ID}`)
+      .classList.add("active-card");
+  }
+
+  newRound() {
+    this.method = "POST";
+    document.getElementById("round-modal-id").value = null;
+    document.getElementById("round-modal-date").value = null;
+    document.getElementById("round-modal-round").value = null;
+    Common.toggleModal("round-modal", "round-modal-card");
+  }
+
+  editRound(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-round").value = round.Round;
+    Common.toggleModal("round-modal", "round-modal-card");
+  }
+
+  async saveRound() {
+    let round;
+    if (this.method == "POST")
+      document.getElementById("round-modal-id").value = null;
+
+    try {
+      const response = await fetch(
+        "/api/Round/" + document.getElementById("round-modal-id").value,
+        {
+          method: this.method,
+          headers: new Headers({
+            "XSRF-Token": current_user.xsrftoken,
+          }),
+          body: JSON.stringify({
+            ID: parseInt(document.getElementById("round-modal-id").value),
+            ElectionID: parseInt(
+              document.getElementById("round-modal-election-id").value
+            ),
+            Date: document.getElementById("round-modal-date").value,
+            Round: parseInt(document.getElementById("round-modal-round").value),
+          }),
+        }
+      );
+      if (response.status !== 200) {
+        throw new Error(
+          `Area could not be updated or created (status ${response.status})`
+        );
+      }
+      round = await response.json();
+      this.displayRounds();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+      return;
+    }
+    Common.toggleModal("round-modal", "round-modal-card");
+    this.activateRound(round);
+    // TODO open desks
+    // TODO open candidateLists
+    return round;
+  }
+
+  async updateRounds() {
+    try {
+      const response = await fetch("/api/Round/", {
+        method: "GET",
+        headers: new Headers({
+          "XSRF-Token": current_user.xsrftoken,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Rounds could not be fetched (status ${response.status})`
+        );
+      }
+      return await response.json();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+    }
+  }
+
+  async deleteRound(round) {
+    try {
+      const response = await fetch("/api/Round/" + round.ID, {
+        method: "delete",
+        headers: new Headers({
+          "XSRF-Token": current_user.xsrftoken,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Round could not be deleted (status ${response.status})`
+        );
+      }
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+    }
+    this.displayRounds();
+    // TODO empty desks
+    // TODO empty candidateLists
+    document
+      .getElementById("candidate-list-new")
+      .setAttribute("disabled", "true");
   }
 }
diff --git a/web/services/common/getters.js b/web/services/common/getters.js
new file mode 100644
index 0000000..4691a74
--- /dev/null
+++ b/web/services/common/getters.js
@@ -0,0 +1,39 @@
+export async function getElections(current_user) {
+  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);
+  }
+}
+
+export async function getElection(current_user, id) {
+  try {
+    const response = await fetch("/api/Election/" + id, {
+      method: "GET",
+      headers: new Headers({
+        "XSRF-Token": current_user.xsrftoken,
+      }),
+    });
+    if (response.status !== 200) {
+      throw new Error(
+        `Election 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
-- 
GitLab