From 1a53b291b7ef46b1979489f032133890d3d72514 Mon Sep 17 00:00:00 2001
From: Alexis POYEN <apoyen@grandlyon.com>
Date: Thu, 11 Jun 2020 13:32:36 +0200
Subject: [PATCH] Resolve "Handle CandidateList"

---
 web/components/management/candidate-lists.js | 282 +++++++++++++++++--
 web/components/management/round.js           |   1 +
 web/components/management/rounds-card.js     |  15 +-
 web/services/model/area-model.js             |   2 +-
 web/services/model/candidateList-model.js    | 100 +++++++
 web/services/model/party-model.js            |   2 +-
 6 files changed, 376 insertions(+), 26 deletions(-)
 create mode 100644 web/services/model/candidateList-model.js

diff --git a/web/components/management/candidate-lists.js b/web/components/management/candidate-lists.js
index 5c9bab1..c58460c 100644
--- a/web/components/management/candidate-lists.js
+++ b/web/components/management/candidate-lists.js
@@ -1,6 +1,11 @@
 // 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 * as AreaModel from "/services/model/area-model.js";
+import * as PartyModel from "/services/model/party-model.js";
+import * as CandidateListModel from "/services/model/candidateList-model.js";
+import { Delete } from "/services/common/delete.js";
 
 // DOM elements
 
@@ -18,10 +23,14 @@ class CandidateList {
     this.method = null;
     this.parent = parent;
     this.AreaModel = AreaModel.getAreaModel();
+    this.PartyModel = PartyModel.getPartyModel();
+    this.CandidateListModel = CandidateListModel.getCandidateListModel();
   }
 
   async mount(where) {
     this.AreaModel.current_user = await Auth.GetUser();
+    this.PartyModel.current_user = await Auth.GetUser();
+    this.CandidateListModel.current_user = await Auth.GetUser();
     const mountpoint = where;
     document.getElementById(mountpoint).innerHTML = /* HTML */ `
       <header class="card-header">
@@ -34,22 +43,24 @@ class CandidateList {
           </span>
         </button>
       </header>
-        <div class="columns card-content">
-          <div class="column">
-            <div id="areas-round"></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 class="columns card-content">
+        <div class="column">
+          <div id="areas-round"></div>
         </div>
+        <div class="column">
+          <div id="candidate-lists-list"></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();
+    this.mountModal("candidateList-modal");
+    this.handleDom();
   }
 
   areaTemplate(area) {
@@ -66,6 +77,127 @@ class CandidateList {
     </div>`;
   }
 
