From b09210b1c7426db62dd9291c5deaf3652e060359 Mon Sep 17 00:00:00 2001
From: Alexis POYEN <apoyen@grandlyon.com>
Date: Thu, 11 Jun 2020 17:05:08 +0200
Subject: [PATCH] Resolve "Handle candidates"

---
 web/components/management/candidate-lists.js | 233 +++++++++++++++++--
 web/components/management/rounds-card.js     |   4 +
 web/services/model/candidate-model.js        | 115 +++++++++
 3 files changed, 331 insertions(+), 21 deletions(-)
 create mode 100644 web/services/model/candidate-model.js

diff --git a/web/components/management/candidate-lists.js b/web/components/management/candidate-lists.js
index c58460c..1b0dd46 100644
--- a/web/components/management/candidate-lists.js
+++ b/web/components/management/candidate-lists.js
@@ -5,13 +5,11 @@ 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 * as CandidateModel from "/services/model/candidate-model.js";
 import { Delete } from "/services/common/delete.js";
 
 // DOM elements
 
-// local variables
-let current_user;
-
 export async function mount(where, parent) {
   const candidateListComponent = new CandidateList(parent);
   await candidateListComponent.mount(where);
@@ -25,12 +23,14 @@ class CandidateList {
     this.AreaModel = AreaModel.getAreaModel();
     this.PartyModel = PartyModel.getPartyModel();
     this.CandidateListModel = CandidateListModel.getCandidateListModel();
+    this.CandidateModel = CandidateModel.getCandidateModel();
   }
 
   async mount(where) {
     this.AreaModel.current_user = await Auth.GetUser();
     this.PartyModel.current_user = await Auth.GetUser();
     this.CandidateListModel.current_user = await Auth.GetUser();
+    this.CandidateModel.current_user = await Auth.GetUser();
     const mountpoint = where;
     document.getElementById(mountpoint).innerHTML = /* HTML */ `
       <header class="card-header">
@@ -50,15 +50,11 @@ class CandidateList {
         <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 class="column is-two-thirds">
+          <div id="candidate-list" class="card"></div>
         </div>
       </div>
     `;
-    current_user = await Auth.GetUser();
     this.mountModal("candidateList-modal");
     this.handleDom();
   }
@@ -167,6 +163,161 @@ class CandidateList {
     `;
   }
 
+  async displayCandidates() {
+    document.getElementById("candidate-list").innerHTML = /* HTML */ `<div
+      class="table-container"
+    >
+      <table class="table is-bordered is-narrow is-hoverable is-fullwidth">
+        <thead>
+          <tr class="is-selected">
+            <th hidden>Id</th>
+            <th>Nom complet</th>
+            <th>Rang</th>
+            <th>Conseiller communautaire</th>
+            <th>Date de naissance</th>
+            <th>Incompatibilité potentielle</th>
+            <th>Refusé</th>
+            <th>Supprimé</th>
+            <th>Actions</th>
+          </tr>
+        </thead>
+        <tbody id="candidates"></tbody>
+      </table>
+    </div> `;
+
+    let candidates = await this.updateCandidates();
+    const markup = candidates
+      .map((candidate) => this.candidateTemplate(candidate))
+      .join("");
+    document.getElementById("candidates").innerHTML = markup;
+
+    let candidate = {
+      ID: 0,
+      FullName: "",
+      Rank: 0,
+      CommunityCounseller: false,
+      Birthdate: "2000-01-01",
+      PotentialIncompatibility: false,
+      Refused: false,
+      Removed: false,
+    };
+    document.getElementById("candidates").innerHTML += this.candidateTemplate(
+      candidate
+    );
+    document
+      .getElementById(`candidates-candidate-edit-0`)
+      .addEventListener("click", function () {
+        candidateListHandler.method = "POST";
+        candidateListHandler.saveCandidate(candidate.ID);
+      });
+    document.getElementById(`candidates-candidate-delete-0`).remove();
+
+    let candidateListHandler = this;
+    candidates.map((candidate) => {
+      document
+        .getElementById(`candidates-candidate-edit-${candidate.ID}`)
+        .addEventListener("click", function () {
+          candidateListHandler.method = "PUT";
+          candidateListHandler.saveCandidate(candidate.ID);
+        });
+      document
+        .getElementById(`candidates-candidate-delete-${candidate.ID}`)
+        .addEventListener("click", function () {
+          new Delete(() => {
+            candidateListHandler.deleteCandidate(candidate);
+          });
+        });
+    });
+  }
+
+  candidateTemplate(candidate) {
+    return /* HTML */ `
+      <tr id="candidates-candidate-${candidate.ID}">
+        <td hidden>
+          <input
+            class="input"
+            type="number"
+            id="${candidate.ID}-candidate-id"
+            value="${candidate.ID}"
+            disabled
+          />
+        </td>
+        <td>
+          <input
+            class="input"
+            type="text"
+            id="${candidate.ID}-candidate-fullname"
+            value="${candidate.FullName}"
+          />
+        </td>
+        <td>
+          <input
+            class="input"
+            type="number"
+            id="${candidate.ID}-candidate-rank"
+            value="${candidate.Rank}"
+          />
+        </td>
+        <td>
+          <input
+            type="checkbox"
+            id="${candidate.ID}-candidate-community-counseller"
+            ${candidate.CommunityCounseller ? "checked" : ""}
+          />
+        </td>
+        <td>
+          <input
+            class="input"
+            type="date"
+            id="${candidate.ID}-candidate-birthdate"
+            value="${candidate.Birthdate}"
+          />
+        </td>
+        <td>
+          <input
+            type="checkbox"
+            id="${candidate.ID}-candidate-potential-incompatibility"
+            ${candidate.PotentialIncompatibility ? "checked" : ""}
+          />
+        </td>
+        <td>
+          <input
+            type="checkbox"
+            id="${candidate.ID}-candidate-refused"
+            ${candidate.Refused ? "checked" : ""}
+          />
+        </td>
+        <td>
+          <input
+            type="checkbox"
+            id="${candidate.ID}-candidate-removed"
+            ${candidate.Removed ? "checked" : ""}
+          />
+        </td>
+        <td>
+          <a
+            id="candidates-candidate-edit-${candidate.ID}"
+            class="button is-success is-small"
+          >
+            <span>Valider</span>
+            <span class="icon is-small">
+              <i class="fas fa-check"></i>
+            </span>
+          </a>
+          <a
+            id="candidates-candidate-delete-${candidate.ID}"
+            class="button is-danger is-small"
+          >
+            <span>Supprimer</span>
+            <span class="icon is-small">
+              <i class="fas fa-times"></i>
+            </span>
+          </a>
+        </td>
+      </tr>
+    `;
+  }
+
   handleDom() {
     let candidateListHandler = this;
     document
@@ -236,7 +387,18 @@ class CandidateList {
     document.getElementById("candidate-list-new").removeAttribute("disabled");
   }
 
-  async activateCandidateList() {}
+  async activateCandidateList(candidateListToActivate) {
+    this.candidateList = candidateListToActivate;
+    let candidateLists = await this.updateCandidateLists();
+    candidateLists.forEach((candidateList) => {
+      document
+        .getElementById(`candidateLists-candidateList-${candidateList.ID}`)
+        .classList.remove("active-card");
+    });
+    document
+      .getElementById(`candidateLists-candidateList-${candidateListToActivate.ID}`)
+      .classList.add("active-card");
+  }
 
   async displayCandidateLists() {
     let candidateListHandler = this;
@@ -265,13 +427,12 @@ class CandidateList {
             candidateListHandler.deleteCandidateList(candidateList);
           });
         });
-      // document
-      //   .getElementById(`areas-area-${candidateList.ID}`)
-      //   .addEventListener("click", async function () {
-      //     await candidateListHandler.activateArea(candidateList);
-      //     candidateListHandler.area = candidateList;
-      //     await candidateListHandler.displayCandidateLists();
-      //   });
+      document
+        .getElementById(`candidateLists-candidateList-${candidateList.ID}`)
+        .addEventListener("click", async function () {
+          await candidateListHandler.activateCandidateList(candidateList);
+          await candidateListHandler.displayCandidates();
+        });
     });
   }
 
@@ -283,6 +444,14 @@ class CandidateList {
     });
   }
 
+  async updateCandidates() {
+    let candidateListHandler = this;
+    let candidates = await this.CandidateModel.getCandidates();
+    return candidates.filter(function (candidate) {
+      return candidate.CandidateListID == candidateListHandler.candidateList.ID;
+    });
+  }
+
   async newCandidateList() {
     this.method = "POST";
     await this.refreshParties();
@@ -337,15 +506,37 @@ class CandidateList {
     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
+    // TODO empty Candidates
+  }
+
+  async saveCandidate(candidateID) {
+    let candidate = await this.CandidateModel.saveCandidate(
+      this.method,
+      candidateID,
+      this.candidateList.ID,
+      document.getElementById(candidateID + "-candidate-fullname").value,
+      parseInt(document.getElementById(candidateID + "-candidate-rank").value),
+      document.getElementById(candidateID + "-candidate-community-counseller")
+        .checked,
+      document.getElementById(candidateID + "-candidate-birthdate").value,
+      document.getElementById(
+        candidateID + "-candidate-potential-incompatibility"
+      ).checked,
+      document.getElementById(candidateID + "-candidate-refused").checked,
+      document.getElementById(candidateID + "-candidate-removed").checked
+    );
+    await this.displayCandidates();
+    return candidate;
+  }
+
+  async deleteCandidate(candidate) {
+    await this.CandidateModel.deleteCandidate(candidate.ID);
+    await this.displayCandidates();
   }
 }
diff --git a/web/components/management/rounds-card.js b/web/components/management/rounds-card.js
index 1dfb0e2..7793cca 100644
--- a/web/components/management/rounds-card.js
+++ b/web/components/management/rounds-card.js
@@ -226,6 +226,10 @@ class Round {
       .getElementById(`rounds-round-${roundToActivate.ID}`)
       .classList.add("active-card");
     // TODO unselect candidateLists
+    document.getElementById("rounds-list").setAttribute("hidden", "true");
+    document.getElementById("rounds-list").parentElement.className = "column";
+    document.getElementById("candidate-lists").parentElement.className =
+      "column is-full";
   }
 
   async newRound() {
diff --git a/web/services/model/candidate-model.js b/web/services/model/candidate-model.js
new file mode 100644
index 0000000..a415342
--- /dev/null
+++ b/web/services/model/candidate-model.js
@@ -0,0 +1,115 @@
+import * as Messages from "/services/messages/messages.js";
+
+let candidateModel;
+
+export function getCandidateModel() {
+  if (candidateModel == null) {
+    candidateModel = new CandidateModel();
+  }
+  return candidateModel;
+}
+
+class CandidateModel {
+  constructor() {}
+
+  async getCandidate(id) {
+    if (this.candidates == null) await this.refreshCandidates();
+    let candidateToGet;
+    this.candidates.forEach((candidate) => {
+      if (candidate.ID == id) candidateToGet = candidate;
+    });
+    return candidateToGet;
+  }
+
+  async getCandidates() {
+    if (this.candidates == null) {
+      try {
+        const response = await fetch("/api/Candidate/", {
+          method: "GET",
+          headers: new Headers({
+            "XSRF-Token": this.current_user.xsrftoken,
+          }),
+        });
+        if (response.status !== 200) {
+          throw new Error(
+            `Candidates could not be fetched (status ${response.status})`
+          );
+        }
+        this.candidates = await response.json();
+      } catch (e) {
+        Messages.Show("is-warning", e.message);
+        console.error(e);
+      }
+    }
+    return this.candidates;
+  }
+
+  async saveCandidate(
+    method,
+    ID,
+    CandidateListID,
+    FullName,
+    Rank,
+    CommunityCounseller,
+    Birthdate,
+    PotentialIncompatibility,
+    Refused,
+    Removed
+  ) {
+    try {
+      const response = await fetch("/api/Candidate/" + ID, {
+        method: method,
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: JSON.stringify({
+          ID: ID,
+          CandidateListID: CandidateListID,
+          FullName: FullName,
+          Rank: Rank,
+          CommunityCounseller: CommunityCounseller,
+          Birthdate: Birthdate,
+          PotentialIncompatibility: PotentialIncompatibility,
+          Refused: Refused,
+          Removed: Removed,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Candidate could not be updated or created (status ${response.status})`
+        );
+      }
+      this.refreshCandidates();
+      return await response.json();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+      return;
+    }
+  }
+
+  async deleteCandidate(ID) {
+    try {
+      const response = await fetch("/api/Candidate/" + ID, {
+        method: "delete",
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Candidate could not be deleted (status ${response.status})`
+        );
+      }
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+    }
+    await this.refreshCandidates();
+  }
+
+  async refreshCandidates() {
+    this.candidates = null;
+    await this.getCandidates();
+  }
+}
-- 
GitLab