+  candidateListTemplate(candidateList) {
+    return /* HTML */ `<div class="card card-list">
+      <div
+        id="candidateLists-candidateList-${candidateList.ID}"
+        class="card-content"
+      >
+        <div class="content">
+          <nav class="level">
+            <div
+              id="candidateLists-candidateList-desc-${candidateList.ID}"
+              class="level-left"
+            >
+              ${candidateList.Name}
+            </div>
+            <div class="level-right">
+              <a
+                id="candidateLists-candidateList-edit-${candidateList.ID}"
+                class="button is-link is-small"
+                title="Modifier"
+              >
+                <span class="icon is-small">
+                  <i class="fas fa-pen"></i>
+                </span>
+              </a>
+              <a
+                id="candidateLists-candidateList-delete-${candidateList.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>`;
+  }
+
+  mountModal(where) {
+    const mountpoint = where;
+    document.getElementById(mountpoint).innerHTML = /* HTML */ `
+      <div class="modal-background"></div>
+      <div class="modal-card" id="candidateList-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="candidateList-modal-close"
+          ></button>
+        </header>
+        <section class="modal-card-body">
+          <div class="field">
+            <label>Id</label>
+            <div class="control">
+              <input
+                class="input"
+                type="number"
+                id="candidateList-modal-id"
+                disabled
+              />
+            </div>
+          </div>
+          <div class="field">
+            <label>Name</label>
+            <div class="control">
+              <input class="input" type="text" id="candidateList-modal-name" />
+            </div>
+          </div>
+          <div class="field">
+            <label>Parti</label><br />
+            <div class="control select">
+              <select name="party" id="candidateList-modal-party"></select>
+            </div>
+          </div>
+        </section>
+        <footer class="modal-card-foot">
+          <button id="candidateList-modal-save" class="button is-success">
+            Sauvegarder
+          </button>
+          <button id="candidateList-modal-cancel" class="button">
+            Annuler
+          </button>
+        </footer>
+      </div>
+    `;
+  }
+
+  handleDom() {
+    let candidateListHandler = this;
+    document
+      .getElementById(`candidate-list-new`)
+      .addEventListener("click", function () {
+        candidateListHandler.newCandidateList();
+      });
+
+    document
+      .getElementById(`candidateList-modal-close`)
+      .addEventListener("click", function () {
+        Common.toggleModal("candidateList-modal", "candidateList-modal-card");
+      });
+    document
+      .getElementById(`candidateList-modal-cancel`)
+      .addEventListener("click", function () {
+        Common.toggleModal("candidateList-modal", "candidateList-modal-card");
+      });
+    document
+      .getElementById(`candidateList-modal-save`)
+      .addEventListener("click", async function () {
+        if (
+          document.getElementById("candidateList-modal-party").value === "0"
+        ) {
+          Messages.Show("is-danger", "Veuillez sélectionner un parti.");
+          return;
+        }
+        await candidateListHandler.saveCandidateList();
+      });
+  }
+
   async displayAreas() {
     let candidateListHandler = this;
     let areas = await this.updateAreas();
@@ -77,7 +209,6 @@ class CandidateList {
         .getElementById(`areas-area-${area.ID}`)
         .addEventListener("click", async function () {
           await candidateListHandler.activateArea(area);
-          candidateListHandler.area = area;
           await candidateListHandler.displayCandidateLists();
         });
     });
@@ -91,11 +222,130 @@ class CandidateList {
     });
   }
 
-  async activateArea(){
+  async activateArea(areaToActivate) {
+    this.area = areaToActivate;
+    let areas = await this.updateAreas();
+    areas.forEach((area) => {
+      document
+        .getElementById(`areas-area-${area.ID}`)
+        .classList.remove("active-card");
+    });
+    document
+      .getElementById(`areas-area-${areaToActivate.ID}`)
+      .classList.add("active-card");
+    document.getElementById("candidate-list-new").removeAttribute("disabled");
+  }
+
+  async activateCandidateList() {}
+
+  async displayCandidateLists() {
+    let candidateListHandler = this;
+    let candidateLists = await this.updateCandidateLists();
+    const markup = candidateLists
+      .map((candidateList) => this.candidateListTemplate(candidateList))
+      .join("");
+    document.getElementById("candidate-lists-list").innerHTML = markup;
 
+    candidateLists.map(async (candidateList) => {
+      let party = await this.PartyModel.getParty(candidateList.PartyID);
+      document.getElementById(
+        `candidateLists-candidateList-desc-${candidateList.ID}`
+      ).innerHTML += "(" + party.Name + ")";
+      document
+        .getElementById(`candidateLists-candidateList-edit-${candidateList.ID}`)
+        .addEventListener("click", function () {
+          candidateListHandler.editCandidateList(candidateList);
+        });
+      document
+        .getElementById(
+          `candidateLists-candidateList-delete-${candidateList.ID}`
+        )
+        .addEventListener("click", function () {
+          new Delete(() => {
+            candidateListHandler.deleteCandidateList(candidateList);
+          });
+        });
+      // document
+      //   .getElementById(`areas-area-${candidateList.ID}`)
+      //   .addEventListener("click", async function () {
+      //     await candidateListHandler.activateArea(candidateList);
+      //     candidateListHandler.area = candidateList;
+      //     await candidateListHandler.displayCandidateLists();
+      //   });
+    });
+  }
+
+  async updateCandidateLists() {
+    let candidateListHandler = this;
+    let candidateLists = await this.CandidateListModel.getCandidateLists();
+    return candidateLists.filter(function (candidateList) {
+      return candidateList.AreaID == candidateListHandler.area.ID;
+    });
+  }
+
+  async newCandidateList() {
+    this.method = "POST";
+    await this.refreshParties();
+    document.getElementById("candidateList-modal-id").value = null;
+    document.getElementById("candidateList-modal-party").value = null;
+    document.getElementById("candidateList-modal-name").value = null;
+    Common.toggleModal("candidateList-modal", "candidateList-modal-card");
   }
 
-  async displayCandidateLists(){
+  async editCandidateList(candidateList) {
+    this.method = "PUT";
+    await this.refreshParties();
+    document.getElementById("candidateList-modal-id").value = candidateList.ID;
+    document.getElementById("candidateList-modal-party").value =
+      candidateList.PartyID;
+    document.getElementById("candidateList-modal-name").value =
+      candidateList.Name;
+    Common.toggleModal("candidateList-modal", "candidateList-modal-card");
+  }
+
+  async refreshParties() {
+    let selectParties = document.getElementById("candidateList-modal-party");
+    let parties = await this.PartyModel.getParties();
+    for (let i = selectParties.options.length - 1; i >= 0; i--) {
+      selectParties.remove(i);
+    }
+
+    let el = document.createElement("option");
+    el.textContent = "Veuillez sélectionner un parti";
+    el.value = 0;
+    selectParties.appendChild(el);
+    parties.forEach((party) => {
+      el = document.createElement("option");
+      el.textContent = party.Name;
+      el.value = party.ID;
+      selectParties.appendChild(el);
+    });
+  }
+
+  async saveCandidateList() {
+    if (this.method == "POST")
+      document.getElementById("candidateList-modal-id").value = null;
+
+    let candidateList = await this.CandidateListModel.saveCandidateList(
+      this.method,
+      parseInt(document.getElementById("candidateList-modal-id").value),
+      document.getElementById("candidateList-modal-name").value,
+      parseInt(document.getElementById("candidateList-modal-party").value),
+      this.round.ID,
+      this.area.ID
+    );
+    await this.displayCandidateLists();
+    Common.toggleModal("candidateList-modal", "candidateList-modal-card");
+    this.activateCandidateList(candidateList);
+    // TODO open desks
+    // TODO open candidateLists
+    return candidateList;
+  }
 
+  async deleteCandidateList(candidateList) {
+    await this.CandidateListModel.deleteCandidateList(candidateList.ID);
+    await this.displayCandidateLists();
+    // TODO empty desks
+    // TODO empty candidateLists
   }
 }
diff --git a/web/components/management/round.js b/web/components/management/round.js
index 673fb62..ce7429d 100644
--- a/web/components/management/round.js
+++ b/web/components/management/round.js
@@ -35,6 +35,7 @@ class Round {
 
       <div class="modal" id="round-modal"></div>
       <div class="modal" id="capturers-modal"></div>
+      <div class="modal" id="candidateList-modal"></div>
     `;
     this.roundsHandler = await RoundsCard.mount("rounds-list", this)
     this.deskRoundsHandler = await RoundDesks.mount("round-desks", this)
diff --git a/web/components/management/rounds-card.js b/web/components/management/rounds-card.js
index 06d2b9a..1dfb0e2 100644
--- a/web/components/management/rounds-card.js
+++ b/web/components/management/rounds-card.js
@@ -44,7 +44,6 @@ class Round {
     this.mountModal("round-modal");
     this.handleDom();
     this.displayRounds();
-    this.refreshElections();
   }
 
   handleDom() {
@@ -77,7 +76,6 @@ class Round {
   async refreshElections() {
     let selectElection = document.getElementById("round-modal-election-id");
     let elections = await this.ElectionModel.getElections();
-    elections.forEach((election) => {});
     for (let i = selectElection.options.length - 1; i >= 0; i--) {
       selectElection.remove(i);
     }
@@ -173,10 +171,6 @@ class Round {
           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
           roundHandler.parent.candidateListHandler.round = round;
           roundHandler.parent.candidateListHandler.displayAreas();
         });
@@ -219,6 +213,9 @@ class Round {
   }
 
   async activateRound(roundToActivate) {
+    document
+      .getElementById("candidate-list-new")
+      .setAttribute("disabled", "true");
     let rounds = await this.RoundModel.getRounds();
     rounds.forEach((round) => {
       document
@@ -231,16 +228,18 @@ class Round {
     // TODO unselect candidateLists
   }
 
-  newRound() {
+  async newRound() {
     this.method = "POST";
+    await this.refreshElections();
     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) {
+  async editRound(round) {
     this.method = "PUT";
+    await this.refreshElections();
     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)
diff --git a/web/services/model/area-model.js b/web/services/model/area-model.js
index d60a9f1..f89bba6 100644
--- a/web/services/model/area-model.js
+++ b/web/services/model/area-model.js
@@ -75,7 +75,7 @@ class AreaModel {
     }
   }
 
-  async deleteElection(ID) {
+  async deleteArea(ID) {
     try {
       const response = await fetch("/api/Area/" + ID, {
         method: "delete",
diff --git a/web/services/model/candidateList-model.js b/web/services/model/candidateList-model.js
new file mode 100644
index 0000000..53bc774
--- /dev/null
+++ b/web/services/model/candidateList-model.js
@@ -0,0 +1,100 @@
+import * as Messages from "/services/messages/messages.js";
+
+let candidateListModel;
+
+export function getCandidateListModel() {
+  if (candidateListModel == null) {
+    candidateListModel = new CandidateListModel();
+  }
+  return candidateListModel;
+}
+
+class CandidateListModel {
+  constructor() {}
+
+  async getCandidateList(id) {
+    if (this.candidateLists == null) await this.refreshCandidateLists();
+    let candidateListToGet;
+    this.candidateLists.forEach((candidateList) => {
+      if (candidateList.ID == id) candidateListToGet = candidateList;
+    });
+    return candidateListToGet;
+  }
+
+  async getCandidateLists() {
+    if (this.candidateLists == null) {
+      try {
+        const response = await fetch("/api/CandidateList/", {
+          method: "GET",
+          headers: new Headers({
+            "XSRF-Token": this.current_user.xsrftoken,
+          }),
+        });
+        if (response.status !== 200) {
+          throw new Error(
+            `CandidateLists could not be fetched (status ${response.status})`
+          );
+        }
+        this.candidateLists = await response.json();
+      } catch (e) {
+        Messages.Show("is-warning", e.message);
+        console.error(e);
+      }
+    }
+    return this.candidateLists;
+  }
+
+  async saveCandidateList(method, ID, Name, PartyID, RoundID, AreaID) {
+    try {
+      const response = await fetch("/api/CandidateList/" + ID, {
+        method: method,
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: JSON.stringify({
+          ID: ID,
+          Name: Name,
+          PartyID: PartyID,
+          RoundID: RoundID,
+          AreaID: AreaID,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `CandidateList could not be updated or created (status ${response.status})`
+        );
+      }
+      this.refreshCandidateLists();
+      return await response.json();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+      return;
+    }
+  }
+
+  async deleteCandidateList(ID) {
+    try {
+      const response = await fetch("/api/CandidateList/" + ID, {
+        method: "delete",
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `CandidateList could not be deleted (status ${response.status})`
+        );
+      }
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+    }
+    await this.refreshCandidateLists();
+  }
+
+  async refreshCandidateLists() {
+    this.candidateLists = null;
+    await this.getCandidateLists();
+  }
+}
diff --git a/web/services/model/party-model.js b/web/services/model/party-model.js
index 5216213..b90aefd 100644
--- a/web/services/model/party-model.js
+++ b/web/services/model/party-model.js
@@ -93,6 +93,6 @@ class PartyModel {
 
   async refreshParties() {
     this.parties = null;
-    this.getParties();
+    await this.getParties();
   }
 }
-- 
GitLab